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 02111-1301 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*/
41ulong subquery_cache_miss, subquery_cache_hit;
42
43Expression_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
58void 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
78static 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
95void 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
175error:
176 disable_cache();
177 DBUG_VOID_RETURN;
178}
179
180
181Expression_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
212Expression_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
259my_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
315err:
316 disable_cache();
317 DBUG_RETURN(TRUE);
318}
319
320
321void 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
340const char *Expression_cache_tracker::state_str[3]=
341{"uninitialized", "disabled", "enabled"};
342