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 | */ |
41 | ulong subquery_cache_miss, subquery_cache_hit; |
42 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |