1 /* Copyright (C) 2010-2011 Monty Program Ab & Oleksandr Byelkin
2 
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License as published by
5    the Free Software Foundation; version 2 of the License.
6 
7    This program is distributed in the hope that it will be useful,
8    but WITHOUT ANY WARRANTY; without even the implied warranty of
9    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10    GNU General Public License for more details.
11 
12    You should have received a copy of the GNU General Public License
13    along with this program; if not, write to the Free Software
14    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
15 
16 #include "mariadb.h"
17 #include "sql_base.h"
18 #include "sql_select.h"
19 #include "sql_expression_cache.h"
20 
21 /**
22   Minimum hit ration to proceed on disk if in memory table overflowed.
23   hit_rate = hit / (miss + hit);
24 */
25 #define EXPCACHE_MIN_HIT_RATE_FOR_DISK_TABLE 0.7
26 /**
27   Minimum hit ratio to keep in memory table (do not switch cache off)
28   hit_rate = hit / (miss + hit);
29 */
30 #define EXPCACHE_MIN_HIT_RATE_FOR_MEM_TABLE  0.2
31 /**
32   Number of cache miss to check hit ratio (maximum cache performance
33   impact in the case when the cache is not applicable)
34 */
35 #define EXPCACHE_CHECK_HIT_RATIO_AFTER 200
36 
37 /*
38   Expression cache is used only for caching subqueries now, so its statistic
39   variables we call subquery_cache*.
40 */
41 ulong subquery_cache_miss, subquery_cache_hit;
42 
Expression_cache_tmptable(THD * thd,List<Item> & dependants,Item * value)43 Expression_cache_tmptable::Expression_cache_tmptable(THD *thd,
44                                                      List<Item> &dependants,
45                                                      Item *value)
46   :cache_table(NULL), table_thd(thd), tracker(NULL), items(dependants), val(value),
47    hit(0), miss(0), inited (0)
48 {
49   DBUG_ENTER("Expression_cache_tmptable::Expression_cache_tmptable");
50   DBUG_VOID_RETURN;
51 };
52 
53 
54 /**
55   Disable cache
56 */
57 
disable_cache()58 void Expression_cache_tmptable::disable_cache()
59 {
60   if (cache_table->file->inited)
61     cache_table->file->ha_index_end();
62   free_tmp_table(table_thd, cache_table);
63   cache_table= NULL;
64   update_tracker();
65   if (tracker)
66     tracker->cache= NULL;
67 }
68 
69 
70 /**
71   Field enumerator for TABLE::add_tmp_key
72 
73   @param arg             reference variable with current field number
74 
75   @return field number
76 */
77 
field_enumerator(uchar * arg)78 static uint field_enumerator(uchar *arg)
79 {
80   return ((uint*)arg)[0]++;
81 }
82 
83 
84 /**
85   Initialize temporary table and auxiliary structures for the expression
86   cache
87 
88   @details
89   The function creates a temporary table for the expression cache, defines
90   the search index and initializes auxiliary search structures used to check
91   whether a given set of of values of the expression parameters is in some
92   cache entry.
93 */
94 
init()95 void Expression_cache_tmptable::init()
96 {
97   List_iterator<Item> li(items);
98   Item_iterator_list it(li);
99   uint field_counter;
100   LEX_CSTRING cache_table_name= { STRING_WITH_LEN("subquery-cache-table") };
101   DBUG_ENTER("Expression_cache_tmptable::init");
102   DBUG_ASSERT(!inited);
103   inited= TRUE;
104   cache_table= NULL;
105 
106   if (items.elements == 0)
107   {
108     DBUG_PRINT("info", ("All parameters were removed by optimizer."));
109     DBUG_VOID_RETURN;
110   }
111 
112   /* add result field */
113   items.push_front(val);
114 
115   cache_table_param.init();
116   /* dependent items and result */
117   cache_table_param.field_count= items.elements;
118   /* postpone table creation to index description */
119   cache_table_param.skip_create_table= 1;
120 
121   if (!(cache_table= create_tmp_table(table_thd, &cache_table_param,
122                                       items, (ORDER*) NULL,
123                                       FALSE, TRUE,
124                                       ((table_thd->variables.option_bits |
125                                         TMP_TABLE_ALL_COLUMNS) &
126                                         ~TMP_TABLE_FORCE_MYISAM),
127                                       HA_POS_ERROR,
128                                       &cache_table_name,
129                                       TRUE)))
130   {
131     DBUG_PRINT("error", ("create_tmp_table failed, caching switched off"));
132     DBUG_VOID_RETURN;
133   }
134 
135   if (cache_table->s->db_type() != heap_hton)
136   {
137     DBUG_PRINT("error", ("we need only heap table"));
138     goto error;
139   }
140 
141   field_counter= 1;
142 
143   if (cache_table->alloc_keys(1) ||
144       cache_table->add_tmp_key(0, items.elements - 1, &field_enumerator,
145                                 (uchar*)&field_counter, TRUE) ||
146       ref.tmp_table_index_lookup_init(table_thd, cache_table->key_info, it,
147                                       TRUE, 1 /* skip result field*/))
148   {
149     DBUG_PRINT("error", ("creating index failed"));
150     goto error;
151   }
152   cache_table->s->keys= 1;
153   ref.null_rejecting= 1;
154   ref.disable_cache= FALSE;
155   ref.has_record= 0;
156   ref.use_count= 0;
157 
158 
159   if (open_tmp_table(cache_table))
160   {
161     DBUG_PRINT("error", ("Opening (creating) temporary table failed"));
162     goto error;
163   }
164 
165   if (!(cached_result= new (table_thd->mem_root)
166         Item_field(table_thd, cache_table->field[0])))
167   {
168     DBUG_PRINT("error", ("Creating Item_field failed"));
169     goto error;
170   }
171 
172   update_tracker();
173   DBUG_VOID_RETURN;
174 
175 error:
176   disable_cache();
177   DBUG_VOID_RETURN;
178 }
179 
180 
~Expression_cache_tmptable()181 Expression_cache_tmptable::~Expression_cache_tmptable()
182 {
183   /* Add accumulated statistics */
184   statistic_add(subquery_cache_miss, miss, &LOCK_status);
185   statistic_add(subquery_cache_hit, hit, &LOCK_status);
186 
187   if (cache_table)
188     disable_cache();
189   else
190   {
191     update_tracker();
192     tracker= NULL;
193   }
194 }
195 
196 
197 /**
198   Check if a given set of parameters of the expression is in the cache
199 
200   @param [out] value     the expression value found in the cache if any
201 
202   @details
203   For a given set of the parameters of the expression the function
204   checks whether it can be found in some entry of the cache. If so
205   the function returns the result of the expression extracted from
206   the cache.
207 
208   @retval Expression_cache::HIT if the set of parameters is in the cache
209   @retval Expression_cache::MISS - otherwise
210 */
211 
check_value(Item ** value)212 Expression_cache::result Expression_cache_tmptable::check_value(Item **value)
213 {
214   int res;
215   DBUG_ENTER("Expression_cache_tmptable::check_value");
216 
217   if (cache_table)
218   {
219     DBUG_PRINT("info", ("status: %u  has_record %u",
220                         (uint)cache_table->status, (uint)ref.has_record));
221     if ((res= join_read_key2(table_thd, NULL, cache_table, &ref)) == 1)
222       DBUG_RETURN(ERROR);
223 
224     if (res)
225     {
226       if (((++miss) == EXPCACHE_CHECK_HIT_RATIO_AFTER) &&
227           ((double)hit / ((double)hit + miss)) <
228           EXPCACHE_MIN_HIT_RATE_FOR_MEM_TABLE)
229       {
230         DBUG_PRINT("info",
231                    ("Early check: hit rate is not so good to keep the cache"));
232         disable_cache();
233       }
234 
235       DBUG_RETURN(MISS);
236     }
237 
238     hit++;
239     *value= cached_result;
240     DBUG_RETURN(Expression_cache::HIT);
241   }
242   DBUG_RETURN(Expression_cache::MISS);
243 }
244 
245 
246 /**
247   Put a new entry into the expression cache
248 
249   @param value     the result of the expression to be put into the cache
250 
251   @details
252   The function evaluates 'value' and puts the result into the cache as the
253   result of the expression for the current set of parameters.
254 
255   @retval FALSE OK
256   @retval TRUE  Error
257 */
258 
put_value(Item * value)259 my_bool Expression_cache_tmptable::put_value(Item *value)
260 {
261   int error;
262   DBUG_ENTER("Expression_cache_tmptable::put_value");
263   DBUG_ASSERT(inited);
264 
265   if (!cache_table)
266   {
267     DBUG_PRINT("info", ("No table so behave as we successfully put value"));
268     DBUG_RETURN(FALSE);
269   }
270 
271   *(items.head_ref())= value;
272   fill_record(table_thd, cache_table, cache_table->field, items, TRUE, TRUE);
273   if (unlikely(table_thd->is_error()))
274     goto err;;
275 
276   if (unlikely((error=
277                 cache_table->file->ha_write_tmp_row(cache_table->record[0]))))
278   {
279     /* create_myisam_from_heap will generate error if needed */
280     if (cache_table->file->is_fatal_error(error, HA_CHECK_DUP))
281       goto err;
282     else
283     {
284       double hit_rate= ((double)hit / ((double)hit + miss));
285       DBUG_ASSERT(miss > 0);
286       if (hit_rate < EXPCACHE_MIN_HIT_RATE_FOR_MEM_TABLE)
287       {
288         DBUG_PRINT("info", ("hit rate is not so good to keep the cache"));
289         disable_cache();
290         DBUG_RETURN(FALSE);
291       }
292       else if (hit_rate < EXPCACHE_MIN_HIT_RATE_FOR_DISK_TABLE)
293       {
294         DBUG_PRINT("info", ("hit rate is not so good to go to disk"));
295         if (cache_table->file->ha_delete_all_rows() ||
296             cache_table->file->ha_write_tmp_row(cache_table->record[0]))
297           goto err;
298       }
299       else
300       {
301         if (create_internal_tmp_table_from_heap(table_thd, cache_table,
302                                                 cache_table_param.start_recinfo,
303                                                 &cache_table_param.recinfo,
304                                                 error, 1, NULL))
305           goto err;
306       }
307     }
308   }
309   cache_table->status= 0; /* cache_table->record contains an existed record */
310   ref.has_record= TRUE; /* the same as above */
311   DBUG_PRINT("info", ("has_record: TRUE  status: 0"));
312 
313   DBUG_RETURN(FALSE);
314 
315 err:
316   disable_cache();
317   DBUG_RETURN(TRUE);
318 }
319 
320 
print(String * str,enum_query_type query_type)321 void Expression_cache_tmptable::print(String *str, enum_query_type query_type)
322 {
323   List_iterator<Item> li(items);
324   Item *item;
325   bool is_first= TRUE;
326 
327   str->append('<');
328   li++;  // skip result field
329   while ((item= li++))
330   {
331     if (!is_first)
332       str->append(',');
333     item->print(str, query_type);
334     is_first= FALSE;
335   }
336   str->append('>');
337 }
338 
339 
340 const char *Expression_cache_tracker::state_str[3]=
341 {"uninitialized", "disabled", "enabled"};
342