mirror of
				https://github.com/MariaDB/server.git
				synced 2025-10-31 02:46:29 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			344 lines
		
	
	
	
		
			9.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			344 lines
		
	
	
	
		
			9.4 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->detach_from_cache();
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   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= cache_table_param.func_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();
 | |
|     if (tracker)
 | |
|       tracker->detach_from_cache();
 | |
|     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,
 | |
|               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"};
 | 
