diff options
Diffstat (limited to 'storage/innobase/dict/dict0stats.cc')
-rw-r--r-- | storage/innobase/dict/dict0stats.cc | 311 |
1 files changed, 173 insertions, 138 deletions
diff --git a/storage/innobase/dict/dict0stats.cc b/storage/innobase/dict/dict0stats.cc index 8bf02f9785c..928bdb3f2ef 100644 --- a/storage/innobase/dict/dict0stats.cc +++ b/storage/innobase/dict/dict0stats.cc @@ -1,6 +1,6 @@ /***************************************************************************** -Copyright (c) 2009, 2013, Oracle and/or its affiliates. All Rights Reserved. +Copyright (c) 2009, 2014, Oracle and/or its affiliates. All Rights Reserved. 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 @@ -46,6 +46,7 @@ Created Jan 06, 2010 Vasil Dimov #include "ut0rnd.h" /* ut_rnd_interval() */ #include "ut0ut.h" /* ut_format_name(), ut_time() */ +#include <map> #include <vector> /* Sampling algorithm description @{ @@ -143,6 +144,17 @@ data: b,b,b,b,b,b,g,g,j,j,j, x, y then we would store 5,7,10,11,12 in the array. */ typedef std::vector<ib_uint64_t> boundaries_t; +/* This is used to arrange the index based on the index name. +@return true if index_name1 is smaller than index_name2. */ +struct index_cmp +{ + bool operator()(const char* index_name1, const char* index_name2) const { + return(strcmp(index_name1, index_name2) < 0); + } +}; + +typedef std::map<const char*, dict_index_t*, index_cmp> index_map_t; + /*********************************************************************//** Checks whether an index should be ignored in stats manipulations: * stats fetch @@ -266,22 +278,24 @@ dict_stats_persistent_storage_check( return(true); } -/*********************************************************************//** -Executes a given SQL statement using the InnoDB internal SQL parser -in its own transaction and commits it. +/** Executes a given SQL statement using the InnoDB internal SQL parser. This function will free the pinfo object. +@param[in,out] pinfo pinfo to pass to que_eval_sql() must already +have any literals bound to it +@param[in] sql SQL string to execute +@param[in,out] trx in case of NULL the function will allocate and +free the trx object. If it is not NULL then it will be rolled back +only in the case of error, but not freed. @return DB_SUCCESS or error code */ static dberr_t dict_stats_exec_sql( -/*================*/ - pars_info_t* pinfo, /*!< in/out: pinfo to pass to que_eval_sql() - must already have any literals bound to it */ - const char* sql) /*!< in: SQL string to execute */ + pars_info_t* pinfo, + const char* sql, + trx_t* trx) { - trx_t* trx; dberr_t err; - + bool trx_started = false; #ifdef UNIV_SYNC_DEBUG ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX)); #endif /* UNIV_SYNC_DEBUG */ @@ -292,11 +306,24 @@ dict_stats_exec_sql( return(DB_STATS_DO_NOT_EXIST); } - trx = trx_allocate_for_background(); - trx_start_if_not_started(trx); + if (trx == NULL) { + trx = trx_allocate_for_background(); + trx_start_if_not_started(trx); + trx_started = true; + } err = que_eval_sql(pinfo, sql, FALSE, trx); /* pinfo is freed here */ + DBUG_EXECUTE_IF("stats_index_error", + if (!trx_started) { + err = DB_STATS_DO_NOT_EXIST; + trx->error_state = DB_STATS_DO_NOT_EXIST; + }); + + if (!trx_started && err == DB_SUCCESS) { + return(DB_SUCCESS); + } + if (err == DB_SUCCESS) { trx_commit_for_mysql(trx); } else { @@ -308,7 +335,9 @@ dict_stats_exec_sql( ut_a(trx->error_state == DB_SUCCESS); } - trx_free_for_background(trx); + if (trx_started) { + trx_free_for_background(trx); + } return(err); } @@ -400,6 +429,11 @@ dict_stats_table_clone_create( t->corrupted = table->corrupted; + /* This private object "t" is not shared with other threads, so + we do not need the stats_latch. The lock/unlock routines will do + nothing if stats_latch is NULL. */ + t->stats_latch = NULL; + UT_LIST_INIT(t->indexes); for (index = dict_table_get_first_index(table); @@ -731,7 +765,7 @@ static dict_table_t* dict_stats_snapshot_create( /*=======================*/ - const dict_table_t* table) /*!< in: table whose stats to copy */ + dict_table_t* table) /*!< in: table whose stats to copy */ { mutex_enter(&dict_sys->mutex); @@ -1583,7 +1617,8 @@ dict_stats_analyze_index_for_n_prefix( == !(REC_INFO_MIN_REC_FLAG & rec_get_info_bits( btr_pcur_get_rec(&pcur), page_is_comp(page)))); - last_idx_on_level = boundaries->at(n_diff_for_this_prefix - 1); + last_idx_on_level = boundaries->at( + static_cast<unsigned int>(n_diff_for_this_prefix - 1)); rec_idx = 0; @@ -1595,7 +1630,7 @@ dict_stats_analyze_index_for_n_prefix( for (i = 0; i < n_recs_to_dive_below; i++) { ib_uint64_t left; ib_uint64_t right; - ulint rnd; + ib_uint64_t rnd; ib_uint64_t dive_below_idx; /* there are n_diff_for_this_prefix elements @@ -1636,9 +1671,11 @@ dict_stats_analyze_index_for_n_prefix( /* we do not pass (left, right) because we do not want to ask ut_rnd_interval() to work with too big numbers since ib_uint64_t could be bigger than ulint */ - rnd = ut_rnd_interval(0, (ulint) (right - left)); + rnd = static_cast<ib_uint64_t>( + ut_rnd_interval(0, static_cast<ulint>(right - left))); - dive_below_idx = boundaries->at(left + rnd); + dive_below_idx = boundaries->at( + static_cast<unsigned int>(left + rnd)); #if 0 DEBUG_PRINTF(" %s(): dive below record with index=" @@ -2079,20 +2116,28 @@ dict_stats_update_persistent( } #include "mysql_com.h" -/*********************************************************************//** -Save an individual index's statistic into the persistent statistics +/** Save an individual index's statistic into the persistent statistics storage. +@param[in] index index to be updated +@param[in] last_update timestamp of the stat +@param[in] stat_name name of the stat +@param[in] stat_value value of the stat +@param[in] sample_size n pages sampled or NULL +@param[in] stat_description description of the stat +@param[in,out] trx in case of NULL the function will +allocate and free the trx object. If it is not NULL then it will be +rolled back only in the case of error, but not freed. @return DB_SUCCESS or error code */ static dberr_t dict_stats_save_index_stat( -/*=======================*/ - dict_index_t* index, /*!< in: index */ - lint last_update, /*!< in: timestamp of the stat */ - const char* stat_name, /*!< in: name of the stat */ - ib_uint64_t stat_value, /*!< in: value of the stat */ - ib_uint64_t* sample_size, /*!< in: n pages sampled or NULL */ - const char* stat_description)/*!< in: description of the stat */ + dict_index_t* index, + lint last_update, + const char* stat_name, + ib_uint64_t stat_value, + ib_uint64_t* sample_size, + const char* stat_description, + trx_t* trx) { pars_info_t* pinfo; dberr_t ret; @@ -2131,8 +2176,16 @@ dict_stats_save_index_stat( ret = dict_stats_exec_sql( pinfo, - "PROCEDURE INDEX_STATS_SAVE_INSERT () IS\n" + "PROCEDURE INDEX_STATS_SAVE () IS\n" "BEGIN\n" + + "DELETE FROM \"" INDEX_STATS_NAME "\"\n" + "WHERE\n" + "database_name = :database_name AND\n" + "table_name = :table_name AND\n" + "index_name = :index_name AND\n" + "stat_name = :stat_name;\n" + "INSERT INTO \"" INDEX_STATS_NAME "\"\n" "VALUES\n" "(\n" @@ -2145,48 +2198,7 @@ dict_stats_save_index_stat( ":sample_size,\n" ":stat_description\n" ");\n" - "END;"); - - if (ret == DB_DUPLICATE_KEY) { - - pinfo = pars_info_create(); - pars_info_add_str_literal(pinfo, "database_name", db_utf8); - pars_info_add_str_literal(pinfo, "table_name", table_utf8); - UNIV_MEM_ASSERT_RW_ABORT(index->name, strlen(index->name)); - pars_info_add_str_literal(pinfo, "index_name", index->name); - UNIV_MEM_ASSERT_RW_ABORT(&last_update, 4); - pars_info_add_int4_literal(pinfo, "last_update", last_update); - UNIV_MEM_ASSERT_RW_ABORT(stat_name, strlen(stat_name)); - pars_info_add_str_literal(pinfo, "stat_name", stat_name); - UNIV_MEM_ASSERT_RW_ABORT(&stat_value, 8); - pars_info_add_ull_literal(pinfo, "stat_value", stat_value); - if (sample_size != NULL) { - UNIV_MEM_ASSERT_RW_ABORT(sample_size, 8); - pars_info_add_ull_literal(pinfo, "sample_size", *sample_size); - } else { - pars_info_add_literal(pinfo, "sample_size", NULL, - UNIV_SQL_NULL, DATA_FIXBINARY, 0); - } - UNIV_MEM_ASSERT_RW_ABORT(stat_description, strlen(stat_description)); - pars_info_add_str_literal(pinfo, "stat_description", - stat_description); - - ret = dict_stats_exec_sql( - pinfo, - "PROCEDURE INDEX_STATS_SAVE_UPDATE () IS\n" - "BEGIN\n" - "UPDATE \"" INDEX_STATS_NAME "\" SET\n" - "last_update = :last_update,\n" - "stat_value = :stat_value,\n" - "sample_size = :sample_size,\n" - "stat_description = :stat_description\n" - "WHERE\n" - "database_name = :database_name AND\n" - "table_name = :table_name AND\n" - "index_name = :index_name AND\n" - "stat_name = :stat_name;\n" - "END;"); - } + "END;", trx); if (ret != DB_SUCCESS) { char buf_table[MAX_FULL_NAME_LEN]; @@ -2205,14 +2217,18 @@ dict_stats_save_index_stat( return(ret); } -/*********************************************************************//** -Save the table's statistics into the persistent statistics storage. +/** Save the table's statistics into the persistent statistics storage. +@param[in] table_orig table whose stats to save +@param[in] only_for_index if this is non-NULL, then stats for indexes +that are not equal to it will not be saved, if NULL, then all +indexes' stats are saved @return DB_SUCCESS or error code */ static dberr_t dict_stats_save( /*============*/ - dict_table_t* table_orig) /*!< in: table */ + dict_table_t* table_orig, + const index_id_t* only_for_index) { pars_info_t* pinfo; lint now; @@ -2234,26 +2250,27 @@ dict_stats_save( lint */ now = (lint) ut_time(); -#define PREPARE_PINFO_FOR_TABLE_SAVE(p, t, n) \ - do { \ - pars_info_add_str_literal((p), "database_name", db_utf8); \ - pars_info_add_str_literal((p), "table_name", table_utf8); \ - pars_info_add_int4_literal((p), "last_update", (n)); \ - pars_info_add_ull_literal((p), "n_rows", (t)->stat_n_rows); \ - pars_info_add_ull_literal((p), "clustered_index_size", \ - (t)->stat_clustered_index_size); \ - pars_info_add_ull_literal((p), "sum_of_other_index_sizes", \ - (t)->stat_sum_of_other_index_sizes); \ - } while(false); - pinfo = pars_info_create(); - PREPARE_PINFO_FOR_TABLE_SAVE(pinfo, table, now); + pars_info_add_str_literal(pinfo, "database_name", db_utf8); + pars_info_add_str_literal(pinfo, "table_name", table_utf8); + pars_info_add_int4_literal(pinfo, "last_update", now); + pars_info_add_ull_literal(pinfo, "n_rows", table->stat_n_rows); + pars_info_add_ull_literal(pinfo, "clustered_index_size", + table->stat_clustered_index_size); + pars_info_add_ull_literal(pinfo, "sum_of_other_index_sizes", + table->stat_sum_of_other_index_sizes); ret = dict_stats_exec_sql( pinfo, - "PROCEDURE TABLE_STATS_SAVE_INSERT () IS\n" + "PROCEDURE TABLE_STATS_SAVE () IS\n" "BEGIN\n" + + "DELETE FROM \"" TABLE_STATS_NAME "\"\n" + "WHERE\n" + "database_name = :database_name AND\n" + "table_name = :table_name;\n" + "INSERT INTO \"" TABLE_STATS_NAME "\"\n" "VALUES\n" "(\n" @@ -2264,28 +2281,7 @@ dict_stats_save( ":clustered_index_size,\n" ":sum_of_other_index_sizes\n" ");\n" - "END;"); - - if (ret == DB_DUPLICATE_KEY) { - pinfo = pars_info_create(); - - PREPARE_PINFO_FOR_TABLE_SAVE(pinfo, table, now); - - ret = dict_stats_exec_sql( - pinfo, - "PROCEDURE TABLE_STATS_SAVE_UPDATE () IS\n" - "BEGIN\n" - "UPDATE \"" TABLE_STATS_NAME "\" SET\n" - "last_update = :last_update,\n" - "n_rows = :n_rows,\n" - "clustered_index_size = :clustered_index_size,\n" - "sum_of_other_index_sizes = " - " :sum_of_other_index_sizes\n" - "WHERE\n" - "database_name = :database_name AND\n" - "table_name = :table_name;\n" - "END;"); - } + "END;", NULL); if (ret != DB_SUCCESS) { char buf[MAX_FULL_NAME_LEN]; @@ -2295,39 +2291,56 @@ dict_stats_save( "%s: %s\n", ut_format_name(table->name, TRUE, buf, sizeof(buf)), ut_strerr(ret)); - goto end; + + mutex_exit(&dict_sys->mutex); + rw_lock_x_unlock(&dict_operation_lock); + + dict_stats_snapshot_free(table); + + return(ret); } + trx_t* trx = trx_allocate_for_background(); + trx_start_if_not_started(trx); + dict_index_t* index; + index_map_t indexes; + + /* Below we do all the modifications in innodb_index_stats in a single + transaction for performance reasons. Modifying more than one row in a + single transaction may deadlock with other transactions if they + lock the rows in different order. Other transaction could be for + example when we DROP a table and do + DELETE FROM innodb_index_stats WHERE database_name = '...' + AND table_name = '...'; which will affect more than one row. To + prevent deadlocks we always lock the rows in the same order - the + order of the PK, which is (database_name, table_name, index_name, + stat_name). This is why below we sort the indexes by name and then + for each index, do the mods ordered by stat_name. */ for (index = dict_table_get_first_index(table); index != NULL; index = dict_table_get_next_index(index)) { - if (dict_stats_should_ignore_index(index)) { - continue; - } + indexes[index->name] = index; + } - ut_ad(!dict_index_is_univ(index)); + index_map_t::const_iterator it; - ret = dict_stats_save_index_stat(index, now, "size", - index->stat_index_size, - NULL, - "Number of pages " - "in the index"); - if (ret != DB_SUCCESS) { - goto end; + for (it = indexes.begin(); it != indexes.end(); ++it) { + + index = it->second; + + if (only_for_index != NULL && index->id != *only_for_index) { + continue; } - ret = dict_stats_save_index_stat(index, now, "n_leaf_pages", - index->stat_n_leaf_pages, - NULL, - "Number of leaf pages " - "in the index"); - if (ret != DB_SUCCESS) { - goto end; + if (dict_stats_should_ignore_index(index)) { + continue; } + ut_ad(!dict_index_is_univ(index)); + for (ulint i = 0; i < index->n_uniq; i++) { char stat_name[16]; @@ -2355,15 +2368,37 @@ dict_stats_save( index, now, stat_name, index->stat_n_diff_key_vals[i], &index->stat_n_sample_sizes[i], - stat_description); + stat_description, trx); if (ret != DB_SUCCESS) { goto end; } } + + ret = dict_stats_save_index_stat(index, now, "n_leaf_pages", + index->stat_n_leaf_pages, + NULL, + "Number of leaf pages " + "in the index", trx); + if (ret != DB_SUCCESS) { + goto end; + } + + ret = dict_stats_save_index_stat(index, now, "size", + index->stat_index_size, + NULL, + "Number of pages " + "in the index", trx); + if (ret != DB_SUCCESS) { + goto end; + } } + trx_commit_for_mysql(trx); + end: + trx_free_for_background(trx); + mutex_exit(&dict_sys->mutex); rw_lock_x_unlock(&dict_operation_lock); @@ -2860,7 +2895,7 @@ dict_stats_update_for_index( dict_table_stats_lock(index->table, RW_X_LATCH); dict_stats_analyze_index(index); dict_table_stats_unlock(index->table, RW_X_LATCH); - dict_stats_save(index->table); + dict_stats_save(index->table, &index->id); DBUG_VOID_RETURN; } /* else */ @@ -2955,7 +2990,7 @@ dict_stats_update( return(err); } - err = dict_stats_save(table); + err = dict_stats_save(table, NULL); return(err); } @@ -2988,7 +3023,7 @@ dict_stats_update( if (dict_stats_persistent_storage_check(false)) { - return(dict_stats_save(table)); + return(dict_stats_save(table, NULL)); } return(DB_STATS_DO_NOT_EXIST); @@ -3178,7 +3213,7 @@ dict_stats_drop_index( "database_name = :database_name AND\n" "table_name = :table_name AND\n" "index_name = :index_name;\n" - "END;\n"); + "END;\n", NULL); mutex_exit(&dict_sys->mutex); rw_lock_x_unlock(&dict_operation_lock); @@ -3246,7 +3281,7 @@ dict_stats_delete_from_table_stats( "DELETE FROM \"" TABLE_STATS_NAME "\" WHERE\n" "database_name = :database_name AND\n" "table_name = :table_name;\n" - "END;\n"); + "END;\n", NULL); return(ret); } @@ -3284,7 +3319,7 @@ dict_stats_delete_from_index_stats( "DELETE FROM \"" INDEX_STATS_NAME "\" WHERE\n" "database_name = :database_name AND\n" "table_name = :table_name;\n" - "END;\n"); + "END;\n", NULL); return(ret); } @@ -3407,7 +3442,7 @@ dict_stats_rename_in_table_stats( "WHERE\n" "database_name = :old_dbname_utf8 AND\n" "table_name = :old_tablename_utf8;\n" - "END;\n"); + "END;\n", NULL); return(ret); } @@ -3453,7 +3488,7 @@ dict_stats_rename_in_index_stats( "WHERE\n" "database_name = :old_dbname_utf8 AND\n" "table_name = :old_tablename_utf8;\n" - "END;\n"); + "END;\n", NULL); return(ret); } @@ -3834,7 +3869,7 @@ test_dict_stats_save() index2_stat_n_sample_sizes[2] = TEST_IDX2_N_DIFF3_SAMPLE_SIZE; index2_stat_n_sample_sizes[3] = TEST_IDX2_N_DIFF4_SAMPLE_SIZE; - ret = dict_stats_save(&table); + ret = dict_stats_save(&table, NULL); ut_a(ret == DB_SUCCESS); |