sql_expression_cache.cc 9.42 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13
/* Copyright (C) 2010-2011 Monty Program Ab & Oleksandr Byelkin

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
Michal Schorm's avatar
Michal Schorm committed
14
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
unknown's avatar
unknown committed
15

16
#include "mariadb.h"
17
#include "sql_base.h"
unknown's avatar
unknown committed
18
#include "sql_select.h"
19
#include "sql_expression_cache.h"
unknown's avatar
unknown committed
20

21 22 23 24 25 26 27 28 29 30
/**
  Minimum hit ration to proceed on disk if in memory table overflowed.
  hit_rate = hit / (miss + hit);
*/
#define EXPCACHE_MIN_HIT_RATE_FOR_DISK_TABLE 0.7
/**
  Minimum hit ratio to keep in memory table (do not switch cache off)
  hit_rate = hit / (miss + hit);
*/
#define EXPCACHE_MIN_HIT_RATE_FOR_MEM_TABLE  0.2
31 32 33 34 35
/**
  Number of cache miss to check hit ratio (maximum cache performance
  impact in the case when the cache is not applicable)
*/
#define EXPCACHE_CHECK_HIT_RATIO_AFTER 200
36

unknown's avatar
unknown committed
37 38 39 40
/*
  Expression cache is used only for caching subqueries now, so its statistic
  variables we call subquery_cache*.
*/
unknown's avatar
unknown committed
41
ulong subquery_cache_miss, subquery_cache_hit;
unknown's avatar
unknown committed
42 43

Expression_cache_tmptable::Expression_cache_tmptable(THD *thd,
unknown's avatar
unknown committed
44 45
                                                     List<Item> &dependants,
                                                     Item *value)
46
  :cache_table(NULL), table_thd(thd), tracker(NULL), items(dependants), val(value),
47
   hit(0), miss(0), inited (0)
unknown's avatar
unknown committed
48 49 50 51 52 53
{
  DBUG_ENTER("Expression_cache_tmptable::Expression_cache_tmptable");
  DBUG_VOID_RETURN;
};


54 55 56 57 58 59
/**
  Disable cache
*/

void Expression_cache_tmptable::disable_cache()
{
60 61
  if (cache_table->file->inited)
    cache_table->file->ha_index_end();
62 63
  free_tmp_table(table_thd, cache_table);
  cache_table= NULL;
64 65
  update_tracker();
  if (tracker)
66
    tracker->detach_from_cache();
67 68 69
}


unknown's avatar
unknown committed
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
/**
  Field enumerator for TABLE::add_tmp_key

  @param arg             reference variable with current field number

  @return field number
*/

static uint field_enumerator(uchar *arg)
{
  return ((uint*)arg)[0]++;
}


/**
  Initialize temporary table and auxiliary structures for the expression
  cache

  @details
  The function creates a temporary table for the expression cache, defines
  the search index and initializes auxiliary search structures used to check
  whether a given set of of values of the expression parameters is in some
  cache entry.
*/

void Expression_cache_tmptable::init()
{
unknown's avatar
unknown committed
97 98
  List_iterator<Item> li(items);
  Item_iterator_list it(li);
unknown's avatar
unknown committed
99
  uint field_counter;
100
  LEX_CSTRING cache_table_name= { STRING_WITH_LEN("subquery-cache-table") };
unknown's avatar
unknown committed
101 102 103
  DBUG_ENTER("Expression_cache_tmptable::init");
  DBUG_ASSERT(!inited);
  inited= TRUE;
104
  cache_table= NULL;
unknown's avatar
unknown committed
105

unknown's avatar
unknown committed
106
  if (items.elements == 0)
107 108 109 110
  {
    DBUG_PRINT("info", ("All parameters were removed by optimizer."));
    DBUG_VOID_RETURN;
  }
unknown's avatar
unknown committed
111

unknown's avatar
unknown committed
112 113 114
  /* add result field */
  items.push_front(val);

unknown's avatar
unknown committed
115 116
  cache_table_param.init();
  /* dependent items and result */
117
  cache_table_param.field_count= cache_table_param.func_count= items.elements;
unknown's avatar
unknown committed
118 119 120 121 122
  /* postpone table creation to index description */
  cache_table_param.skip_create_table= 1;

  if (!(cache_table= create_tmp_table(table_thd, &cache_table_param,
                                      items, (ORDER*) NULL,
unknown's avatar
unknown committed
123
                                      FALSE, TRUE,
124
                                      ((table_thd->variables.option_bits |
unknown's avatar
unknown committed
125
                                        TMP_TABLE_ALL_COLUMNS) &
126
                                        ~TMP_TABLE_FORCE_MYISAM),
unknown's avatar
unknown committed
127
                                      HA_POS_ERROR,
128
                                      &cache_table_name,
129
                                      TRUE)))
unknown's avatar
unknown committed
130 131 132 133 134 135 136 137 138 139 140
  {
    DBUG_PRINT("error", ("create_tmp_table failed, caching switched off"));
    DBUG_VOID_RETURN;
  }

  if (cache_table->s->db_type() != heap_hton)
  {
    DBUG_PRINT("error", ("we need only heap table"));
    goto error;
  }

unknown's avatar
unknown committed
141
  field_counter= 1;
unknown's avatar
unknown committed
142 143

  if (cache_table->alloc_keys(1) ||
Sergei Golubchik's avatar
Sergei Golubchik committed
144 145
      cache_table->add_tmp_key(0, items.elements - 1, &field_enumerator,
                                (uchar*)&field_counter, TRUE) ||
unknown's avatar
unknown committed
146
      ref.tmp_table_index_lookup_init(table_thd, cache_table->key_info, it,
unknown's avatar
unknown committed
147
                                      TRUE, 1 /* skip result field*/))
unknown's avatar
unknown committed
148 149 150 151 152 153
  {
    DBUG_PRINT("error", ("creating index failed"));
    goto error;
  }
  cache_table->s->keys= 1;
  ref.null_rejecting= 1;
154
  ref.const_ref_part_map= 0;
unknown's avatar
unknown committed
155 156 157 158 159 160 161 162 163 164 165
  ref.disable_cache= FALSE;
  ref.has_record= 0;
  ref.use_count= 0;


  if (open_tmp_table(cache_table))
  {
    DBUG_PRINT("error", ("Opening (creating) temporary table failed"));
    goto error;
  }

Monty's avatar
Monty committed
166 167
  if (!(cached_result= new (table_thd->mem_root)
        Item_field(table_thd, cache_table->field[0])))
unknown's avatar
unknown committed
168 169 170 171 172
  {
    DBUG_PRINT("error", ("Creating Item_field failed"));
    goto error;
  }

173
  update_tracker();
unknown's avatar
unknown committed
174 175 176
  DBUG_VOID_RETURN;

error:
177
  disable_cache();
unknown's avatar
unknown committed
178 179 180 181 182 183
  DBUG_VOID_RETURN;
}


Expression_cache_tmptable::~Expression_cache_tmptable()
{
184 185 186 187
  /* Add accumulated statistics */
  statistic_add(subquery_cache_miss, miss, &LOCK_status);
  statistic_add(subquery_cache_hit, hit, &LOCK_status);

unknown's avatar
unknown committed
188
  if (cache_table)
189
    disable_cache();
190 191
  else
  {
192
    update_tracker();
193 194
    if (tracker)
      tracker->detach_from_cache();
195
    tracker= NULL;
196
  }
unknown's avatar
unknown committed
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
}


/**
  Check if a given set of parameters of the expression is in the cache

  @param [out] value     the expression value found in the cache if any

  @details
  For a given set of the parameters of the expression the function
  checks whether it can be found in some entry of the cache. If so
  the function returns the result of the expression extracted from
  the cache.

  @retval Expression_cache::HIT if the set of parameters is in the cache
  @retval Expression_cache::MISS - otherwise
*/

Expression_cache::result Expression_cache_tmptable::check_value(Item **value)
{
  int res;
  DBUG_ENTER("Expression_cache_tmptable::check_value");

  if (cache_table)
  {
    DBUG_PRINT("info", ("status: %u  has_record %u",
                        (uint)cache_table->status, (uint)ref.has_record));
    if ((res= join_read_key2(table_thd, NULL, cache_table, &ref)) == 1)
      DBUG_RETURN(ERROR);
226

unknown's avatar
unknown committed
227
    if (res)
unknown's avatar
unknown committed
228
    {
229 230 231 232 233 234 235 236 237
      if (((++miss) == EXPCACHE_CHECK_HIT_RATIO_AFTER) &&
          ((double)hit / ((double)hit + miss)) <
          EXPCACHE_MIN_HIT_RATE_FOR_MEM_TABLE)
      {
        DBUG_PRINT("info",
                   ("Early check: hit rate is not so good to keep the cache"));
        disable_cache();
      }

unknown's avatar
unknown committed
238 239 240
      DBUG_RETURN(MISS);
    }

241
    hit++;
unknown's avatar
unknown committed
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
    *value= cached_result;
    DBUG_RETURN(Expression_cache::HIT);
  }
  DBUG_RETURN(Expression_cache::MISS);
}


/**
  Put a new entry into the expression cache

  @param value     the result of the expression to be put into the cache

  @details
  The function evaluates 'value' and puts the result into the cache as the
  result of the expression for the current set of parameters.

  @retval FALSE OK
  @retval TRUE  Error
*/

my_bool Expression_cache_tmptable::put_value(Item *value)
{
  int error;
  DBUG_ENTER("Expression_cache_tmptable::put_value");
  DBUG_ASSERT(inited);

  if (!cache_table)
  {
    DBUG_PRINT("info", ("No table so behave as we successfully put value"));
    DBUG_RETURN(FALSE);
  }

  *(items.head_ref())= value;
275
  fill_record(table_thd, cache_table, cache_table->field, items, TRUE, TRUE);
276
  if (unlikely(table_thd->is_error()))
unknown's avatar
unknown committed
277 278
    goto err;;

279 280
  if (unlikely((error=
                cache_table->file->ha_write_tmp_row(cache_table->record[0]))))
unknown's avatar
unknown committed
281 282
  {
    /* create_myisam_from_heap will generate error if needed */
283
    if (cache_table->file->is_fatal_error(error, HA_CHECK_DUP))
unknown's avatar
unknown committed
284
      goto err;
285 286 287 288 289 290 291
    else
    {
      double hit_rate= ((double)hit / ((double)hit + miss));
      DBUG_ASSERT(miss > 0);
      if (hit_rate < EXPCACHE_MIN_HIT_RATE_FOR_MEM_TABLE)
      {
        DBUG_PRINT("info", ("hit rate is not so good to keep the cache"));
292
        disable_cache();
293 294 295 296 297 298 299 300 301 302 303 304 305 306
        DBUG_RETURN(FALSE);
      }
      else if (hit_rate < EXPCACHE_MIN_HIT_RATE_FOR_DISK_TABLE)
      {
        DBUG_PRINT("info", ("hit rate is not so good to go to disk"));
        if (cache_table->file->ha_delete_all_rows() ||
            cache_table->file->ha_write_tmp_row(cache_table->record[0]))
          goto err;
      }
      else
      {
        if (create_internal_tmp_table_from_heap(table_thd, cache_table,
                                                cache_table_param.start_recinfo,
                                                &cache_table_param.recinfo,
307
                                                error, 1, NULL))
308 309 310
          goto err;
      }
    }
unknown's avatar
unknown committed
311 312 313 314 315 316 317 318
  }
  cache_table->status= 0; /* cache_table->record contains an existed record */
  ref.has_record= TRUE; /* the same as above */
  DBUG_PRINT("info", ("has_record: TRUE  status: 0"));

  DBUG_RETURN(FALSE);

err:
319
  disable_cache();
unknown's avatar
unknown committed
320 321
  DBUG_RETURN(TRUE);
}
322 323 324 325


void Expression_cache_tmptable::print(String *str, enum_query_type query_type)
{
unknown's avatar
unknown committed
326 327
  List_iterator<Item> li(items);
  Item *item;
328 329 330
  bool is_first= TRUE;

  str->append('<');
unknown's avatar
unknown committed
331
  li++;  // skip result field
332 333 334 335
  while ((item= li++))
  {
    if (!is_first)
      str->append(',');
unknown's avatar
unknown committed
336
    item->print(str, query_type);
337 338 339 340
    is_first= FALSE;
  }
  str->append('>');
}
341 342


343 344
const char *Expression_cache_tracker::state_str[3]=
{"uninitialized", "disabled", "enabled"};