| 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 | |