mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 12:02:42 +01:00
341 lines
9.3 KiB
C++
341 lines
9.3 KiB
C++
/* 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
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
|
|
|
|
#include "mariadb.h"
|
|
#include "sql_base.h"
|
|
#include "sql_select.h"
|
|
#include "sql_expression_cache.h"
|
|
|
|
/**
|
|
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
|
|
/**
|
|
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
|
|
|
|
/*
|
|
Expression cache is used only for caching subqueries now, so its statistic
|
|
variables we call subquery_cache*.
|
|
*/
|
|
ulong subquery_cache_miss, subquery_cache_hit;
|
|
|
|
Expression_cache_tmptable::Expression_cache_tmptable(THD *thd,
|
|
List<Item> &dependants,
|
|
Item *value)
|
|
:cache_table(NULL), table_thd(thd), tracker(NULL), items(dependants), val(value),
|
|
hit(0), miss(0), inited (0)
|
|
{
|
|
DBUG_ENTER("Expression_cache_tmptable::Expression_cache_tmptable");
|
|
DBUG_VOID_RETURN;
|
|
};
|
|
|
|
|
|
/**
|
|
Disable cache
|
|
*/
|
|
|
|
void Expression_cache_tmptable::disable_cache()
|
|
{
|
|
if (cache_table->file->inited)
|
|
cache_table->file->ha_index_end();
|
|
free_tmp_table(table_thd, cache_table);
|
|
cache_table= NULL;
|
|
update_tracker();
|
|
if (tracker)
|
|
tracker->cache= NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
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()
|
|
{
|
|
List_iterator<Item> li(items);
|
|
Item_iterator_list it(li);
|
|
uint field_counter;
|
|
LEX_CSTRING cache_table_name= { STRING_WITH_LEN("subquery-cache-table") };
|
|
DBUG_ENTER("Expression_cache_tmptable::init");
|
|
DBUG_ASSERT(!inited);
|
|
inited= TRUE;
|
|
cache_table= NULL;
|
|
|
|
if (items.elements == 0)
|
|
{
|
|
DBUG_PRINT("info", ("All parameters were removed by optimizer."));
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/* add result field */
|
|
items.push_front(val);
|
|
|
|
cache_table_param.init();
|
|
/* dependent items and result */
|
|
cache_table_param.field_count= items.elements;
|
|
/* 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,
|
|
FALSE, TRUE,
|
|
((table_thd->variables.option_bits |
|
|
TMP_TABLE_ALL_COLUMNS) &
|
|
~TMP_TABLE_FORCE_MYISAM),
|
|
HA_POS_ERROR,
|
|
&cache_table_name,
|
|
TRUE)))
|
|
{
|
|
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;
|
|
}
|
|
|
|
field_counter= 1;
|
|
|
|
if (cache_table->alloc_keys(1) ||
|
|
cache_table->add_tmp_key(0, items.elements - 1, &field_enumerator,
|
|
(uchar*)&field_counter, TRUE) ||
|
|
ref.tmp_table_index_lookup_init(table_thd, cache_table->key_info, it,
|
|
TRUE, 1 /* skip result field*/))
|
|
{
|
|
DBUG_PRINT("error", ("creating index failed"));
|
|
goto error;
|
|
}
|
|
cache_table->s->keys= 1;
|
|
ref.null_rejecting= 1;
|
|
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;
|
|
}
|
|
|
|
if (!(cached_result= new (table_thd->mem_root)
|
|
Item_field(table_thd, cache_table->field[0])))
|
|
{
|
|
DBUG_PRINT("error", ("Creating Item_field failed"));
|
|
goto error;
|
|
}
|
|
|
|
update_tracker();
|
|
DBUG_VOID_RETURN;
|
|
|
|
error:
|
|
disable_cache();
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
Expression_cache_tmptable::~Expression_cache_tmptable()
|
|
{
|
|
/* Add accumulated statistics */
|
|
statistic_add(subquery_cache_miss, miss, &LOCK_status);
|
|
statistic_add(subquery_cache_hit, hit, &LOCK_status);
|
|
|
|
if (cache_table)
|
|
disable_cache();
|
|
else
|
|
{
|
|
update_tracker();
|
|
tracker= NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
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);
|
|
|
|
if (res)
|
|
{
|
|
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();
|
|
}
|
|
|
|
DBUG_RETURN(MISS);
|
|
}
|
|
|
|
hit++;
|
|
*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;
|
|
fill_record(table_thd, cache_table, cache_table->field, items, TRUE, TRUE);
|
|
if (unlikely(table_thd->is_error()))
|
|
goto err;;
|
|
|
|
if (unlikely((error=
|
|
cache_table->file->ha_write_tmp_row(cache_table->record[0]))))
|
|
{
|
|
/* create_myisam_from_heap will generate error if needed */
|
|
if (cache_table->file->is_fatal_error(error, HA_CHECK_DUP))
|
|
goto err;
|
|
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"));
|
|
disable_cache();
|
|
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,
|
|
error, 1, NULL))
|
|
goto err;
|
|
}
|
|
}
|
|
}
|
|
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:
|
|
disable_cache();
|
|
DBUG_RETURN(TRUE);
|
|
}
|
|
|
|
|
|
void Expression_cache_tmptable::print(String *str, enum_query_type query_type)
|
|
{
|
|
List_iterator<Item> li(items);
|
|
Item *item;
|
|
bool is_first= TRUE;
|
|
|
|
str->append('<');
|
|
li++; // skip result field
|
|
while ((item= li++))
|
|
{
|
|
if (!is_first)
|
|
str->append(',');
|
|
item->print(str, query_type);
|
|
is_first= FALSE;
|
|
}
|
|
str->append('>');
|
|
}
|
|
|
|
|
|
const char *Expression_cache_tracker::state_str[3]=
|
|
{"uninitialized", "disabled", "enabled"};
|