summaryrefslogtreecommitdiff
path: root/storage/xtradb/dict/dict0stats.cc
diff options
context:
space:
mode:
Diffstat (limited to 'storage/xtradb/dict/dict0stats.cc')
-rw-r--r--storage/xtradb/dict/dict0stats.cc4463
1 files changed, 0 insertions, 4463 deletions
diff --git a/storage/xtradb/dict/dict0stats.cc b/storage/xtradb/dict/dict0stats.cc
deleted file mode 100644
index c1463e98ce0..00000000000
--- a/storage/xtradb/dict/dict0stats.cc
+++ /dev/null
@@ -1,4463 +0,0 @@
-/*****************************************************************************
-
-Copyright (c) 2009, 2016, Oracle and/or its affiliates. All Rights Reserved.
-Copyright (c) 2015, 2017, MariaDB Corporation.
-
-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, Suite 500, Boston, MA 02110-1335 USA
-
-*****************************************************************************/
-
-/**************************************************//**
-@file dict/dict0stats.cc
-Code used for calculating and manipulating table statistics.
-
-Created Jan 06, 2010 Vasil Dimov
-*******************************************************/
-
-#ifndef UNIV_HOTBACKUP
-
-#include "univ.i"
-
-#include "btr0btr.h" /* btr_get_size() */
-#include "btr0cur.h" /* btr_estimate_number_of_different_key_vals() */
-#include "dict0dict.h" /* dict_table_get_first_index(), dict_fs2utf8() */
-#include "dict0mem.h" /* DICT_TABLE_MAGIC_N */
-#include "dict0stats.h"
-#include "data0type.h" /* dtype_t */
-#include "db0err.h" /* dberr_t */
-#include "page0page.h" /* page_align() */
-#include "pars0pars.h" /* pars_info_create() */
-#include "pars0types.h" /* pars_info_t */
-#include "que0que.h" /* que_eval_sql() */
-#include "rem0cmp.h" /* REC_MAX_N_FIELDS,cmp_rec_rec_with_match() */
-#include "row0sel.h" /* sel_node_t */
-#include "row0types.h" /* sel_node_t */
-#include "trx0trx.h" /* trx_create() */
-#include "trx0roll.h" /* trx_rollback_to_savepoint() */
-#include "ut0rnd.h" /* ut_rnd_interval() */
-#include "ut0ut.h" /* ut_format_name(), ut_time() */
-
-#include <algorithm>
-#include <map>
-#include <vector>
-
-/* Sampling algorithm description @{
-
-The algorithm is controlled by one number - N_SAMPLE_PAGES(index),
-let it be A, which is the number of leaf pages to analyze for a given index
-for each n-prefix (if the index is on 3 columns, then 3*A leaf pages will be
-analyzed).
-
-Let the total number of leaf pages in the table be T.
-Level 0 - leaf pages, level H - root.
-
-Definition: N-prefix-boring record is a record on a non-leaf page that equals
-the next (to the right, cross page boundaries, skipping the supremum and
-infimum) record on the same level when looking at the fist n-prefix columns.
-The last (user) record on a level is not boring (it does not match the
-non-existent user record to the right). We call the records boring because all
-the records on the page below a boring record are equal to that boring record.
-
-We avoid diving below boring records when searching for a leaf page to
-estimate the number of distinct records because we know that such a leaf
-page will have number of distinct records == 1.
-
-For each n-prefix: start from the root level and full scan subsequent lower
-levels until a level that contains at least A*10 distinct records is found.
-Lets call this level LA.
-As an optimization the search is canceled if it has reached level 1 (never
-descend to the level 0 (leaf)) and also if the next level to be scanned
-would contain more than A pages. The latter is because the user has asked
-to analyze A leaf pages and it does not make sense to scan much more than
-A non-leaf pages with the sole purpose of finding a good sample of A leaf
-pages.
-
-After finding the appropriate level LA with >A*10 distinct records (or less in
-the exceptions described above), divide it into groups of equal records and
-pick A such groups. Then pick the last record from each group. For example,
-let the level be:
-
-index: 0,1,2,3,4,5,6,7,8,9,10
-record: 1,1,1,2,2,7,7,7,7,7,9
-
-There are 4 groups of distinct records and if A=2 random ones are selected,
-e.g. 1,1,1 and 7,7,7,7,7, then records with indexes 2 and 9 will be selected.
-
-After selecting A records as described above, dive below them to find A leaf
-pages and analyze them, finding the total number of distinct records. The
-dive to the leaf level is performed by selecting a non-boring record from
-each page and diving below it.
-
-This way, a total of A leaf pages are analyzed for the given n-prefix.
-
-Let the number of different key values found in each leaf page i be Pi (i=1..A).
-Let N_DIFF_AVG_LEAF be (P1 + P2 + ... + PA) / A.
-Let the number of different key values on level LA be N_DIFF_LA.
-Let the total number of records on level LA be TOTAL_LA.
-Let R be N_DIFF_LA / TOTAL_LA, we assume this ratio is the same on the
-leaf level.
-Let the number of leaf pages be N.
-Then the total number of different key values on the leaf level is:
-N * R * N_DIFF_AVG_LEAF.
-See REF01 for the implementation.
-
-The above describes how to calculate the cardinality of an index.
-This algorithm is executed for each n-prefix of a multi-column index
-where n=1..n_uniq.
-@} */
-
-/* names of the tables from the persistent statistics storage */
-#define TABLE_STATS_NAME "mysql/innodb_table_stats"
-#define TABLE_STATS_NAME_PRINT "mysql.innodb_table_stats"
-#define INDEX_STATS_NAME "mysql/innodb_index_stats"
-#define INDEX_STATS_NAME_PRINT "mysql.innodb_index_stats"
-
-#ifdef UNIV_STATS_DEBUG
-#define DEBUG_PRINTF(fmt, ...) printf(fmt, ## __VA_ARGS__)
-#else /* UNIV_STATS_DEBUG */
-#define DEBUG_PRINTF(fmt, ...) /* noop */
-#endif /* UNIV_STATS_DEBUG */
-
-/* Gets the number of leaf pages to sample in persistent stats estimation */
-#define N_SAMPLE_PAGES(index) \
- static_cast<ib_uint64_t>( \
- (index)->table->stats_sample_pages != 0 \
- ? (index)->table->stats_sample_pages \
- : srv_stats_persistent_sample_pages)
-
-/* number of distinct records on a given level that are required to stop
-descending to lower levels and fetch N_SAMPLE_PAGES(index) records
-from that level */
-#define N_DIFF_REQUIRED(index) (N_SAMPLE_PAGES(index) * 10)
-
-/* A dynamic array where we store the boundaries of each distinct group
-of keys. For example if a btree level is:
-index: 0,1,2,3,4,5,6,7,8,9,10,11,12
-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
-* stats recalc
-* stats save
-@return true if exists and all tables are ok */
-UNIV_INLINE
-bool
-dict_stats_should_ignore_index(
-/*===========================*/
- const dict_index_t* index) /*!< in: index */
-{
- return((index->type & DICT_FTS)
- || dict_index_is_corrupted(index)
- || index->to_be_dropped
- || *index->name == TEMP_INDEX_PREFIX);
-}
-
-/*********************************************************************//**
-Checks whether the persistent statistics storage exists and that all
-tables have the proper structure.
-@return true if exists and all tables are ok */
-static
-bool
-dict_stats_persistent_storage_check(
-/*================================*/
- bool caller_has_dict_sys_mutex) /*!< in: true if the caller
- owns dict_sys->mutex */
-{
- /* definition for the table TABLE_STATS_NAME */
- dict_col_meta_t table_stats_columns[] = {
- {"database_name", DATA_VARMYSQL,
- DATA_NOT_NULL, 192},
-
- {"table_name", DATA_VARMYSQL,
- DATA_NOT_NULL, 192},
-
- {"last_update", DATA_FIXBINARY,
- DATA_NOT_NULL, 4},
-
- {"n_rows", DATA_INT,
- DATA_NOT_NULL | DATA_UNSIGNED, 8},
-
- {"clustered_index_size", DATA_INT,
- DATA_NOT_NULL | DATA_UNSIGNED, 8},
-
- {"sum_of_other_index_sizes", DATA_INT,
- DATA_NOT_NULL | DATA_UNSIGNED, 8}
- };
- dict_table_schema_t table_stats_schema = {
- TABLE_STATS_NAME,
- UT_ARR_SIZE(table_stats_columns),
- table_stats_columns,
- 0 /* n_foreign */,
- 0 /* n_referenced */
- };
-
- /* definition for the table INDEX_STATS_NAME */
- dict_col_meta_t index_stats_columns[] = {
- {"database_name", DATA_VARMYSQL,
- DATA_NOT_NULL, 192},
-
- {"table_name", DATA_VARMYSQL,
- DATA_NOT_NULL, 192},
-
- {"index_name", DATA_VARMYSQL,
- DATA_NOT_NULL, 192},
-
- {"last_update", DATA_FIXBINARY,
- DATA_NOT_NULL, 4},
-
- {"stat_name", DATA_VARMYSQL,
- DATA_NOT_NULL, 64*3},
-
- {"stat_value", DATA_INT,
- DATA_NOT_NULL | DATA_UNSIGNED, 8},
-
- {"sample_size", DATA_INT,
- DATA_UNSIGNED, 8},
-
- {"stat_description", DATA_VARMYSQL,
- DATA_NOT_NULL, 1024*3}
- };
- dict_table_schema_t index_stats_schema = {
- INDEX_STATS_NAME,
- UT_ARR_SIZE(index_stats_columns),
- index_stats_columns,
- 0 /* n_foreign */,
- 0 /* n_referenced */
- };
-
- char errstr[512];
- dberr_t ret;
-
- if (!caller_has_dict_sys_mutex) {
- mutex_enter(&(dict_sys->mutex));
- }
-
- ut_ad(mutex_own(&dict_sys->mutex));
-
- /* first check table_stats */
- ret = dict_table_schema_check(&table_stats_schema, errstr,
- sizeof(errstr));
- if (ret == DB_SUCCESS) {
- /* if it is ok, then check index_stats */
- ret = dict_table_schema_check(&index_stats_schema, errstr,
- sizeof(errstr));
- }
-
- if (!caller_has_dict_sys_mutex) {
- mutex_exit(&(dict_sys->mutex));
- }
-
- if (ret != DB_SUCCESS && ret != DB_STATS_DO_NOT_EXIST) {
- ut_print_timestamp(stderr);
- fprintf(stderr, " InnoDB: Error: %s\n", errstr);
- return(false);
- } else if (ret == DB_STATS_DO_NOT_EXIST) {
- return false;
- }
- /* else */
-
- return(true);
-}
-
-/** 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,
- const char* sql,
- 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 */
- ut_ad(mutex_own(&dict_sys->mutex));
-
- if (!dict_stats_persistent_storage_check(true)) {
- pars_info_free(pinfo);
- return(DB_STATS_DO_NOT_EXIST);
- }
-
- 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 {
- trx->op_info = "rollback of internal trx on stats tables";
- trx->dict_operation_lock_mode = RW_X_LATCH;
- trx_rollback_to_savepoint(trx, NULL);
- trx->dict_operation_lock_mode = 0;
- trx->op_info = "";
- ut_a(trx->error_state == DB_SUCCESS);
- }
-
- if (trx_started) {
- trx_free_for_background(trx);
- }
-
- return(err);
-}
-
-/*********************************************************************//**
-Duplicate a table object and its indexes.
-This function creates a dummy dict_table_t object and initializes the
-following table and index members:
-dict_table_t::id (copied)
-dict_table_t::heap (newly created)
-dict_table_t::name (copied)
-dict_table_t::corrupted (copied)
-dict_table_t::indexes<> (newly created)
-dict_table_t::magic_n
-for each entry in dict_table_t::indexes, the following are initialized:
-(indexes that have DICT_FTS set in index->type are skipped)
-dict_index_t::id (copied)
-dict_index_t::name (copied)
-dict_index_t::table_name (points to the copied table name)
-dict_index_t::table (points to the above semi-initialized object)
-dict_index_t::type (copied)
-dict_index_t::to_be_dropped (copied)
-dict_index_t::online_status (copied)
-dict_index_t::n_uniq (copied)
-dict_index_t::fields[] (newly created, only first n_uniq, only fields[i].name)
-dict_index_t::indexes<> (newly created)
-dict_index_t::stat_n_diff_key_vals[] (only allocated, left uninitialized)
-dict_index_t::stat_n_sample_sizes[] (only allocated, left uninitialized)
-dict_index_t::stat_n_non_null_key_vals[] (only allocated, left uninitialized)
-dict_index_t::magic_n
-The returned object should be freed with dict_stats_table_clone_free()
-when no longer needed.
-@return incomplete table object */
-static
-dict_table_t*
-dict_stats_table_clone_create(
-/*==========================*/
- const dict_table_t* table) /*!< in: table whose stats to copy */
-{
- size_t heap_size;
- dict_index_t* index;
-
- /* Estimate the size needed for the table and all of its indexes */
-
- heap_size = 0;
- heap_size += sizeof(dict_table_t);
- heap_size += strlen(table->name) + 1;
-
- 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;
- }
-
- ut_ad(!dict_index_is_univ(index));
-
- ulint n_uniq = dict_index_get_n_unique(index);
-
- heap_size += sizeof(dict_index_t);
- heap_size += strlen(index->name) + 1;
- heap_size += n_uniq * sizeof(index->fields[0]);
- for (ulint i = 0; i < n_uniq; i++) {
- heap_size += strlen(index->fields[i].name) + 1;
- }
- heap_size += n_uniq * sizeof(index->stat_n_diff_key_vals[0]);
- heap_size += n_uniq * sizeof(index->stat_n_sample_sizes[0]);
- heap_size += n_uniq * sizeof(index->stat_n_non_null_key_vals[0]);
- }
-
- /* Allocate the memory and copy the members */
-
- mem_heap_t* heap;
-
- heap = mem_heap_create(heap_size);
-
- dict_table_t* t;
-
- t = (dict_table_t*) mem_heap_alloc(heap, sizeof(*t));
-
- UNIV_MEM_ASSERT_RW_ABORT(&table->id, sizeof(table->id));
- t->id = table->id;
-
- t->heap = heap;
-
- UNIV_MEM_ASSERT_RW_ABORT(table->name, strlen(table->name) + 1);
- t->name = (char*) mem_heap_strdup(heap, table->name);
-
- t->corrupted = table->corrupted;
-
- /* This private object "t" is not shared with other threads, so
- we do not need the stats_latch (thus we pass false below). The
- dict_table_stats_lock()/unlock() routines will do nothing. */
- dict_table_stats_latch_create(t, false);
-
- UT_LIST_INIT(t->indexes);
-
- 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;
- }
-
- ut_ad(!dict_index_is_univ(index));
-
- dict_index_t* idx;
-
- idx = (dict_index_t*) mem_heap_alloc(heap, sizeof(*idx));
-
- UNIV_MEM_ASSERT_RW_ABORT(&index->id, sizeof(index->id));
- idx->id = index->id;
-
- UNIV_MEM_ASSERT_RW_ABORT(index->name, strlen(index->name) + 1);
- idx->name = (char*) mem_heap_strdup(heap, index->name);
-
- idx->table_name = t->name;
-
- idx->table = t;
-
- idx->type = index->type;
-
- idx->to_be_dropped = 0;
-
- idx->online_status = ONLINE_INDEX_COMPLETE;
-
- idx->n_uniq = index->n_uniq;
-
- idx->fields = (dict_field_t*) mem_heap_alloc(
- heap, idx->n_uniq * sizeof(idx->fields[0]));
-
- for (ulint i = 0; i < idx->n_uniq; i++) {
- UNIV_MEM_ASSERT_RW_ABORT(index->fields[i].name, strlen(index->fields[i].name) + 1);
- idx->fields[i].name = (char*) mem_heap_strdup(
- heap, index->fields[i].name);
- }
-
- /* hook idx into t->indexes */
- UT_LIST_ADD_LAST(indexes, t->indexes, idx);
-
- idx->stat_n_diff_key_vals = (ib_uint64_t*) mem_heap_alloc(
- heap,
- idx->n_uniq * sizeof(idx->stat_n_diff_key_vals[0]));
-
- idx->stat_n_sample_sizes = (ib_uint64_t*) mem_heap_alloc(
- heap,
- idx->n_uniq * sizeof(idx->stat_n_sample_sizes[0]));
-
- idx->stat_n_non_null_key_vals = (ib_uint64_t*) mem_heap_alloc(
- heap,
- idx->n_uniq * sizeof(idx->stat_n_non_null_key_vals[0]));
- ut_d(idx->magic_n = DICT_INDEX_MAGIC_N);
-
- idx->stat_defrag_n_page_split = 0;
- idx->stat_defrag_n_pages_freed = 0;
- }
-
- ut_d(t->magic_n = DICT_TABLE_MAGIC_N);
-
- return(t);
-}
-
-/*********************************************************************//**
-Free the resources occupied by an object returned by
-dict_stats_table_clone_create(). */
-static
-void
-dict_stats_table_clone_free(
-/*========================*/
- dict_table_t* t) /*!< in: dummy table object to free */
-{
- dict_table_stats_latch_destroy(t);
- mem_heap_free(t->heap);
-}
-
-/*********************************************************************//**
-Write all zeros (or 1 where it makes sense) into an index
-statistics members. The resulting stats correspond to an empty index.
-The caller must own index's table stats latch in X mode
-(dict_table_stats_lock(table, RW_X_LATCH)) */
-static
-void
-dict_stats_empty_index(
-/*===================*/
- dict_index_t* index, /*!< in/out: index */
- bool empty_defrag_stats)
- /*!< in: whether to empty defrag stats */
-{
- ut_ad(!(index->type & DICT_FTS));
- ut_ad(!dict_index_is_univ(index));
-
- ulint n_uniq = index->n_uniq;
-
- for (ulint i = 0; i < n_uniq; i++) {
- index->stat_n_diff_key_vals[i] = 0;
- index->stat_n_sample_sizes[i] = 1;
- index->stat_n_non_null_key_vals[i] = 0;
- }
-
- index->stat_index_size = 1;
- index->stat_n_leaf_pages = 1;
-
- if (empty_defrag_stats) {
- dict_stats_empty_defrag_stats(index);
- dict_stats_empty_defrag_summary(index);
- }
-}
-
-/**********************************************************************//**
-Clear defragmentation summary. */
-UNIV_INTERN
-void
-dict_stats_empty_defrag_summary(
-/*==================*/
- dict_index_t* index) /*!< in: index to clear defragmentation stats */
-{
- index->stat_defrag_n_pages_freed = 0;
-}
-
-/**********************************************************************//**
-Clear defragmentation related index stats. */
-UNIV_INTERN
-void
-dict_stats_empty_defrag_stats(
-/*==================*/
- dict_index_t* index) /*!< in: index to clear defragmentation stats */
-{
- index->stat_defrag_modified_counter = 0;
- index->stat_defrag_n_page_split = 0;
-}
-
-/*********************************************************************//**
-Write all zeros (or 1 where it makes sense) into a table and its indexes'
-statistics members. The resulting stats correspond to an empty table. */
-static
-void
-dict_stats_empty_table(
-/*===================*/
- dict_table_t* table, /*!< in/out: table */
- bool empty_defrag_stats)
- /*!< in: whether to empty defrag stats */
-{
- /* Zero the stats members */
-
- dict_table_stats_lock(table, RW_X_LATCH);
-
- table->stat_n_rows = 0;
- table->stat_clustered_index_size = 1;
- /* 1 page for each index, not counting the clustered */
- table->stat_sum_of_other_index_sizes
- = UT_LIST_GET_LEN(table->indexes) - 1;
- table->stat_modified_counter = 0;
-
- dict_index_t* index;
-
- for (index = dict_table_get_first_index(table);
- index != NULL;
- index = dict_table_get_next_index(index)) {
-
- if (index->type & DICT_FTS) {
- continue;
- }
-
- ut_ad(!dict_index_is_univ(index));
-
- dict_stats_empty_index(index, empty_defrag_stats);
- }
-
- table->stat_initialized = TRUE;
-
- dict_table_stats_unlock(table, RW_X_LATCH);
-}
-
-/*********************************************************************//**
-Check whether index's stats are initialized (assert if they are not). */
-static
-void
-dict_stats_assert_initialized_index(
-/*================================*/
- const dict_index_t* index) /*!< in: index */
-{
- UNIV_MEM_ASSERT_RW_ABORT(
- index->stat_n_diff_key_vals,
- index->n_uniq * sizeof(index->stat_n_diff_key_vals[0]));
-
- UNIV_MEM_ASSERT_RW_ABORT(
- index->stat_n_sample_sizes,
- index->n_uniq * sizeof(index->stat_n_sample_sizes[0]));
-
- UNIV_MEM_ASSERT_RW_ABORT(
- index->stat_n_non_null_key_vals,
- index->n_uniq * sizeof(index->stat_n_non_null_key_vals[0]));
-
- UNIV_MEM_ASSERT_RW_ABORT(
- &index->stat_index_size,
- sizeof(index->stat_index_size));
-
- UNIV_MEM_ASSERT_RW_ABORT(
- &index->stat_n_leaf_pages,
- sizeof(index->stat_n_leaf_pages));
-}
-
-/*********************************************************************//**
-Check whether table's stats are initialized (assert if they are not). */
-static
-void
-dict_stats_assert_initialized(
-/*==========================*/
- const dict_table_t* table) /*!< in: table */
-{
- ut_a(table->stat_initialized);
-
- UNIV_MEM_ASSERT_RW_ABORT(&table->stats_last_recalc,
- sizeof(table->stats_last_recalc));
-
- UNIV_MEM_ASSERT_RW_ABORT(&table->stat_persistent,
- sizeof(table->stat_persistent));
-
- UNIV_MEM_ASSERT_RW_ABORT(&table->stats_auto_recalc,
- sizeof(table->stats_auto_recalc));
-
- UNIV_MEM_ASSERT_RW_ABORT(&table->stats_sample_pages,
- sizeof(table->stats_sample_pages));
-
- UNIV_MEM_ASSERT_RW_ABORT(&table->stat_n_rows,
- sizeof(table->stat_n_rows));
-
- UNIV_MEM_ASSERT_RW_ABORT(&table->stat_clustered_index_size,
- sizeof(table->stat_clustered_index_size));
-
- UNIV_MEM_ASSERT_RW_ABORT(&table->stat_sum_of_other_index_sizes,
- sizeof(table->stat_sum_of_other_index_sizes));
-
- UNIV_MEM_ASSERT_RW_ABORT(&table->stat_modified_counter,
- sizeof(table->stat_modified_counter));
-
- UNIV_MEM_ASSERT_RW_ABORT(&table->stats_bg_flag,
- sizeof(table->stats_bg_flag));
-
- for (dict_index_t* index = dict_table_get_first_index(table);
- index != NULL;
- index = dict_table_get_next_index(index)) {
-
- if (!dict_stats_should_ignore_index(index)) {
- dict_stats_assert_initialized_index(index);
- }
- }
-}
-
-#define INDEX_EQ(i1, i2) \
- ((i1) != NULL \
- && (i2) != NULL \
- && (i1)->id == (i2)->id \
- && strcmp((i1)->name, (i2)->name) == 0)
-
-/*********************************************************************//**
-Copy table and index statistics from one table to another, including index
-stats. Extra indexes in src are ignored and extra indexes in dst are
-initialized to correspond to an empty index. */
-static
-void
-dict_stats_copy(
-/*============*/
- dict_table_t* dst, /*!< in/out: destination table */
- const dict_table_t* src, /*!< in: source table */
- bool reset_ignored_indexes) /*!< in: if true, set ignored indexes
- to have the same statistics as if
- the table was empty */
-{
- dst->stats_last_recalc = src->stats_last_recalc;
- dst->stat_n_rows = src->stat_n_rows;
- dst->stat_clustered_index_size = src->stat_clustered_index_size;
- dst->stat_sum_of_other_index_sizes = src->stat_sum_of_other_index_sizes;
- dst->stat_modified_counter = src->stat_modified_counter;
-
- dict_index_t* dst_idx;
- dict_index_t* src_idx;
-
- for (dst_idx = dict_table_get_first_index(dst),
- src_idx = dict_table_get_first_index(src);
- dst_idx != NULL;
- dst_idx = dict_table_get_next_index(dst_idx),
- (src_idx != NULL
- && (src_idx = dict_table_get_next_index(src_idx)))) {
-
- if (dict_stats_should_ignore_index(dst_idx)) {
- if (reset_ignored_indexes) {
- /* Reset index statistics for all ignored indexes,
- unless they are FT indexes (these have no statistics)*/
- if (dst_idx->type & DICT_FTS) {
- continue;
- }
- dict_stats_empty_index(dst_idx, true);
- } else {
- continue;
- }
- }
-
- ut_ad(!dict_index_is_univ(dst_idx));
-
- if (!INDEX_EQ(src_idx, dst_idx)) {
- for (src_idx = dict_table_get_first_index(src);
- src_idx != NULL;
- src_idx = dict_table_get_next_index(src_idx)) {
-
- if (INDEX_EQ(src_idx, dst_idx)) {
- break;
- }
- }
- }
-
- if (!INDEX_EQ(src_idx, dst_idx)) {
- dict_stats_empty_index(dst_idx, true);
- continue;
- }
-
- ulint n_copy_el;
-
- if (dst_idx->n_uniq > src_idx->n_uniq) {
- n_copy_el = src_idx->n_uniq;
- /* Since src is smaller some elements in dst
- will remain untouched by the following memmove(),
- thus we init all of them here. */
- dict_stats_empty_index(dst_idx, true);
- } else {
- n_copy_el = dst_idx->n_uniq;
- }
-
- memmove(dst_idx->stat_n_diff_key_vals,
- src_idx->stat_n_diff_key_vals,
- n_copy_el * sizeof(dst_idx->stat_n_diff_key_vals[0]));
-
- memmove(dst_idx->stat_n_sample_sizes,
- src_idx->stat_n_sample_sizes,
- n_copy_el * sizeof(dst_idx->stat_n_sample_sizes[0]));
-
- memmove(dst_idx->stat_n_non_null_key_vals,
- src_idx->stat_n_non_null_key_vals,
- n_copy_el * sizeof(dst_idx->stat_n_non_null_key_vals[0]));
-
- dst_idx->stat_index_size = src_idx->stat_index_size;
-
- dst_idx->stat_n_leaf_pages = src_idx->stat_n_leaf_pages;
-
- dst_idx->stat_defrag_modified_counter =
- src_idx->stat_defrag_modified_counter;
- dst_idx->stat_defrag_n_pages_freed =
- src_idx->stat_defrag_n_pages_freed;
- dst_idx->stat_defrag_n_page_split =
- src_idx->stat_defrag_n_page_split;
- }
-
- dst->stat_initialized = TRUE;
-}
-
-/*********************************************************************//**
-Duplicate the stats of a table and its indexes.
-This function creates a dummy dict_table_t object and copies the input
-table's stats into it. The returned table object is not in the dictionary
-cache and cannot be accessed by any other threads. In addition to the
-members copied in dict_stats_table_clone_create() this function initializes
-the following:
-dict_table_t::stat_initialized
-dict_table_t::stat_persistent
-dict_table_t::stat_n_rows
-dict_table_t::stat_clustered_index_size
-dict_table_t::stat_sum_of_other_index_sizes
-dict_table_t::stat_modified_counter
-dict_index_t::stat_n_diff_key_vals[]
-dict_index_t::stat_n_sample_sizes[]
-dict_index_t::stat_n_non_null_key_vals[]
-dict_index_t::stat_index_size
-dict_index_t::stat_n_leaf_pages
-dict_index_t::stat_defrag_modified_counter
-dict_index_t::stat_defrag_n_pages_freed
-dict_index_t::stat_defrag_n_page_split
-The returned object should be freed with dict_stats_snapshot_free()
-when no longer needed.
-@return incomplete table object */
-static
-dict_table_t*
-dict_stats_snapshot_create(
-/*=======================*/
- dict_table_t* table) /*!< in: table whose stats to copy */
-{
- mutex_enter(&dict_sys->mutex);
-
- dict_table_stats_lock(table, RW_S_LATCH);
-
- dict_stats_assert_initialized(table);
-
- dict_table_t* t;
-
- t = dict_stats_table_clone_create(table);
-
- dict_stats_copy(t, table, false);
-
- t->stat_persistent = table->stat_persistent;
- t->stats_auto_recalc = table->stats_auto_recalc;
- t->stats_sample_pages = table->stats_sample_pages;
- t->stats_bg_flag = table->stats_bg_flag;
-
- dict_table_stats_unlock(table, RW_S_LATCH);
-
- mutex_exit(&dict_sys->mutex);
-
- return(t);
-}
-
-/*********************************************************************//**
-Free the resources occupied by an object returned by
-dict_stats_snapshot_create(). */
-static
-void
-dict_stats_snapshot_free(
-/*=====================*/
- dict_table_t* t) /*!< in: dummy table object to free */
-{
- dict_stats_table_clone_free(t);
-}
-
-/*********************************************************************//**
-Calculates new estimates for index statistics. This function is
-relatively quick and is used to calculate transient statistics that
-are not saved on disk. This was the only way to calculate statistics
-before the Persistent Statistics feature was introduced.
-This function doesn't update the defragmentation related stats.
-Only persistent statistics supports defragmentation stats. */
-static
-void
-dict_stats_update_transient_for_index(
-/*==================================*/
- dict_index_t* index) /*!< in/out: index */
-{
- if (srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO
- && (srv_force_recovery >= SRV_FORCE_NO_LOG_REDO
- || !dict_index_is_clust(index))) {
- /* If we have set a high innodb_force_recovery
- level, do not calculate statistics, as a badly
- corrupted index can cause a crash in it.
- Initialize some bogus index cardinality
- statistics, so that the data can be queried in
- various means, also via secondary indexes. */
- dict_stats_empty_index(index, false);
-#if defined UNIV_DEBUG || defined UNIV_IBUF_DEBUG
- } else if (ibuf_debug && !dict_index_is_clust(index)) {
- dict_stats_empty_index(index, false);
-#endif /* UNIV_DEBUG || UNIV_IBUF_DEBUG */
- } else {
- mtr_t mtr;
- ulint size;
- mtr_start(&mtr);
- mtr_s_lock(dict_index_get_lock(index), &mtr);
-
- size = btr_get_size(index, BTR_TOTAL_SIZE, &mtr);
-
- if (size != ULINT_UNDEFINED) {
- index->stat_index_size = size;
-
- size = btr_get_size(
- index, BTR_N_LEAF_PAGES, &mtr);
- }
-
- mtr_commit(&mtr);
-
- switch (size) {
- case ULINT_UNDEFINED:
- dict_stats_empty_index(index, false);
- return;
- case 0:
- /* The root node of the tree is a leaf */
- size = 1;
- }
-
- index->stat_n_leaf_pages = size;
-
- /* Do not continue if table decryption has failed or
- table is already marked as corrupted. */
- if (index->is_readable()) {
- btr_estimate_number_of_different_key_vals(index);
- }
- }
-}
-
-/*********************************************************************//**
-Calculates new estimates for table and index statistics. This function
-is relatively quick and is used to calculate transient statistics that
-are not saved on disk.
-This was the only way to calculate statistics before the
-Persistent Statistics feature was introduced. */
-UNIV_INTERN
-void
-dict_stats_update_transient(
-/*========================*/
- dict_table_t* table) /*!< in/out: table */
-{
- dict_index_t* index;
- ulint sum_of_index_sizes = 0;
-
- /* Find out the sizes of the indexes and how many different values
- for the key they approximately have */
-
- index = dict_table_get_first_index(table);
-
- if (dict_table_is_discarded(table)) {
- /* Nothing to do. */
- dict_stats_empty_table(table, false);
- return;
- } else if (index == NULL) {
- /* Table definition is corrupt */
-
- char buf[MAX_FULL_NAME_LEN];
- ut_print_timestamp(stderr);
- fprintf(stderr, " InnoDB: table %s has no indexes. "
- "Cannot calculate statistics.\n",
- ut_format_name(table->name, TRUE, buf, sizeof(buf)));
- dict_stats_empty_table(table, false);
- return;
- }
-
- for (; index != NULL; index = dict_table_get_next_index(index)) {
-
- ut_ad(!dict_index_is_univ(index));
-
- if (index->type & DICT_FTS) {
- continue;
- }
-
- dict_stats_empty_index(index, false);
-
- if (dict_stats_should_ignore_index(index)) {
- continue;
- }
-
- /* Do not continue if table decryption has failed or
- table is already marked as corrupted. */
- if (!index->is_readable()) {
- break;
- }
-
- dict_stats_update_transient_for_index(index);
-
- sum_of_index_sizes += index->stat_index_size;
- }
-
- index = dict_table_get_first_index(table);
-
- table->stat_n_rows = index->stat_n_diff_key_vals[
- dict_index_get_n_unique(index) - 1];
-
- table->stat_clustered_index_size = index->stat_index_size;
-
- table->stat_sum_of_other_index_sizes = sum_of_index_sizes
- - index->stat_index_size;
-
- table->stats_last_recalc = ut_time();
-
- table->stat_modified_counter = 0;
-
- table->stat_initialized = TRUE;
-}
-
-/* @{ Pseudo code about the relation between the following functions
-
-let N = N_SAMPLE_PAGES(index)
-
-dict_stats_analyze_index()
- for each n_prefix
- search for good enough level:
- dict_stats_analyze_index_level() // only called if level has <= N pages
- // full scan of the level in one mtr
- collect statistics about the given level
- if we are not satisfied with the level, search next lower level
- we have found a good enough level here
- dict_stats_analyze_index_for_n_prefix(that level, stats collected above)
- // full scan of the level in one mtr
- dive below some records and analyze the leaf page there:
- dict_stats_analyze_index_below_cur()
-@} */
-
-/*********************************************************************//**
-Find the total number and the number of distinct keys on a given level in
-an index. Each of the 1..n_uniq prefixes are looked up and the results are
-saved in the array n_diff[0] .. n_diff[n_uniq - 1]. The total number of
-records on the level is saved in total_recs.
-Also, the index of the last record in each group of equal records is saved
-in n_diff_boundaries[0..n_uniq - 1], records indexing starts from the leftmost
-record on the level and continues cross pages boundaries, counting from 0. */
-static
-void
-dict_stats_analyze_index_level(
-/*===========================*/
- dict_index_t* index, /*!< in: index */
- ulint level, /*!< in: level */
- ib_uint64_t* n_diff, /*!< out: array for number of
- distinct keys for all prefixes */
- ib_uint64_t* total_recs, /*!< out: total number of records */
- ib_uint64_t* total_pages, /*!< out: total number of pages */
- boundaries_t* n_diff_boundaries,/*!< out: boundaries of the groups
- of distinct keys */
- mtr_t* mtr) /*!< in/out: mini-transaction */
-{
- ulint n_uniq;
- mem_heap_t* heap;
- btr_pcur_t pcur;
- const page_t* page;
- const rec_t* rec;
- const rec_t* prev_rec;
- bool prev_rec_is_copied;
- byte* prev_rec_buf = NULL;
- ulint prev_rec_buf_size = 0;
- ulint* rec_offsets;
- ulint* prev_rec_offsets;
- ulint i;
-
- DEBUG_PRINTF(" %s(table=%s, index=%s, level=%lu)\n", __func__,
- index->table->name, index->name, level);
-
- ut_ad(mtr_memo_contains(mtr, dict_index_get_lock(index),
- MTR_MEMO_S_LOCK));
-
- n_uniq = dict_index_get_n_unique(index);
-
- /* elements in the n_diff array are 0..n_uniq-1 (inclusive) */
- memset(n_diff, 0x0, n_uniq * sizeof(n_diff[0]));
-
- /* Allocate space for the offsets header (the allocation size at
- offsets[0] and the REC_OFFS_HEADER_SIZE bytes), and n_fields + 1,
- so that this will never be less than the size calculated in
- rec_get_offsets_func(). */
- i = (REC_OFFS_HEADER_SIZE + 1 + 1) + index->n_fields;
-
- heap = mem_heap_create((2 * sizeof *rec_offsets) * i);
- rec_offsets = static_cast<ulint*>(
- mem_heap_alloc(heap, i * sizeof *rec_offsets));
- prev_rec_offsets = static_cast<ulint*>(
- mem_heap_alloc(heap, i * sizeof *prev_rec_offsets));
- rec_offs_set_n_alloc(rec_offsets, i);
- rec_offs_set_n_alloc(prev_rec_offsets, i);
-
- /* reset the dynamic arrays n_diff_boundaries[0..n_uniq-1] */
- if (n_diff_boundaries != NULL) {
- for (i = 0; i < n_uniq; i++) {
- n_diff_boundaries[i].erase(
- n_diff_boundaries[i].begin(),
- n_diff_boundaries[i].end());
- }
- }
-
- /* Position pcur on the leftmost record on the leftmost page
- on the desired level. */
-
- btr_pcur_open_at_index_side(
- true, index, BTR_SEARCH_LEAF | BTR_ALREADY_S_LATCHED,
- &pcur, true, level, mtr);
- btr_pcur_move_to_next_on_page(&pcur);
-
- page = btr_pcur_get_page(&pcur);
-
- /* The page must not be empty, except when
- it is the root page (and the whole index is empty). */
- ut_ad(btr_pcur_is_on_user_rec(&pcur) || page_is_leaf(page));
- ut_ad(btr_pcur_get_rec(&pcur)
- == page_rec_get_next_const(page_get_infimum_rec(page)));
-
- /* check that we are indeed on the desired level */
- ut_a(btr_page_get_level(page, mtr) == level);
-
- /* there should not be any pages on the left */
- ut_a(btr_page_get_prev(page, mtr) == FIL_NULL);
-
- /* check whether the first record on the leftmost page is marked
- as such, if we are on a non-leaf level */
- ut_a((level == 0)
- == !(REC_INFO_MIN_REC_FLAG & rec_get_info_bits(
- btr_pcur_get_rec(&pcur), page_is_comp(page))));
-
- prev_rec = NULL;
- prev_rec_is_copied = false;
-
- /* no records by default */
- *total_recs = 0;
-
- *total_pages = 0;
-
- /* iterate over all user records on this level
- and compare each two adjacent ones, even the last on page
- X and the fist on page X+1 */
- for (;
- btr_pcur_is_on_user_rec(&pcur);
- btr_pcur_move_to_next_user_rec(&pcur, mtr)) {
-
- ulint matched_fields = 0;
- ulint matched_bytes = 0;
- bool rec_is_last_on_page;
-
- rec = btr_pcur_get_rec(&pcur);
-
- /* If rec and prev_rec are on different pages, then prev_rec
- must have been copied, because we hold latch only on the page
- where rec resides. */
- if (prev_rec != NULL
- && page_align(rec) != page_align(prev_rec)) {
-
- ut_a(prev_rec_is_copied);
- }
-
- rec_is_last_on_page =
- page_rec_is_supremum(page_rec_get_next_const(rec));
-
- /* increment the pages counter at the end of each page */
- if (rec_is_last_on_page) {
-
- (*total_pages)++;
- }
-
- /* Skip delete-marked records on the leaf level. If we
- do not skip them, then ANALYZE quickly after DELETE
- could count them or not (purge may have already wiped
- them away) which brings non-determinism. We skip only
- leaf-level delete marks because delete marks on
- non-leaf level do not make sense. */
-
- if (level == 0
- && !srv_stats_include_delete_marked
- && rec_get_deleted_flag(
- rec,
- page_is_comp(btr_pcur_get_page(&pcur)))) {
-
- if (rec_is_last_on_page
- && !prev_rec_is_copied
- && prev_rec != NULL) {
- /* copy prev_rec */
-
- prev_rec_offsets = rec_get_offsets(
- prev_rec, index, prev_rec_offsets,
- n_uniq, &heap);
-
- prev_rec = rec_copy_prefix_to_buf(
- prev_rec, index,
- rec_offs_n_fields(prev_rec_offsets),
- &prev_rec_buf, &prev_rec_buf_size);
-
- prev_rec_is_copied = true;
- }
-
- continue;
- }
-
- rec_offsets = rec_get_offsets(
- rec, index, rec_offsets, n_uniq, &heap);
-
- (*total_recs)++;
-
- if (prev_rec != NULL) {
- prev_rec_offsets = rec_get_offsets(
- prev_rec, index, prev_rec_offsets,
- n_uniq, &heap);
-
- cmp_rec_rec_with_match(rec,
- prev_rec,
- rec_offsets,
- prev_rec_offsets,
- index,
- FALSE,
- &matched_fields,
- &matched_bytes);
-
- for (i = matched_fields; i < n_uniq; i++) {
-
- if (n_diff_boundaries != NULL) {
- /* push the index of the previous
- record, that is - the last one from
- a group of equal keys */
-
- ib_uint64_t idx;
-
- /* the index of the current record
- is total_recs - 1, the index of the
- previous record is total_recs - 2;
- we know that idx is not going to
- become negative here because if we
- are in this branch then there is a
- previous record and thus
- total_recs >= 2 */
- idx = *total_recs - 2;
-
- n_diff_boundaries[i].push_back(idx);
- }
-
- /* increment the number of different keys
- for n_prefix=i+1 (e.g. if i=0 then we increment
- for n_prefix=1 which is stored in n_diff[0]) */
- n_diff[i]++;
- }
- } else {
- /* this is the first non-delete marked record */
- for (i = 0; i < n_uniq; i++) {
- n_diff[i] = 1;
- }
- }
-
- if (rec_is_last_on_page) {
- /* end of a page has been reached */
-
- /* we need to copy the record instead of assigning
- like prev_rec = rec; because when we traverse the
- records on this level at some point we will jump from
- one page to the next and then rec and prev_rec will
- be on different pages and
- btr_pcur_move_to_next_user_rec() will release the
- latch on the page that prev_rec is on */
- prev_rec = rec_copy_prefix_to_buf(
- rec, index, rec_offs_n_fields(rec_offsets),
- &prev_rec_buf, &prev_rec_buf_size);
- prev_rec_is_copied = true;
-
- } else {
- /* still on the same page, the next call to
- btr_pcur_move_to_next_user_rec() will not jump
- on the next page, we can simply assign pointers
- instead of copying the records like above */
-
- prev_rec = rec;
- prev_rec_is_copied = false;
- }
- }
-
- /* if *total_pages is left untouched then the above loop was not
- entered at all and there is one page in the whole tree which is
- empty or the loop was entered but this is level 0, contains one page
- and all records are delete-marked */
- if (*total_pages == 0) {
-
- ut_ad(level == 0);
- ut_ad(*total_recs == 0);
-
- *total_pages = 1;
- }
-
- /* if there are records on this level and boundaries
- should be saved */
- if (*total_recs > 0 && n_diff_boundaries != NULL) {
-
- /* remember the index of the last record on the level as the
- last one from the last group of equal keys; this holds for
- all possible prefixes */
- for (i = 0; i < n_uniq; i++) {
- ib_uint64_t idx;
-
- idx = *total_recs - 1;
-
- n_diff_boundaries[i].push_back(idx);
- }
- }
-
- /* now in n_diff_boundaries[i] there are exactly n_diff[i] integers,
- for i=0..n_uniq-1 */
-
-#ifdef UNIV_STATS_DEBUG
- for (i = 0; i < n_uniq; i++) {
-
- DEBUG_PRINTF(" %s(): total recs: " UINT64PF
- ", total pages: " UINT64PF
- ", n_diff[%lu]: " UINT64PF "\n",
- __func__, *total_recs,
- *total_pages,
- i, n_diff[i]);
-
-#if 0
- if (n_diff_boundaries != NULL) {
- ib_uint64_t j;
-
- DEBUG_PRINTF(" %s(): boundaries[%lu]: ",
- __func__, i);
-
- for (j = 0; j < n_diff[i]; j++) {
- ib_uint64_t idx;
-
- idx = n_diff_boundaries[i][j];
-
- DEBUG_PRINTF(UINT64PF "=" UINT64PF ", ",
- j, idx);
- }
- DEBUG_PRINTF("\n");
- }
-#endif
- }
-#endif /* UNIV_STATS_DEBUG */
-
- /* Release the latch on the last page, because that is not done by
- btr_pcur_close(). This function works also for non-leaf pages. */
- btr_leaf_page_release(btr_pcur_get_block(&pcur), BTR_SEARCH_LEAF, mtr);
-
- btr_pcur_close(&pcur);
-
- if (prev_rec_buf != NULL) {
-
- mem_free(prev_rec_buf);
- }
-
- mem_heap_free(heap);
-}
-
-/* aux enum for controlling the behavior of dict_stats_scan_page() @{ */
-enum page_scan_method_t {
- COUNT_ALL_NON_BORING_AND_SKIP_DEL_MARKED,/* scan all records on
- the given page and count the number of
- distinct ones, also ignore delete marked
- records */
- QUIT_ON_FIRST_NON_BORING,/* quit when the first record that differs
- from its right neighbor is found */
- COUNT_ALL_NON_BORING_INCLUDE_DEL_MARKED/* scan all records on
- the given page and count the number of
- distinct ones, include delete marked
- records */
-};
-/* @} */
-
-/** Scan a page, reading records from left to right and counting the number
-of distinct records (looking only at the first n_prefix
-columns) and the number of external pages pointed by records from this page.
-If scan_method is QUIT_ON_FIRST_NON_BORING then the function
-will return as soon as it finds a record that does not match its neighbor
-to the right, which means that in the case of QUIT_ON_FIRST_NON_BORING the
-returned n_diff can either be 0 (empty page), 1 (the whole page has all keys
-equal) or 2 (the function found a non-boring record and returned).
-@param[out] out_rec record, or NULL
-@param[out] offsets1 rec_get_offsets() working space (must
-be big enough)
-@param[out] offsets2 rec_get_offsets() working space (must
-be big enough)
-@param[in] index index of the page
-@param[in] page the page to scan
-@param[in] n_prefix look at the first n_prefix columns
-@param[in] scan_method scan to the end of the page or not
-@param[out] n_diff number of distinct records encountered
-@param[out] n_external_pages if this is non-NULL then it will be set
-to the number of externally stored pages which were encountered
-@return offsets1 or offsets2 (the offsets of *out_rec),
-or NULL if the page is empty and does not contain user records. */
-UNIV_INLINE
-ulint*
-dict_stats_scan_page(
- const rec_t** out_rec,
- ulint* offsets1,
- ulint* offsets2,
- dict_index_t* index,
- const page_t* page,
- ulint n_prefix,
- page_scan_method_t scan_method,
- ib_uint64_t* n_diff,
- ib_uint64_t* n_external_pages)
-{
- ulint* offsets_rec = offsets1;
- ulint* offsets_next_rec = offsets2;
- const rec_t* rec;
- const rec_t* next_rec;
- /* A dummy heap, to be passed to rec_get_offsets().
- Because offsets1,offsets2 should be big enough,
- this memory heap should never be used. */
- mem_heap_t* heap = NULL;
- const rec_t* (*get_next)(const rec_t*);
-
- if (scan_method == COUNT_ALL_NON_BORING_AND_SKIP_DEL_MARKED) {
- get_next = page_rec_get_next_non_del_marked;
- } else {
- get_next = page_rec_get_next_const;
- }
-
- const bool should_count_external_pages = n_external_pages != NULL;
-
- if (should_count_external_pages) {
- *n_external_pages = 0;
- }
-
- rec = get_next(page_get_infimum_rec(page));
-
- if (page_rec_is_supremum(rec)) {
- /* the page is empty or contains only delete-marked records */
- *n_diff = 0;
- *out_rec = NULL;
- return(NULL);
- }
-
- offsets_rec = rec_get_offsets(rec, index, offsets_rec,
- ULINT_UNDEFINED, &heap);
-
- if (should_count_external_pages) {
- *n_external_pages += btr_rec_get_externally_stored_len(
- rec, offsets_rec);
- }
-
- next_rec = get_next(rec);
-
- *n_diff = 1;
-
- while (!page_rec_is_supremum(next_rec)) {
-
- ulint matched_fields = 0;
- ulint matched_bytes = 0;
-
- offsets_next_rec = rec_get_offsets(next_rec, index,
- offsets_next_rec,
- ULINT_UNDEFINED,
- &heap);
-
- /* check whether rec != next_rec when looking at
- the first n_prefix fields */
- cmp_rec_rec_with_match(rec, next_rec,
- offsets_rec, offsets_next_rec,
- index, FALSE, &matched_fields,
- &matched_bytes);
-
- if (matched_fields < n_prefix) {
- /* rec != next_rec, => rec is non-boring */
-
- (*n_diff)++;
-
- if (scan_method == QUIT_ON_FIRST_NON_BORING) {
- goto func_exit;
- }
- }
-
- rec = next_rec;
- {
- /* Assign offsets_rec = offsets_next_rec
- so that offsets_rec matches with rec which
- was just assigned rec = next_rec above.
- Also need to point offsets_next_rec to the
- place where offsets_rec was pointing before
- because we have just 2 placeholders where
- data is actually stored:
- offsets_onstack1 and offsets_onstack2 and we
- are using them in circular fashion
- (offsets[_next]_rec are just pointers to
- those placeholders). */
- ulint* offsets_tmp;
- offsets_tmp = offsets_rec;
- offsets_rec = offsets_next_rec;
- offsets_next_rec = offsets_tmp;
- }
-
- if (should_count_external_pages) {
- *n_external_pages += btr_rec_get_externally_stored_len(
- rec, offsets_rec);
- }
-
- next_rec = get_next(next_rec);
- }
-
-func_exit:
- /* offsets1,offsets2 should have been big enough */
- ut_a(heap == NULL);
- *out_rec = rec;
- return(offsets_rec);
-}
-
-/** Dive below the current position of a cursor and calculate the number of
-distinct records on the leaf page, when looking at the fist n_prefix
-columns. Also calculate the number of external pages pointed by records
-on the leaf page.
-@param[in] cur cursor
-@param[in] n_prefix look at the first n_prefix columns
-when comparing records
-@param[out] n_diff number of distinct records
-@param[out] n_external_pages number of external pages
-@return number of distinct records on the leaf page */
-static
-void
-dict_stats_analyze_index_below_cur(
- const btr_cur_t* cur,
- ulint n_prefix,
- ib_uint64_t* n_diff,
- ib_uint64_t* n_external_pages)
-{
- dict_index_t* index;
- ulint space;
- ulint zip_size;
- buf_block_t* block;
- ulint page_no;
- const page_t* page;
- mem_heap_t* heap;
- const rec_t* rec;
- ulint* offsets1;
- ulint* offsets2;
- ulint* offsets_rec;
- ulint size;
- mtr_t mtr;
-
- index = btr_cur_get_index(cur);
-
- /* Allocate offsets for the record and the node pointer, for
- node pointer records. In a secondary index, the node pointer
- record will consist of all index fields followed by a child
- page number.
- Allocate space for the offsets header (the allocation size at
- offsets[0] and the REC_OFFS_HEADER_SIZE bytes), and n_fields + 1,
- so that this will never be less than the size calculated in
- rec_get_offsets_func(). */
- size = (1 + REC_OFFS_HEADER_SIZE) + 1 + dict_index_get_n_fields(index);
-
- heap = mem_heap_create(size * (sizeof *offsets1 + sizeof *offsets2));
-
- offsets1 = static_cast<ulint*>(mem_heap_alloc(
- heap, size * sizeof *offsets1));
-
- offsets2 = static_cast<ulint*>(mem_heap_alloc(
- heap, size * sizeof *offsets2));
-
- rec_offs_set_n_alloc(offsets1, size);
- rec_offs_set_n_alloc(offsets2, size);
-
- space = dict_index_get_space(index);
- zip_size = dict_table_zip_size(index->table);
-
- rec = btr_cur_get_rec(cur);
-
- offsets_rec = rec_get_offsets(rec, index, offsets1,
- ULINT_UNDEFINED, &heap);
-
- page_no = btr_node_ptr_get_child_page_no(rec, offsets_rec);
-
- /* assume no external pages by default - in case we quit from this
- function without analyzing any leaf pages */
- *n_external_pages = 0;
-
- mtr_start(&mtr);
-
- /* descend to the leaf level on the B-tree */
- for (;;) {
-
- block = buf_page_get_gen(space, zip_size, page_no, RW_S_LATCH,
- NULL /* no guessed block */,
- BUF_GET, __FILE__, __LINE__, &mtr);
-
- page = buf_block_get_frame(block);
-
- if (page_is_leaf(page)) {
- /* leaf level */
- break;
- }
- /* else */
-
- /* search for the first non-boring record on the page */
- offsets_rec = dict_stats_scan_page(
- &rec, offsets1, offsets2, index, page, n_prefix,
- QUIT_ON_FIRST_NON_BORING, n_diff, NULL);
-
- /* pages on level > 0 are not allowed to be empty */
- ut_a(offsets_rec != NULL);
- /* if page is not empty (offsets_rec != NULL) then n_diff must
- be > 0, otherwise there is a bug in dict_stats_scan_page() */
- ut_a(*n_diff > 0);
-
- if (*n_diff == 1) {
- mtr_commit(&mtr);
-
- /* page has all keys equal and the end of the page
- was reached by dict_stats_scan_page(), no need to
- descend to the leaf level */
- mem_heap_free(heap);
- /* can't get an estimate for n_external_pages here
- because we do not dive to the leaf level, assume no
- external pages (*n_external_pages was assigned to 0
- above). */
- return;
- }
- /* else */
-
- /* when we instruct dict_stats_scan_page() to quit on the
- first non-boring record it finds, then the returned n_diff
- can either be 0 (empty page), 1 (page has all keys equal) or
- 2 (non-boring record was found) */
- ut_a(*n_diff == 2);
-
- /* we have a non-boring record in rec, descend below it */
-
- page_no = btr_node_ptr_get_child_page_no(rec, offsets_rec);
- }
-
- /* make sure we got a leaf page as a result from the above loop */
- ut_ad(page_is_leaf(page));
-
- /* scan the leaf page and find the number of distinct keys,
- when looking only at the first n_prefix columns; also estimate
- the number of externally stored pages pointed by records on this
- page */
-
- offsets_rec = dict_stats_scan_page(
- &rec, offsets1, offsets2, index, page, n_prefix,
- srv_stats_include_delete_marked ?
- COUNT_ALL_NON_BORING_INCLUDE_DEL_MARKED:
- COUNT_ALL_NON_BORING_AND_SKIP_DEL_MARKED, n_diff,
- n_external_pages);
-
-#if 0
- DEBUG_PRINTF(" %s(): n_diff below page_no=%lu: " UINT64PF "\n",
- __func__, page_no, n_diff);
-#endif
-
- mtr_commit(&mtr);
- mem_heap_free(heap);
-}
-
-/** Input data that is used to calculate dict_index_t::stat_n_diff_key_vals[]
-for each n-columns prefix (n from 1 to n_uniq). */
-struct n_diff_data_t {
- /** Index of the level on which the descent through the btree
- stopped. level 0 is the leaf level. This is >= 1 because we
- avoid scanning the leaf level because it may contain too many
- pages and doing so is useless when combined with the random dives -
- if we are to scan the leaf level, this means a full scan and we can
- simply do that instead of fiddling with picking random records higher
- in the tree and to dive below them. At the start of the analyzing
- we may decide to do full scan of the leaf level, but then this
- structure is not used in that code path. */
- ulint level;
-
- /** Number of records on the level where the descend through the btree
- stopped. When we scan the btree from the root, we stop at some mid
- level, choose some records from it and dive below them towards a leaf
- page to analyze. */
- ib_uint64_t n_recs_on_level;
-
- /** Number of different key values that were found on the mid level. */
- ib_uint64_t n_diff_on_level;
-
- /** Number of leaf pages that are analyzed. This is also the same as
- the number of records that we pick from the mid level and dive below
- them. */
- ib_uint64_t n_leaf_pages_to_analyze;
-
- /** Cumulative sum of the number of different key values that were
- found on all analyzed pages. */
- ib_uint64_t n_diff_all_analyzed_pages;
-
- /** Cumulative sum of the number of external pages (stored outside of
- the btree but in the same file segment). */
- ib_uint64_t n_external_pages_sum;
-};
-
-/** Estimate the number of different key values in an index when looking at
-the first n_prefix columns. For a given level in an index select
-n_diff_data->n_leaf_pages_to_analyze records from that level and dive below
-them to the corresponding leaf pages, then scan those leaf pages and save the
-sampling results in n_diff_data->n_diff_all_analyzed_pages.
-@param[in] index index
-@param[in] n_prefix look at first 'n_prefix' columns when
-comparing records
-@param[in] boundaries a vector that contains
-n_diff_data->n_diff_on_level integers each of which represents the index (on
-level 'level', counting from left/smallest to right/biggest from 0) of the
-last record from each group of distinct keys
-@param[in,out] n_diff_data n_diff_all_analyzed_pages and
-n_external_pages_sum in this structure will be set by this function. The
-members level, n_diff_on_level and n_leaf_pages_to_analyze must be set by the
-caller in advance - they are used by some calculations inside this function
-@param[in,out] mtr mini-transaction */
-static
-void
-dict_stats_analyze_index_for_n_prefix(
- dict_index_t* index,
- ulint n_prefix,
- const boundaries_t* boundaries,
- n_diff_data_t* n_diff_data,
- mtr_t* mtr)
-{
- btr_pcur_t pcur;
- const page_t* page;
- ib_uint64_t rec_idx;
- ib_uint64_t i;
-
-#if 0
- DEBUG_PRINTF(" %s(table=%s, index=%s, level=%lu, n_prefix=%lu, "
- "n_diff_on_level=" UINT64PF ")\n",
- __func__, index->table->name, index->name, level,
- n_prefix, n_diff_data->n_diff_on_level);
-#endif
-
- ut_ad(mtr_memo_contains(mtr, dict_index_get_lock(index),
- MTR_MEMO_S_LOCK));
-
- /* Position pcur on the leftmost record on the leftmost page
- on the desired level. */
-
- btr_pcur_open_at_index_side(
- true, index, BTR_SEARCH_LEAF | BTR_ALREADY_S_LATCHED,
- &pcur, true, n_diff_data->level, mtr);
- btr_pcur_move_to_next_on_page(&pcur);
-
- page = btr_pcur_get_page(&pcur);
-
- const rec_t* first_rec = btr_pcur_get_rec(&pcur);
-
- /* We shouldn't be scanning the leaf level. The caller of this function
- should have stopped the descend on level 1 or higher. */
- ut_ad(n_diff_data->level > 0);
- ut_ad(!page_is_leaf(page));
-
- /* The page must not be empty, except when
- it is the root page (and the whole index is empty). */
- ut_ad(btr_pcur_is_on_user_rec(&pcur));
- ut_ad(first_rec == page_rec_get_next_const(page_get_infimum_rec(page)));
-
- /* check that we are indeed on the desired level */
- ut_a(btr_page_get_level(page, mtr) == n_diff_data->level);
-
- /* there should not be any pages on the left */
- ut_a(btr_page_get_prev(page, mtr) == FIL_NULL);
-
- /* check whether the first record on the leftmost page is marked
- as such; we are on a non-leaf level */
- ut_a(rec_get_info_bits(first_rec, page_is_comp(page))
- & REC_INFO_MIN_REC_FLAG);
-
- const ib_uint64_t last_idx_on_level = boundaries->at(
- static_cast<unsigned>(n_diff_data->n_diff_on_level - 1));
-
- rec_idx = 0;
-
- n_diff_data->n_diff_all_analyzed_pages = 0;
- n_diff_data->n_external_pages_sum = 0;
-
- for (i = 0; i < n_diff_data->n_leaf_pages_to_analyze; i++) {
- /* there are n_diff_on_level elements
- in 'boundaries' and we divide those elements
- into n_leaf_pages_to_analyze segments, for example:
-
- let n_diff_on_level=100, n_leaf_pages_to_analyze=4, then:
- segment i=0: [0, 24]
- segment i=1: [25, 49]
- segment i=2: [50, 74]
- segment i=3: [75, 99] or
-
- let n_diff_on_level=1, n_leaf_pages_to_analyze=1, then:
- segment i=0: [0, 0] or
-
- let n_diff_on_level=2, n_leaf_pages_to_analyze=2, then:
- segment i=0: [0, 0]
- segment i=1: [1, 1] or
-
- let n_diff_on_level=13, n_leaf_pages_to_analyze=7, then:
- segment i=0: [0, 0]
- segment i=1: [1, 2]
- segment i=2: [3, 4]
- segment i=3: [5, 6]
- segment i=4: [7, 8]
- segment i=5: [9, 10]
- segment i=6: [11, 12]
-
- then we select a random record from each segment and dive
- below it */
- const ib_uint64_t n_diff = n_diff_data->n_diff_on_level;
- const ib_uint64_t n_pick
- = n_diff_data->n_leaf_pages_to_analyze;
-
- const ib_uint64_t left = n_diff * i / n_pick;
- const ib_uint64_t right = n_diff * (i + 1) / n_pick - 1;
-
- ut_a(left <= right);
- ut_a(right <= last_idx_on_level);
-
- /* 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 */
- const ulint rnd = ut_rnd_interval(
- 0, static_cast<ulint>(right - left));
-
- const ib_uint64_t dive_below_idx
- = boundaries->at(static_cast<unsigned>(left + rnd));
-
-#if 0
- DEBUG_PRINTF(" %s(): dive below record with index="
- UINT64PF "\n", __func__, dive_below_idx);
-#endif
-
- /* seek to the record with index dive_below_idx */
- while (rec_idx < dive_below_idx
- && btr_pcur_is_on_user_rec(&pcur)) {
-
- btr_pcur_move_to_next_user_rec(&pcur, mtr);
- rec_idx++;
- }
-
- /* if the level has finished before the record we are
- searching for, this means that the B-tree has changed in
- the meantime, quit our sampling and use whatever stats
- we have collected so far */
- if (rec_idx < dive_below_idx) {
-
- ut_ad(!btr_pcur_is_on_user_rec(&pcur));
- break;
- }
-
- /* it could be that the tree has changed in such a way that
- the record under dive_below_idx is the supremum record, in
- this case rec_idx == dive_below_idx and pcur is positioned
- on the supremum, we do not want to dive below it */
- if (!btr_pcur_is_on_user_rec(&pcur)) {
- break;
- }
-
- ut_a(rec_idx == dive_below_idx);
-
- ib_uint64_t n_diff_on_leaf_page;
- ib_uint64_t n_external_pages;
-
- dict_stats_analyze_index_below_cur(btr_pcur_get_btr_cur(&pcur),
- n_prefix,
- &n_diff_on_leaf_page,
- &n_external_pages);
-
- /* We adjust n_diff_on_leaf_page here to avoid counting
- one record twice - once as the last on some page and once
- as the first on another page. Consider the following example:
- Leaf level:
- page: (2,2,2,2,3,3)
- ... many pages like (3,3,3,3,3,3) ...
- page: (3,3,3,3,5,5)
- ... many pages like (5,5,5,5,5,5) ...
- page: (5,5,5,5,8,8)
- page: (8,8,8,8,9,9)
- our algo would (correctly) get an estimate that there are
- 2 distinct records per page (average). Having 4 pages below
- non-boring records, it would (wrongly) estimate the number
- of distinct records to 8. */
- if (n_diff_on_leaf_page > 0) {
- n_diff_on_leaf_page--;
- }
-
- n_diff_data->n_diff_all_analyzed_pages += n_diff_on_leaf_page;
-
- n_diff_data->n_external_pages_sum += n_external_pages;
- }
-
- btr_pcur_close(&pcur);
-}
-
-/** Set dict_index_t::stat_n_diff_key_vals[] and stat_n_sample_sizes[].
-@param[in] n_diff_data input data to use to derive the results
-@param[in,out] index index whose stat_n_diff_key_vals[] to set */
-UNIV_INLINE
-void
-dict_stats_index_set_n_diff(
- const n_diff_data_t* n_diff_data,
- dict_index_t* index)
-{
- for (ulint n_prefix = dict_index_get_n_unique(index);
- n_prefix >= 1;
- n_prefix--) {
- /* n_diff_all_analyzed_pages can be 0 here if
- all the leaf pages sampled contained only
- delete-marked records. In this case we should assign
- 0 to index->stat_n_diff_key_vals[n_prefix - 1], which
- the formula below does. */
-
- const n_diff_data_t* data = &n_diff_data[n_prefix - 1];
-
- ut_ad(data->n_leaf_pages_to_analyze > 0);
- ut_ad(data->n_recs_on_level > 0);
-
- ulint n_ordinary_leaf_pages;
-
- if (data->level == 1) {
- /* If we know the number of records on level 1, then
- this number is the same as the number of pages on
- level 0 (leaf). */
- n_ordinary_leaf_pages = data->n_recs_on_level;
- } else {
- /* If we analyzed D ordinary leaf pages and found E
- external pages in total linked from those D ordinary
- leaf pages, then this means that the ratio
- ordinary/external is D/E. Then the ratio ordinary/total
- is D / (D + E). Knowing that the total number of pages
- is T (including ordinary and external) then we estimate
- that the total number of ordinary leaf pages is
- T * D / (D + E). */
- n_ordinary_leaf_pages
- = index->stat_n_leaf_pages
- * data->n_leaf_pages_to_analyze
- / (data->n_leaf_pages_to_analyze
- + data->n_external_pages_sum);
- }
-
- /* See REF01 for an explanation of the algorithm */
- index->stat_n_diff_key_vals[n_prefix - 1]
- = n_ordinary_leaf_pages
-
- * data->n_diff_on_level
- / data->n_recs_on_level
-
- * data->n_diff_all_analyzed_pages
- / data->n_leaf_pages_to_analyze;
-
- index->stat_n_sample_sizes[n_prefix - 1]
- = data->n_leaf_pages_to_analyze;
-
- DEBUG_PRINTF(" %s(): n_diff=" UINT64PF " for n_prefix=%lu"
- " (%lu"
- " * " UINT64PF " / " UINT64PF
- " * " UINT64PF " / " UINT64PF ")\n",
- __func__,
- index->stat_n_diff_key_vals[n_prefix - 1],
- n_prefix,
- index->stat_n_leaf_pages,
- data->n_diff_on_level,
- data->n_recs_on_level,
- data->n_diff_all_analyzed_pages,
- data->n_leaf_pages_to_analyze);
- }
-}
-
-/*********************************************************************//**
-Calculates new statistics for a given index and saves them to the index
-members stat_n_diff_key_vals[], stat_n_sample_sizes[], stat_index_size and
-stat_n_leaf_pages. This function could be slow. */
-static
-void
-dict_stats_analyze_index(
-/*=====================*/
- dict_index_t* index) /*!< in/out: index to analyze */
-{
- ulint root_level;
- ulint level;
- bool level_is_analyzed;
- ulint n_uniq;
- ulint n_prefix;
- ib_uint64_t total_recs;
- ib_uint64_t total_pages;
- mtr_t mtr;
- ulint size;
- DBUG_ENTER("dict_stats_analyze_index");
-
- DBUG_PRINT("info", ("index: %s, online status: %d", index->name,
- dict_index_get_online_status(index)));
-
- DEBUG_PRINTF(" %s(index=%s)\n", __func__, index->name);
-
- dict_stats_empty_index(index, false);
-
- mtr_start(&mtr);
-
- mtr_s_lock(dict_index_get_lock(index), &mtr);
-
- size = btr_get_size(index, BTR_TOTAL_SIZE, &mtr);
-
- if (size != ULINT_UNDEFINED) {
- index->stat_index_size = size;
- size = btr_get_size(index, BTR_N_LEAF_PAGES, &mtr);
- }
-
- /* Release the X locks on the root page taken by btr_get_size() */
- mtr_commit(&mtr);
-
- switch (size) {
- case ULINT_UNDEFINED:
- dict_stats_assert_initialized_index(index);
- DBUG_VOID_RETURN;
- case 0:
- /* The root node of the tree is a leaf */
- size = 1;
- }
-
- index->stat_n_leaf_pages = size;
-
- mtr_start(&mtr);
-
- mtr_s_lock(dict_index_get_lock(index), &mtr);
-
- root_level = btr_height_get(index, &mtr);
-
- n_uniq = dict_index_get_n_unique(index);
-
- /* If the tree has just one level (and one page) or if the user
- has requested to sample too many pages then do full scan.
-
- For each n-column prefix (for n=1..n_uniq) N_SAMPLE_PAGES(index)
- will be sampled, so in total N_SAMPLE_PAGES(index) * n_uniq leaf
- pages will be sampled. If that number is bigger than the total
- number of leaf pages then do full scan of the leaf level instead
- since it will be faster and will give better results. */
-
- if (root_level == 0
- || N_SAMPLE_PAGES(index) * n_uniq > index->stat_n_leaf_pages) {
-
- if (root_level == 0) {
- DEBUG_PRINTF(" %s(): just one page, "
- "doing full scan\n", __func__);
- } else {
- DEBUG_PRINTF(" %s(): too many pages requested for "
- "sampling, doing full scan\n", __func__);
- }
-
- /* do full scan of level 0; save results directly
- into the index */
-
- dict_stats_analyze_index_level(index,
- 0 /* leaf level */,
- index->stat_n_diff_key_vals,
- &total_recs,
- &total_pages,
- NULL /* boundaries not needed */,
- &mtr);
-
- for (ulint i = 0; i < n_uniq; i++) {
- index->stat_n_sample_sizes[i] = total_pages;
- }
-
- mtr_commit(&mtr);
-
- dict_stats_assert_initialized_index(index);
- DBUG_VOID_RETURN;
- }
-
- /* For each level that is being scanned in the btree, this contains the
- number of different key values for all possible n-column prefixes. */
- ib_uint64_t* n_diff_on_level = new ib_uint64_t[n_uniq];
-
- /* For each level that is being scanned in the btree, this contains the
- index of the last record from each group of equal records (when
- comparing only the first n columns, n=1..n_uniq). */
- boundaries_t* n_diff_boundaries = new boundaries_t[n_uniq];
-
- /* For each n-column prefix this array contains the input data that is
- used to calculate dict_index_t::stat_n_diff_key_vals[]. */
- n_diff_data_t* n_diff_data = new n_diff_data_t[n_uniq];
-
- /* total_recs is also used to estimate the number of pages on one
- level below, so at the start we have 1 page (the root) */
- total_recs = 1;
-
- /* Here we use the following optimization:
- If we find that level L is the first one (searching from the
- root) that contains at least D distinct keys when looking at
- the first n_prefix columns, then:
- if we look at the first n_prefix-1 columns then the first
- level that contains D distinct keys will be either L or a
- lower one.
- So if we find that the first level containing D distinct
- keys (on n_prefix columns) is L, we continue from L when
- searching for D distinct keys on n_prefix-1 columns. */
- level = root_level;
- level_is_analyzed = false;
-
- for (n_prefix = n_uniq; n_prefix >= 1; n_prefix--) {
-
- DEBUG_PRINTF(" %s(): searching level with >=%llu "
- "distinct records, n_prefix=%lu\n",
- __func__, N_DIFF_REQUIRED(index), n_prefix);
-
- /* Commit the mtr to release the tree S lock to allow
- other threads to do some work too. */
- mtr_commit(&mtr);
- mtr_start(&mtr);
- mtr_s_lock(dict_index_get_lock(index), &mtr);
- if (root_level != btr_height_get(index, &mtr)) {
- /* Just quit if the tree has changed beyond
- recognition here. The old stats from previous
- runs will remain in the values that we have
- not calculated yet. Initially when the index
- object is created the stats members are given
- some sensible values so leaving them untouched
- here even the first time will not cause us to
- read uninitialized memory later. */
- break;
- }
-
- /* check whether we should pick the current level;
- we pick level 1 even if it does not have enough
- distinct records because we do not want to scan the
- leaf level because it may contain too many records */
- if (level_is_analyzed
- && (n_diff_on_level[n_prefix - 1] >= N_DIFF_REQUIRED(index)
- || level == 1)) {
-
- goto found_level;
- }
-
- /* search for a level that contains enough distinct records */
-
- if (level_is_analyzed && level > 1) {
-
- /* if this does not hold we should be on
- "found_level" instead of here */
- ut_ad(n_diff_on_level[n_prefix - 1]
- < N_DIFF_REQUIRED(index));
-
- level--;
- level_is_analyzed = false;
- }
-
- /* descend into the tree, searching for "good enough" level */
- for (;;) {
-
- /* make sure we do not scan the leaf level
- accidentally, it may contain too many pages */
- ut_ad(level > 0);
-
- /* scanning the same level twice is an optimization
- bug */
- ut_ad(!level_is_analyzed);
-
- /* Do not scan if this would read too many pages.
- Here we use the following fact:
- the number of pages on level L equals the number
- of records on level L+1, thus we deduce that the
- following call would scan total_recs pages, because
- total_recs is left from the previous iteration when
- we scanned one level upper or we have not scanned any
- levels yet in which case total_recs is 1. */
- if (total_recs > N_SAMPLE_PAGES(index)) {
-
- /* if the above cond is true then we are
- not at the root level since on the root
- level total_recs == 1 (set before we
- enter the n-prefix loop) and cannot
- be > N_SAMPLE_PAGES(index) */
- ut_a(level != root_level);
-
- /* step one level back and be satisfied with
- whatever it contains */
- level++;
- level_is_analyzed = true;
-
- break;
- }
-
- dict_stats_analyze_index_level(index,
- level,
- n_diff_on_level,
- &total_recs,
- &total_pages,
- n_diff_boundaries,
- &mtr);
-
- level_is_analyzed = true;
-
- if (level == 1
- || n_diff_on_level[n_prefix - 1]
- >= N_DIFF_REQUIRED(index)) {
- /* we have reached the last level we could scan
- or we found a good level with many distinct
- records */
- break;
- }
-
- level--;
- level_is_analyzed = false;
- }
-found_level:
-
- DEBUG_PRINTF(" %s(): found level %lu that has " UINT64PF
- " distinct records for n_prefix=%lu\n",
- __func__, level, n_diff_on_level[n_prefix - 1],
- n_prefix);
- /* here we are either on level 1 or the level that we are on
- contains >= N_DIFF_REQUIRED distinct keys or we did not scan
- deeper levels because they would contain too many pages */
-
- ut_ad(level > 0);
-
- ut_ad(level_is_analyzed);
-
- /* if any of these is 0 then there is exactly one page in the
- B-tree and it is empty and we should have done full scan and
- should not be here */
- ut_ad(total_recs > 0);
- ut_ad(n_diff_on_level[n_prefix - 1] > 0);
-
- ut_ad(N_SAMPLE_PAGES(index) > 0);
-
- n_diff_data_t* data = &n_diff_data[n_prefix - 1];
-
- data->level = level;
-
- data->n_recs_on_level = total_recs;
-
- data->n_diff_on_level = n_diff_on_level[n_prefix - 1];
-
- data->n_leaf_pages_to_analyze = std::min(
- N_SAMPLE_PAGES(index),
- n_diff_on_level[n_prefix - 1]);
-
- /* pick some records from this level and dive below them for
- the given n_prefix */
-
- dict_stats_analyze_index_for_n_prefix(
- index, n_prefix, &n_diff_boundaries[n_prefix - 1],
- data, &mtr);
- }
-
- mtr_commit(&mtr);
-
- delete[] n_diff_boundaries;
-
- delete[] n_diff_on_level;
-
- /* n_prefix == 0 means that the above loop did not end up prematurely
- due to tree being changed and so n_diff_data[] is set up. */
- if (n_prefix == 0) {
- dict_stats_index_set_n_diff(n_diff_data, index);
- }
-
- delete[] n_diff_data;
-
- dict_stats_assert_initialized_index(index);
- DBUG_VOID_RETURN;
-}
-
-/*********************************************************************//**
-Calculates new estimates for table and index statistics. This function
-is relatively slow and is used to calculate persistent statistics that
-will be saved on disk.
-@return DB_SUCCESS or error code */
-static
-dberr_t
-dict_stats_update_persistent(
-/*=========================*/
- dict_table_t* table) /*!< in/out: table */
-{
- dict_index_t* index;
-
- DEBUG_PRINTF("%s(table=%s)\n", __func__, table->name);
-
- dict_table_stats_lock(table, RW_X_LATCH);
-
- /* analyze the clustered index first */
-
- index = dict_table_get_first_index(table);
-
- if (index == NULL
- || dict_index_is_corrupted(index)
- || (index->type | DICT_UNIQUE) != (DICT_CLUSTERED | DICT_UNIQUE)) {
-
- /* Table definition is corrupt */
- dict_table_stats_unlock(table, RW_X_LATCH);
- dict_stats_empty_table(table, true);
-
- return(DB_CORRUPTION);
- }
-
- ut_ad(!dict_index_is_univ(index));
-
- dict_stats_analyze_index(index);
-
- ulint n_unique = dict_index_get_n_unique(index);
-
- table->stat_n_rows = index->stat_n_diff_key_vals[n_unique - 1];
-
- table->stat_clustered_index_size = index->stat_index_size;
-
- /* analyze other indexes from the table, if any */
-
- table->stat_sum_of_other_index_sizes = 0;
-
- for (index = dict_table_get_next_index(index);
- index != NULL;
- index = dict_table_get_next_index(index)) {
-
- ut_ad(!dict_index_is_univ(index));
-
- if (index->type & DICT_FTS) {
- continue;
- }
-
- dict_stats_empty_index(index, false);
-
- if (dict_stats_should_ignore_index(index)) {
- continue;
- }
-
- if (!(table->stats_bg_flag & BG_STAT_SHOULD_QUIT)) {
- dict_stats_analyze_index(index);
- }
-
- table->stat_sum_of_other_index_sizes
- += index->stat_index_size;
- }
-
- table->stats_last_recalc = ut_time();
-
- table->stat_modified_counter = 0;
-
- table->stat_initialized = TRUE;
-
- dict_stats_assert_initialized(table);
-
- dict_table_stats_unlock(table, RW_X_LATCH);
-
- return(DB_SUCCESS);
-}
-
-#include "mysql_com.h"
-/** 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,
- 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;
- char db_utf8[MAX_DB_UTF8_LEN];
- char table_utf8[MAX_TABLE_UTF8_LEN];
-
-#ifdef UNIV_SYNC_DEBUG
- ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
-#endif /* UNIV_SYNC_DEBUG */
- ut_ad(mutex_own(&dict_sys->mutex));
-
- dict_fs2utf8(index->table->name, db_utf8, sizeof(db_utf8),
- table_utf8, sizeof(table_utf8));
-
- 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 () 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"
- ":database_name,\n"
- ":table_name,\n"
- ":index_name,\n"
- ":last_update,\n"
- ":stat_name,\n"
- ":stat_value,\n"
- ":sample_size,\n"
- ":stat_description\n"
- ");\n"
- "END;", trx);
-
- if (ret != DB_SUCCESS) {
- if (innodb_index_stats_not_found == false &&
- index->stats_error_printed == false) {
- char buf_table[MAX_FULL_NAME_LEN];
- char buf_index[MAX_FULL_NAME_LEN];
- ut_print_timestamp(stderr);
- fprintf(stderr,
- " InnoDB: Cannot save index statistics for table "
- "%s, index %s, stat name \"%s\": %s\n",
- ut_format_name(index->table->name, TRUE,
- buf_table, sizeof(buf_table)),
- ut_format_name(index->name, FALSE,
- buf_index, sizeof(buf_index)),
- stat_name, ut_strerr(ret));
- index->stats_error_printed = true;
- }
- }
-
- return(ret);
-}
-
-/** Report error if statistic update for a table failed because
-.ibd file is missing, table decryption failed or table is corrupted.
-@param[in,out] table Table
-@param[in] defragment true if statistics is for defragment
-@return DB_DECRYPTION_FAILED, DB_TABLESPACE_DELETED or DB_CORRUPTION
-@retval DB_DECRYPTION_FAILED if decryption of the table failed
-@retval DB_TABLESPACE_DELETED if .ibd file is missing
-@retval DB_CORRUPTION if table is marked as corrupted */
-static
-dberr_t
-dict_stats_report_error(
- dict_table_t* table,
- bool defragment = false)
-{
- char buf[3 * NAME_LEN];
- dberr_t err;
-
- innobase_format_name(buf, sizeof buf,
- table->name,
- true);
-
- FilSpace space(table->space);
-
- if (space()) {
- if (table->corrupted) {
- ib_logf(IB_LOG_LEVEL_INFO,
- "Cannot save%s statistics because "
- " table %s in file %s is corrupted.",
- defragment ? " defragment" : " ",
- buf, space()->chain.start->name);
- err = DB_CORRUPTION;
- } else {
- ib_logf(IB_LOG_LEVEL_INFO,
- "Cannot save%s statistics because "
- " table %s in file %s can't be decrypted.",
- defragment ? " defragment" : " ",
- buf, space()->chain.start->name);
- err = DB_DECRYPTION_FAILED;
- }
- } else {
- ib_logf(IB_LOG_LEVEL_INFO,
- "Cannot save%s statistics for "
- " table %s because .ibd file is missing."
- " For help, please "
- "refer to " REFMAN "innodb-troubleshooting.html.",
- defragment ? " defragment" : " ",
- buf);
- err = DB_TABLESPACE_DELETED;
- }
-
- dict_stats_empty_table(table, defragment);
-
- return (err);
-}
-
-/** 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,
- const index_id_t* only_for_index)
-{
- pars_info_t* pinfo;
- lint now;
- dberr_t ret;
- dict_table_t* table;
- char db_utf8[MAX_DB_UTF8_LEN];
- char table_utf8[MAX_TABLE_UTF8_LEN];
-
- if (table_orig->is_readable()) {
- } else {
- return (dict_stats_report_error(table_orig));
- }
-
- table = dict_stats_snapshot_create(table_orig);
-
- dict_fs2utf8(table->name, db_utf8, sizeof(db_utf8),
- table_utf8, sizeof(table_utf8));
-
- rw_lock_x_lock(&dict_operation_lock);
- mutex_enter(&dict_sys->mutex);
-
- /* MySQL's timestamp is 4 byte, so we use
- pars_info_add_int4_literal() which takes a lint arg, so "now" is
- lint */
- now = (lint) ut_time();
-
- pinfo = pars_info_create();
-
- 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 () 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"
- ":database_name,\n"
- ":table_name,\n"
- ":last_update,\n"
- ":n_rows,\n"
- ":clustered_index_size,\n"
- ":sum_of_other_index_sizes\n"
- ");\n"
- "END;", NULL);
-
- if (ret != DB_SUCCESS) {
- char buf[MAX_FULL_NAME_LEN];
- ut_print_timestamp(stderr);
- fprintf(stderr,
- " InnoDB: Cannot save table statistics for table "
- "%s: %s\n",
- ut_format_name(table->name, TRUE, buf, sizeof(buf)),
- ut_strerr(ret));
-
- 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)) {
-
- indexes[index->name] = index;
- }
-
- index_map_t::const_iterator it;
-
- for (it = indexes.begin(); it != indexes.end(); ++it) {
-
- index = it->second;
-
- if (only_for_index != NULL && index->id != *only_for_index) {
- continue;
- }
-
- 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];
- char stat_description[1024];
- ulint j;
-
- ut_snprintf(stat_name, sizeof(stat_name),
- "n_diff_pfx%02lu", i + 1);
-
- /* craft a string that contains the columns names */
- ut_snprintf(stat_description,
- sizeof(stat_description),
- "%s", index->fields[0].name);
- for (j = 1; j <= i; j++) {
- size_t len;
-
- len = strlen(stat_description);
-
- ut_snprintf(stat_description + len,
- sizeof(stat_description) - len,
- ",%s", index->fields[j].name);
- }
-
- ret = dict_stats_save_index_stat(
- index, now, stat_name,
- index->stat_n_diff_key_vals[i],
- &index->stat_n_sample_sizes[i],
- 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);
-
- dict_stats_snapshot_free(table);
-
- return(ret);
-}
-
-/*********************************************************************//**
-Called for the row that is selected by
-SELECT ... FROM mysql.innodb_table_stats WHERE table='...'
-The second argument is a pointer to the table and the fetched stats are
-written to it.
-@return non-NULL dummy */
-static
-ibool
-dict_stats_fetch_table_stats_step(
-/*==============================*/
- void* node_void, /*!< in: select node */
- void* table_void) /*!< out: table */
-{
- sel_node_t* node = (sel_node_t*) node_void;
- dict_table_t* table = (dict_table_t*) table_void;
- que_common_t* cnode;
- int i;
-
- /* this should loop exactly 3 times - for
- n_rows,clustered_index_size,sum_of_other_index_sizes */
- for (cnode = static_cast<que_common_t*>(node->select_list), i = 0;
- cnode != NULL;
- cnode = static_cast<que_common_t*>(que_node_get_next(cnode)),
- i++) {
-
- const byte* data;
- dfield_t* dfield = que_node_get_val(cnode);
- dtype_t* type = dfield_get_type(dfield);
- ulint len = dfield_get_len(dfield);
-
- data = static_cast<const byte*>(dfield_get_data(dfield));
-
- switch (i) {
- case 0: /* mysql.innodb_table_stats.n_rows */
-
- ut_a(dtype_get_mtype(type) == DATA_INT);
- ut_a(len == 8);
-
- table->stat_n_rows = mach_read_from_8(data);
-
- break;
-
- case 1: /* mysql.innodb_table_stats.clustered_index_size */
-
- ut_a(dtype_get_mtype(type) == DATA_INT);
- ut_a(len == 8);
-
- table->stat_clustered_index_size
- = (ulint) mach_read_from_8(data);
-
- break;
-
- case 2: /* mysql.innodb_table_stats.sum_of_other_index_sizes */
-
- ut_a(dtype_get_mtype(type) == DATA_INT);
- ut_a(len == 8);
-
- table->stat_sum_of_other_index_sizes
- = (ulint) mach_read_from_8(data);
-
- break;
-
- default:
-
- /* someone changed SELECT
- n_rows,clustered_index_size,sum_of_other_index_sizes
- to select more columns from innodb_table_stats without
- adjusting here */
- ut_error;
- }
- }
-
- /* if i < 3 this means someone changed the
- SELECT n_rows,clustered_index_size,sum_of_other_index_sizes
- to select less columns from innodb_table_stats without adjusting here;
- if i > 3 we would have ut_error'ed earlier */
- ut_a(i == 3 /*n_rows,clustered_index_size,sum_of_other_index_sizes*/);
-
- /* XXX this is not used but returning non-NULL is necessary */
- return(TRUE);
-}
-
-/** Aux struct used to pass a table and a boolean to
-dict_stats_fetch_index_stats_step(). */
-struct index_fetch_t {
- dict_table_t* table; /*!< table whose indexes are to be modified */
- bool stats_were_modified; /*!< will be set to true if at
- least one index stats were modified */
-};
-
-/*********************************************************************//**
-Called for the rows that are selected by
-SELECT ... FROM mysql.innodb_index_stats WHERE table='...'
-The second argument is a pointer to the table and the fetched stats are
-written to its indexes.
-Let a table has N indexes and each index has Ui unique columns for i=1..N,
-then mysql.innodb_index_stats will have SUM(Ui) i=1..N rows for that table.
-So this function will be called SUM(Ui) times where SUM(Ui) is of magnitude
-N*AVG(Ui). In each call it searches for the currently fetched index into
-table->indexes linearly, assuming this list is not sorted. Thus, overall,
-fetching all indexes' stats from mysql.innodb_index_stats is O(N^2) where N
-is the number of indexes.
-This can be improved if we sort table->indexes in a temporary area just once
-and then search in that sorted list. Then the complexity will be O(N*log(N)).
-We assume a table will not have more than 100 indexes, so we go with the
-simpler N^2 algorithm.
-@return non-NULL dummy */
-static
-ibool
-dict_stats_fetch_index_stats_step(
-/*==============================*/
- void* node_void, /*!< in: select node */
- void* arg_void) /*!< out: table + a flag that tells if we
- modified anything */
-{
- sel_node_t* node = (sel_node_t*) node_void;
- index_fetch_t* arg = (index_fetch_t*) arg_void;
- dict_table_t* table = arg->table;
- dict_index_t* index = NULL;
- que_common_t* cnode;
- const char* stat_name = NULL;
- ulint stat_name_len = ULINT_UNDEFINED;
- ib_uint64_t stat_value = UINT64_UNDEFINED;
- ib_uint64_t sample_size = UINT64_UNDEFINED;
- int i;
-
- /* this should loop exactly 4 times - for the columns that
- were selected: index_name,stat_name,stat_value,sample_size */
- for (cnode = static_cast<que_common_t*>(node->select_list), i = 0;
- cnode != NULL;
- cnode = static_cast<que_common_t*>(que_node_get_next(cnode)),
- i++) {
-
- const byte* data;
- dfield_t* dfield = que_node_get_val(cnode);
- dtype_t* type = dfield_get_type(dfield);
- ulint len = dfield_get_len(dfield);
-
- data = static_cast<const byte*>(dfield_get_data(dfield));
-
- switch (i) {
- case 0: /* mysql.innodb_index_stats.index_name */
-
- ut_a(dtype_get_mtype(type) == DATA_VARMYSQL);
-
- /* search for index in table's indexes whose name
- matches data; the fetched index name is in data,
- has no terminating '\0' and has length len */
- for (index = dict_table_get_first_index(table);
- index != NULL;
- index = dict_table_get_next_index(index)) {
-
- if (strlen(index->name) == len
- && memcmp(index->name, data, len) == 0) {
- /* the corresponding index was found */
- break;
- }
- }
-
- /* if index is NULL here this means that
- mysql.innodb_index_stats contains more rows than the
- number of indexes in the table; this is ok, we just
- return ignoring those extra rows; in other words
- dict_stats_fetch_index_stats_step() has been called
- for a row from index_stats with unknown index_name
- column */
- if (index == NULL) {
-
- return(TRUE);
- }
-
- break;
-
- case 1: /* mysql.innodb_index_stats.stat_name */
-
- ut_a(dtype_get_mtype(type) == DATA_VARMYSQL);
-
- ut_a(index != NULL);
-
- stat_name = (const char*) data;
- stat_name_len = len;
-
- break;
-
- case 2: /* mysql.innodb_index_stats.stat_value */
-
- ut_a(dtype_get_mtype(type) == DATA_INT);
- ut_a(len == 8);
-
- ut_a(index != NULL);
- ut_a(stat_name != NULL);
- ut_a(stat_name_len != ULINT_UNDEFINED);
-
- stat_value = mach_read_from_8(data);
-
- break;
-
- case 3: /* mysql.innodb_index_stats.sample_size */
-
- ut_a(dtype_get_mtype(type) == DATA_INT);
- ut_a(len == 8 || len == UNIV_SQL_NULL);
-
- ut_a(index != NULL);
- ut_a(stat_name != NULL);
- ut_a(stat_name_len != ULINT_UNDEFINED);
- ut_a(stat_value != UINT64_UNDEFINED);
-
- if (len == UNIV_SQL_NULL) {
- break;
- }
- /* else */
-
- sample_size = mach_read_from_8(data);
-
- break;
-
- default:
-
- /* someone changed
- SELECT index_name,stat_name,stat_value,sample_size
- to select more columns from innodb_index_stats without
- adjusting here */
- ut_error;
- }
- }
-
- /* if i < 4 this means someone changed the
- SELECT index_name,stat_name,stat_value,sample_size
- to select less columns from innodb_index_stats without adjusting here;
- if i > 4 we would have ut_error'ed earlier */
- ut_a(i == 4 /* index_name,stat_name,stat_value,sample_size */);
-
- ut_a(index != NULL);
- ut_a(stat_name != NULL);
- ut_a(stat_name_len != ULINT_UNDEFINED);
- ut_a(stat_value != UINT64_UNDEFINED);
- /* sample_size could be UINT64_UNDEFINED here, if it is NULL */
-
-#define PFX "n_diff_pfx"
-#define PFX_LEN 10
-
- if (stat_name_len == 4 /* strlen("size") */
- && strncasecmp("size", stat_name, stat_name_len) == 0) {
- index->stat_index_size = (ulint) stat_value;
- arg->stats_were_modified = true;
- } else if (stat_name_len == 12 /* strlen("n_leaf_pages") */
- && strncasecmp("n_leaf_pages", stat_name, stat_name_len)
- == 0) {
- index->stat_n_leaf_pages = (ulint) stat_value;
- arg->stats_were_modified = true;
- } else if (stat_name_len == 12 /* strlen("n_page_split") */
- && strncasecmp("n_page_split", stat_name, stat_name_len)
- == 0) {
- index->stat_defrag_n_page_split = (ulint) stat_value;
- arg->stats_were_modified = true;
- } else if (stat_name_len == 13 /* strlen("n_pages_freed") */
- && strncasecmp("n_pages_freed", stat_name, stat_name_len)
- == 0) {
- index->stat_defrag_n_pages_freed = (ulint) stat_value;
- arg->stats_were_modified = true;
- } else if (stat_name_len > PFX_LEN /* e.g. stat_name=="n_diff_pfx01" */
- && strncasecmp(PFX, stat_name, PFX_LEN) == 0) {
-
- const char* num_ptr;
- unsigned long n_pfx;
-
- /* point num_ptr into "1" from "n_diff_pfx12..." */
- num_ptr = stat_name + PFX_LEN;
-
- /* stat_name should have exactly 2 chars appended to PFX
- and they should be digits */
- if (stat_name_len != PFX_LEN + 2
- || num_ptr[0] < '0' || num_ptr[0] > '9'
- || num_ptr[1] < '0' || num_ptr[1] > '9') {
-
- char db_utf8[MAX_DB_UTF8_LEN];
- char table_utf8[MAX_TABLE_UTF8_LEN];
-
- dict_fs2utf8(table->name, db_utf8, sizeof(db_utf8),
- table_utf8, sizeof(table_utf8));
-
- ut_print_timestamp(stderr);
- fprintf(stderr,
- " InnoDB: Ignoring strange row from "
- "%s WHERE "
- "database_name = '%s' AND "
- "table_name = '%s' AND "
- "index_name = '%s' AND "
- "stat_name = '%.*s'; because stat_name "
- "is malformed\n",
- INDEX_STATS_NAME_PRINT,
- db_utf8,
- table_utf8,
- index->name,
- (int) stat_name_len,
- stat_name);
- return(TRUE);
- }
- /* else */
-
- /* extract 12 from "n_diff_pfx12..." into n_pfx
- note that stat_name does not have a terminating '\0' */
- n_pfx = (num_ptr[0] - '0') * 10 + (num_ptr[1] - '0');
-
- ulint n_uniq = index->n_uniq;
-
- if (n_pfx == 0 || n_pfx > n_uniq) {
-
- char db_utf8[MAX_DB_UTF8_LEN];
- char table_utf8[MAX_TABLE_UTF8_LEN];
-
- dict_fs2utf8(table->name, db_utf8, sizeof(db_utf8),
- table_utf8, sizeof(table_utf8));
-
- ut_print_timestamp(stderr);
- fprintf(stderr,
- " InnoDB: Ignoring strange row from "
- "%s WHERE "
- "database_name = '%s' AND "
- "table_name = '%s' AND "
- "index_name = '%s' AND "
- "stat_name = '%.*s'; because stat_name is "
- "out of range, the index has %lu unique "
- "columns\n",
- INDEX_STATS_NAME_PRINT,
- db_utf8,
- table_utf8,
- index->name,
- (int) stat_name_len,
- stat_name,
- n_uniq);
- return(TRUE);
- }
- /* else */
-
- index->stat_n_diff_key_vals[n_pfx - 1] = stat_value;
-
- if (sample_size != UINT64_UNDEFINED) {
- index->stat_n_sample_sizes[n_pfx - 1] = sample_size;
- } else {
- /* hmm, strange... the user must have UPDATEd the
- table manually and SET sample_size = NULL */
- index->stat_n_sample_sizes[n_pfx - 1] = 0;
- }
-
- index->stat_n_non_null_key_vals[n_pfx - 1] = 0;
-
- arg->stats_were_modified = true;
- } else {
- /* silently ignore rows with unknown stat_name, the
- user may have developed her own stats */
- }
-
- /* XXX this is not used but returning non-NULL is necessary */
- return(TRUE);
-}
-
-/*********************************************************************//**
-Read table's statistics from the persistent statistics storage.
-@return DB_SUCCESS or error code */
-static
-dberr_t
-dict_stats_fetch_from_ps(
-/*=====================*/
- dict_table_t* table) /*!< in/out: table */
-{
- index_fetch_t index_fetch_arg;
- trx_t* trx;
- pars_info_t* pinfo;
- dberr_t ret;
- char db_utf8[MAX_DB_UTF8_LEN];
- char table_utf8[MAX_TABLE_UTF8_LEN];
-
- ut_ad(!mutex_own(&dict_sys->mutex));
-
- /* Initialize all stats to dummy values before fetching because if
- the persistent storage contains incomplete stats (e.g. missing stats
- for some index) then we would end up with (partially) uninitialized
- stats. */
- dict_stats_empty_table(table, true);
-
- trx = trx_allocate_for_background();
-
- /* Use 'read-uncommitted' so that the SELECTs we execute
- do not get blocked in case some user has locked the rows we
- are SELECTing */
-
- trx->isolation_level = TRX_ISO_READ_UNCOMMITTED;
-
- trx_start_if_not_started(trx);
-
- dict_fs2utf8(table->name, db_utf8, sizeof(db_utf8),
- table_utf8, sizeof(table_utf8));
-
- pinfo = pars_info_create();
-
- pars_info_add_str_literal(pinfo, "database_name", db_utf8);
-
- pars_info_add_str_literal(pinfo, "table_name", table_utf8);
-
- pars_info_bind_function(pinfo,
- "fetch_table_stats_step",
- dict_stats_fetch_table_stats_step,
- table);
-
- index_fetch_arg.table = table;
- index_fetch_arg.stats_were_modified = false;
- pars_info_bind_function(pinfo,
- "fetch_index_stats_step",
- dict_stats_fetch_index_stats_step,
- &index_fetch_arg);
-
- ret = que_eval_sql(pinfo,
- "PROCEDURE FETCH_STATS () IS\n"
- "found INT;\n"
- "DECLARE FUNCTION fetch_table_stats_step;\n"
- "DECLARE FUNCTION fetch_index_stats_step;\n"
- "DECLARE CURSOR table_stats_cur IS\n"
- " SELECT\n"
- /* if you change the selected fields, be
- sure to adjust
- dict_stats_fetch_table_stats_step() */
- " n_rows,\n"
- " clustered_index_size,\n"
- " sum_of_other_index_sizes\n"
- " FROM \"" TABLE_STATS_NAME "\"\n"
- " WHERE\n"
- " database_name = :database_name AND\n"
- " table_name = :table_name;\n"
- "DECLARE CURSOR index_stats_cur IS\n"
- " SELECT\n"
- /* if you change the selected fields, be
- sure to adjust
- dict_stats_fetch_index_stats_step() */
- " index_name,\n"
- " stat_name,\n"
- " stat_value,\n"
- " sample_size\n"
- " FROM \"" INDEX_STATS_NAME "\"\n"
- " WHERE\n"
- " database_name = :database_name AND\n"
- " table_name = :table_name;\n"
-
- "BEGIN\n"
-
- "OPEN table_stats_cur;\n"
- "FETCH table_stats_cur INTO\n"
- " fetch_table_stats_step();\n"
- "IF (SQL % NOTFOUND) THEN\n"
- " CLOSE table_stats_cur;\n"
- " RETURN;\n"
- "END IF;\n"
- "CLOSE table_stats_cur;\n"
-
- "OPEN index_stats_cur;\n"
- "found := 1;\n"
- "WHILE found = 1 LOOP\n"
- " FETCH index_stats_cur INTO\n"
- " fetch_index_stats_step();\n"
- " IF (SQL % NOTFOUND) THEN\n"
- " found := 0;\n"
- " END IF;\n"
- "END LOOP;\n"
- "CLOSE index_stats_cur;\n"
-
- "END;",
- TRUE, trx);
- /* pinfo is freed by que_eval_sql() */
-
- trx_commit_for_mysql(trx);
-
- trx_free_for_background(trx);
-
- if (!index_fetch_arg.stats_were_modified) {
- return(DB_STATS_DO_NOT_EXIST);
- }
-
- return(ret);
-}
-
-/*********************************************************************//**
-Clear defragmentation stats modified counter for all indices in table. */
-static
-void
-dict_stats_empty_defrag_modified_counter(
- dict_table_t* table) /*!< in: table */
-{
- dict_index_t* index;
- ut_a(table);
- for (index = dict_table_get_first_index(table);
- index != NULL;
- index = dict_table_get_next_index(index)) {
- index->stat_defrag_modified_counter = 0;
- }
-}
-
-/*********************************************************************//**
-Fetches or calculates new estimates for index statistics. */
-UNIV_INTERN
-void
-dict_stats_update_for_index(
-/*========================*/
- dict_index_t* index) /*!< in/out: index */
-{
- DBUG_ENTER("dict_stats_update_for_index");
-
- ut_ad(!mutex_own(&dict_sys->mutex));
-
- if (dict_stats_is_persistent_enabled(index->table)) {
-
- if (dict_stats_persistent_storage_check(false)) {
- 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, &index->id);
- DBUG_VOID_RETURN;
- }
- /* else */
-
- if (innodb_index_stats_not_found == false &&
- index->stats_error_printed == false) {
- /* Fall back to transient stats since the persistent
- storage is not present or is corrupted */
- char buf_table[MAX_FULL_NAME_LEN];
- char buf_index[MAX_FULL_NAME_LEN];
- ut_print_timestamp(stderr);
- fprintf(stderr,
- " InnoDB: Recalculation of persistent statistics "
- "requested for table %s index %s but the required "
- "persistent statistics storage is not present or is "
- "corrupted. Using transient stats instead.\n",
- ut_format_name(index->table->name, TRUE,
- buf_table, sizeof(buf_table)),
- ut_format_name(index->name, FALSE,
- buf_index, sizeof(buf_index)));
- index->stats_error_printed = false;
- }
- }
-
- dict_table_stats_lock(index->table, RW_X_LATCH);
- dict_stats_update_transient_for_index(index);
- dict_table_stats_unlock(index->table, RW_X_LATCH);
-
- DBUG_VOID_RETURN;
-}
-
-/*********************************************************************//**
-Calculates new estimates for table and index statistics. The statistics
-are used in query optimization.
-@return DB_SUCCESS or error code */
-UNIV_INTERN
-dberr_t
-dict_stats_update(
-/*==============*/
- dict_table_t* table, /*!< in/out: table */
- dict_stats_upd_option_t stats_upd_option)
- /*!< in: whether to (re) calc
- the stats or to fetch them from
- the persistent statistics
- storage */
-{
- char buf[MAX_FULL_NAME_LEN];
-
- ut_ad(!mutex_own(&dict_sys->mutex));
-
- if (!table->is_readable()) {
- return (dict_stats_report_error(table));
- } else if (srv_force_recovery >= SRV_FORCE_NO_IBUF_MERGE) {
- /* If we have set a high innodb_force_recovery level, do
- not calculate statistics, as a badly corrupted index can
- cause a crash in it. */
- dict_stats_empty_table(table, false);
- return(DB_SUCCESS);
- }
-
- switch (stats_upd_option) {
- case DICT_STATS_RECALC_PERSISTENT:
-
- if (srv_read_only_mode) {
- goto transient;
- }
-
- /* Persistent recalculation requested, called from
- 1) ANALYZE TABLE, or
- 2) the auto recalculation background thread, or
- 3) open table if stats do not exist on disk and auto recalc
- is enabled */
-
- /* InnoDB internal tables (e.g. SYS_TABLES) cannot have
- persistent stats enabled */
- ut_a(strchr(table->name, '/') != NULL);
-
- /* check if the persistent statistics storage exists
- before calling the potentially slow function
- dict_stats_update_persistent(); that is a
- prerequisite for dict_stats_save() succeeding */
- if (dict_stats_persistent_storage_check(false)) {
-
- dberr_t err;
-
- err = dict_stats_update_persistent(table);
-
- if (err != DB_SUCCESS) {
- return(err);
- }
-
- err = dict_stats_save(table, NULL);
-
- return(err);
- }
-
- /* Fall back to transient stats since the persistent
- storage is not present or is corrupted */
-
- if (innodb_table_stats_not_found == false &&
- table->stats_error_printed == false) {
- ut_print_timestamp(stderr);
- fprintf(stderr,
- " InnoDB: Recalculation of persistent statistics "
- "requested for table %s but the required persistent "
- "statistics storage is not present or is corrupted. "
- "Using transient stats instead.\n",
- ut_format_name(table->name, TRUE, buf, sizeof(buf)));
- table->stats_error_printed = true;
- }
-
- goto transient;
-
- case DICT_STATS_RECALC_TRANSIENT:
-
- goto transient;
-
- case DICT_STATS_EMPTY_TABLE:
-
- dict_stats_empty_table(table, true);
-
- /* If table is using persistent stats,
- then save the stats on disk */
-
- if (dict_stats_is_persistent_enabled(table)) {
-
- if (dict_stats_persistent_storage_check(false)) {
-
- return(dict_stats_save(table, NULL));
- }
-
- return(DB_STATS_DO_NOT_EXIST);
- }
-
- return(DB_SUCCESS);
-
- case DICT_STATS_FETCH_ONLY_IF_NOT_IN_MEMORY:
-
- /* fetch requested, either fetch from persistent statistics
- storage or use the old method */
-
- if (table->stat_initialized) {
- return(DB_SUCCESS);
- }
-
- /* InnoDB internal tables (e.g. SYS_TABLES) cannot have
- persistent stats enabled */
- ut_a(strchr(table->name, '/') != NULL);
-
- if (!dict_stats_persistent_storage_check(false)) {
- /* persistent statistics storage does not exist
- or is corrupted, calculate the transient stats */
-
- if (innodb_table_stats_not_found == false &&
- table->stats_error_printed == false) {
- ut_print_timestamp(stderr);
- fprintf(stderr,
- " InnoDB: Error: Fetch of persistent "
- "statistics requested for table %s but the "
- "required system tables %s and %s are not "
- "present or have unexpected structure. "
- "Using transient stats instead.\n",
- ut_format_name(table->name, TRUE,
- buf, sizeof(buf)),
- TABLE_STATS_NAME_PRINT,
- INDEX_STATS_NAME_PRINT);
- table->stats_error_printed = true;
- }
-
- goto transient;
- }
-
- dict_table_t* t;
-
- /* Create a dummy table object with the same name and
- indexes, suitable for fetching the stats into it. */
- t = dict_stats_table_clone_create(table);
-
- dberr_t err = dict_stats_fetch_from_ps(t);
-
- t->stats_last_recalc = table->stats_last_recalc;
- t->stat_modified_counter = 0;
- dict_stats_empty_defrag_modified_counter(t);
-
- switch (err) {
- case DB_SUCCESS:
-
- dict_table_stats_lock(table, RW_X_LATCH);
-
- /* Pass reset_ignored_indexes=true as parameter
- to dict_stats_copy. This will cause statictics
- for corrupted indexes to be set to empty values */
- dict_stats_copy(table, t, true);
-
- dict_stats_assert_initialized(table);
-
- dict_table_stats_unlock(table, RW_X_LATCH);
-
- dict_stats_table_clone_free(t);
-
- return(DB_SUCCESS);
- case DB_STATS_DO_NOT_EXIST:
-
- dict_stats_table_clone_free(t);
-
- if (srv_read_only_mode) {
- goto transient;
- }
-
- if (dict_stats_auto_recalc_is_enabled(table)) {
- return(dict_stats_update(
- table,
- DICT_STATS_RECALC_PERSISTENT));
- }
-
- ut_format_name(table->name, TRUE, buf, sizeof(buf));
- ut_print_timestamp(stderr);
- fprintf(stderr,
- " InnoDB: Trying to use table %s which has "
- "persistent statistics enabled, but auto "
- "recalculation turned off and the statistics "
- "do not exist in %s and %s. Please either run "
- "\"ANALYZE TABLE %s;\" manually or enable the "
- "auto recalculation with "
- "\"ALTER TABLE %s STATS_AUTO_RECALC=1;\". "
- "InnoDB will now use transient statistics for "
- "%s.\n",
- buf, TABLE_STATS_NAME, INDEX_STATS_NAME, buf,
- buf, buf);
-
- goto transient;
- default:
-
- dict_stats_table_clone_free(t);
-
- if (innodb_table_stats_not_found == false &&
- table->stats_error_printed == false) {
- ut_print_timestamp(stderr);
- fprintf(stderr,
- " InnoDB: Error fetching persistent statistics "
- "for table %s from %s and %s: %s. "
- "Using transient stats method instead.\n",
- ut_format_name(table->name, TRUE, buf,
- sizeof(buf)),
- TABLE_STATS_NAME,
- INDEX_STATS_NAME,
- ut_strerr(err));
- }
-
- goto transient;
- }
- /* no "default:" in order to produce a compilation warning
- about unhandled enumeration value */
- }
-
-transient:
-
- dict_table_stats_lock(table, RW_X_LATCH);
-
- dict_stats_update_transient(table);
-
- dict_table_stats_unlock(table, RW_X_LATCH);
-
- return(DB_SUCCESS);
-}
-
-/*********************************************************************//**
-Removes the information for a particular index's stats from the persistent
-storage if it exists and if there is data stored for this index.
-This function creates its own trx and commits it.
-A note from Marko why we cannot edit user and sys_* tables in one trx:
-marko: The problem is that ibuf merges should be disabled while we are
-rolling back dict transactions.
-marko: If ibuf merges are not disabled, we need to scan the *.ibd files.
-But we shouldn't open *.ibd files before we have rolled back dict
-transactions and opened the SYS_* records for the *.ibd files.
-@return DB_SUCCESS or error code */
-UNIV_INTERN
-dberr_t
-dict_stats_drop_index(
-/*==================*/
- const char* db_and_table,/*!< in: db and table, e.g. 'db/table' */
- const char* iname, /*!< in: index name */
- char* errstr, /*!< out: error message if != DB_SUCCESS
- is returned */
- ulint errstr_sz)/*!< in: size of the errstr buffer */
-{
- char db_utf8[MAX_DB_UTF8_LEN];
- char table_utf8[MAX_TABLE_UTF8_LEN];
- pars_info_t* pinfo;
- dberr_t ret;
-
- ut_ad(!mutex_own(&dict_sys->mutex));
-
- /* skip indexes whose table names do not contain a database name
- e.g. if we are dropping an index from SYS_TABLES */
- if (strchr(db_and_table, '/') == NULL) {
-
- return(DB_SUCCESS);
- }
-
- dict_fs2utf8(db_and_table, db_utf8, sizeof(db_utf8),
- table_utf8, sizeof(table_utf8));
-
- pinfo = pars_info_create();
-
- pars_info_add_str_literal(pinfo, "database_name", db_utf8);
-
- pars_info_add_str_literal(pinfo, "table_name", table_utf8);
-
- pars_info_add_str_literal(pinfo, "index_name", iname);
-
- rw_lock_x_lock(&dict_operation_lock);
- mutex_enter(&dict_sys->mutex);
-
- ret = dict_stats_exec_sql(
- pinfo,
- "PROCEDURE DROP_INDEX_STATS () IS\n"
- "BEGIN\n"
- "DELETE FROM \"" INDEX_STATS_NAME "\" WHERE\n"
- "database_name = :database_name AND\n"
- "table_name = :table_name AND\n"
- "index_name = :index_name;\n"
- "END;\n", NULL);
-
- mutex_exit(&dict_sys->mutex);
- rw_lock_x_unlock(&dict_operation_lock);
-
- if (ret == DB_STATS_DO_NOT_EXIST) {
- ret = DB_SUCCESS;
- }
-
- if (ret != DB_SUCCESS) {
- ut_snprintf(errstr, errstr_sz,
- "Unable to delete statistics for index %s "
- "from %s%s: %s. They can be deleted later using "
- "DELETE FROM %s WHERE "
- "database_name = '%s' AND "
- "table_name = '%s' AND "
- "index_name = '%s';",
- iname,
- INDEX_STATS_NAME_PRINT,
- (ret == DB_LOCK_WAIT_TIMEOUT
- ? " because the rows are locked"
- : ""),
- ut_strerr(ret),
- INDEX_STATS_NAME_PRINT,
- db_utf8,
- table_utf8,
- iname);
-
- ut_print_timestamp(stderr);
- fprintf(stderr, " InnoDB: %s\n", errstr);
- }
-
- return(ret);
-}
-
-/*********************************************************************//**
-Executes
-DELETE FROM mysql.innodb_table_stats
-WHERE database_name = '...' AND table_name = '...';
-Creates its own transaction and commits it.
-@return DB_SUCCESS or error code */
-UNIV_INLINE
-dberr_t
-dict_stats_delete_from_table_stats(
-/*===============================*/
- const char* database_name, /*!< in: database name, e.g. 'db' */
- const char* table_name) /*!< in: table name, e.g. 'table' */
-{
- pars_info_t* pinfo;
- dberr_t ret;
-
-#ifdef UNIV_SYNC_DEBUG
- ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
-#endif /* UNIV_SYNC_DEBUG */
- ut_ad(mutex_own(&dict_sys->mutex));
-
- pinfo = pars_info_create();
-
- pars_info_add_str_literal(pinfo, "database_name", database_name);
- pars_info_add_str_literal(pinfo, "table_name", table_name);
-
- ret = dict_stats_exec_sql(
- pinfo,
- "PROCEDURE DELETE_FROM_TABLE_STATS () IS\n"
- "BEGIN\n"
- "DELETE FROM \"" TABLE_STATS_NAME "\" WHERE\n"
- "database_name = :database_name AND\n"
- "table_name = :table_name;\n"
- "END;\n", NULL);
-
- return(ret);
-}
-
-/*********************************************************************//**
-Executes
-DELETE FROM mysql.innodb_index_stats
-WHERE database_name = '...' AND table_name = '...';
-Creates its own transaction and commits it.
-@return DB_SUCCESS or error code */
-UNIV_INLINE
-dberr_t
-dict_stats_delete_from_index_stats(
-/*===============================*/
- const char* database_name, /*!< in: database name, e.g. 'db' */
- const char* table_name) /*!< in: table name, e.g. 'table' */
-{
- pars_info_t* pinfo;
- dberr_t ret;
-
-#ifdef UNIV_SYNC_DEBUG
- ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
-#endif /* UNIV_SYNC_DEBUG */
- ut_ad(mutex_own(&dict_sys->mutex));
-
- pinfo = pars_info_create();
-
- pars_info_add_str_literal(pinfo, "database_name", database_name);
- pars_info_add_str_literal(pinfo, "table_name", table_name);
-
- ret = dict_stats_exec_sql(
- pinfo,
- "PROCEDURE DELETE_FROM_INDEX_STATS () IS\n"
- "BEGIN\n"
- "DELETE FROM \"" INDEX_STATS_NAME "\" WHERE\n"
- "database_name = :database_name AND\n"
- "table_name = :table_name;\n"
- "END;\n", NULL);
-
- return(ret);
-}
-
-/*********************************************************************//**
-Removes the statistics for a table and all of its indexes from the
-persistent statistics storage if it exists and if there is data stored for
-the table. This function creates its own transaction and commits it.
-@return DB_SUCCESS or error code */
-UNIV_INTERN
-dberr_t
-dict_stats_drop_table(
-/*==================*/
- const char* db_and_table, /*!< in: db and table, e.g. 'db/table' */
- char* errstr, /*!< out: error message
- if != DB_SUCCESS is returned */
- ulint errstr_sz) /*!< in: size of errstr buffer */
-{
- char db_utf8[MAX_DB_UTF8_LEN];
- char table_utf8[MAX_TABLE_UTF8_LEN];
- dberr_t ret;
-
-#ifdef UNIV_SYNC_DEBUG
- ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
-#endif /* UNIV_SYNC_DEBUG */
- ut_ad(mutex_own(&dict_sys->mutex));
-
- /* skip tables that do not contain a database name
- e.g. if we are dropping SYS_TABLES */
- if (strchr(db_and_table, '/') == NULL) {
-
- return(DB_SUCCESS);
- }
-
- /* skip innodb_table_stats and innodb_index_stats themselves */
- if (strcmp(db_and_table, TABLE_STATS_NAME) == 0
- || strcmp(db_and_table, INDEX_STATS_NAME) == 0) {
-
- return(DB_SUCCESS);
- }
-
- dict_fs2utf8(db_and_table, db_utf8, sizeof(db_utf8),
- table_utf8, sizeof(table_utf8));
-
- ret = dict_stats_delete_from_table_stats(db_utf8, table_utf8);
-
- if (ret == DB_SUCCESS) {
- ret = dict_stats_delete_from_index_stats(db_utf8, table_utf8);
- }
-
- if (ret == DB_STATS_DO_NOT_EXIST) {
- ret = DB_SUCCESS;
- }
-
- if (ret != DB_SUCCESS) {
-
- ut_snprintf(errstr, errstr_sz,
- "Unable to delete statistics for table %s.%s: %s. "
- "They can be deleted later using "
-
- "DELETE FROM %s WHERE "
- "database_name = '%s' AND "
- "table_name = '%s'; "
-
- "DELETE FROM %s WHERE "
- "database_name = '%s' AND "
- "table_name = '%s';",
-
- db_utf8, table_utf8,
- ut_strerr(ret),
-
- INDEX_STATS_NAME_PRINT,
- db_utf8, table_utf8,
-
- TABLE_STATS_NAME_PRINT,
- db_utf8, table_utf8);
- }
-
- return(ret);
-}
-
-/*********************************************************************//**
-Executes
-UPDATE mysql.innodb_table_stats SET
-database_name = '...', table_name = '...'
-WHERE database_name = '...' AND table_name = '...';
-Creates its own transaction and commits it.
-@return DB_SUCCESS or error code */
-UNIV_INLINE
-dberr_t
-dict_stats_rename_in_table_stats(
-/*=============================*/
- const char* old_dbname_utf8,/*!< in: database name, e.g. 'olddb' */
- const char* old_tablename_utf8,/*!< in: table name, e.g. 'oldtable' */
- const char* new_dbname_utf8,/*!< in: database name, e.g. 'newdb' */
- const char* new_tablename_utf8)/*!< in: table name, e.g. 'newtable' */
-{
- pars_info_t* pinfo;
- dberr_t ret;
-
-#ifdef UNIV_SYNC_DEBUG
- ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
-#endif /* UNIV_SYNC_DEBUG */
- ut_ad(mutex_own(&dict_sys->mutex));
-
- pinfo = pars_info_create();
-
- pars_info_add_str_literal(pinfo, "old_dbname_utf8", old_dbname_utf8);
- pars_info_add_str_literal(pinfo, "old_tablename_utf8", old_tablename_utf8);
- pars_info_add_str_literal(pinfo, "new_dbname_utf8", new_dbname_utf8);
- pars_info_add_str_literal(pinfo, "new_tablename_utf8", new_tablename_utf8);
-
- ret = dict_stats_exec_sql(
- pinfo,
- "PROCEDURE RENAME_IN_TABLE_STATS () IS\n"
- "BEGIN\n"
- "UPDATE \"" TABLE_STATS_NAME "\" SET\n"
- "database_name = :new_dbname_utf8,\n"
- "table_name = :new_tablename_utf8\n"
- "WHERE\n"
- "database_name = :old_dbname_utf8 AND\n"
- "table_name = :old_tablename_utf8;\n"
- "END;\n", NULL);
-
- return(ret);
-}
-
-/*********************************************************************//**
-Executes
-UPDATE mysql.innodb_index_stats SET
-database_name = '...', table_name = '...'
-WHERE database_name = '...' AND table_name = '...';
-Creates its own transaction and commits it.
-@return DB_SUCCESS or error code */
-UNIV_INLINE
-dberr_t
-dict_stats_rename_in_index_stats(
-/*=============================*/
- const char* old_dbname_utf8,/*!< in: database name, e.g. 'olddb' */
- const char* old_tablename_utf8,/*!< in: table name, e.g. 'oldtable' */
- const char* new_dbname_utf8,/*!< in: database name, e.g. 'newdb' */
- const char* new_tablename_utf8)/*!< in: table name, e.g. 'newtable' */
-{
- pars_info_t* pinfo;
- dberr_t ret;
-
-#ifdef UNIV_SYNC_DEBUG
- ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
-#endif /* UNIV_SYNC_DEBUG */
- ut_ad(mutex_own(&dict_sys->mutex));
-
- pinfo = pars_info_create();
-
- pars_info_add_str_literal(pinfo, "old_dbname_utf8", old_dbname_utf8);
- pars_info_add_str_literal(pinfo, "old_tablename_utf8", old_tablename_utf8);
- pars_info_add_str_literal(pinfo, "new_dbname_utf8", new_dbname_utf8);
- pars_info_add_str_literal(pinfo, "new_tablename_utf8", new_tablename_utf8);
-
- ret = dict_stats_exec_sql(
- pinfo,
- "PROCEDURE RENAME_IN_INDEX_STATS () IS\n"
- "BEGIN\n"
- "UPDATE \"" INDEX_STATS_NAME "\" SET\n"
- "database_name = :new_dbname_utf8,\n"
- "table_name = :new_tablename_utf8\n"
- "WHERE\n"
- "database_name = :old_dbname_utf8 AND\n"
- "table_name = :old_tablename_utf8;\n"
- "END;\n", NULL);
-
- return(ret);
-}
-
-/*********************************************************************//**
-Renames a table in InnoDB persistent stats storage.
-This function creates its own transaction and commits it.
-@return DB_SUCCESS or error code */
-UNIV_INTERN
-dberr_t
-dict_stats_rename_table(
-/*====================*/
- const char* old_name, /*!< in: old name, e.g. 'db/table' */
- const char* new_name, /*!< in: new name, e.g. 'db/table' */
- char* errstr, /*!< out: error string if != DB_SUCCESS
- is returned */
- size_t errstr_sz) /*!< in: errstr size */
-{
- char old_db_utf8[MAX_DB_UTF8_LEN];
- char new_db_utf8[MAX_DB_UTF8_LEN];
- char old_table_utf8[MAX_TABLE_UTF8_LEN];
- char new_table_utf8[MAX_TABLE_UTF8_LEN];
- dberr_t ret;
-
-#ifdef UNIV_SYNC_DEBUG
- ut_ad(!rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
-#endif /* UNIV_SYNC_DEBUG */
- ut_ad(!mutex_own(&dict_sys->mutex));
-
- /* skip innodb_table_stats and innodb_index_stats themselves */
- if (strcmp(old_name, TABLE_STATS_NAME) == 0
- || strcmp(old_name, INDEX_STATS_NAME) == 0
- || strcmp(new_name, TABLE_STATS_NAME) == 0
- || strcmp(new_name, INDEX_STATS_NAME) == 0) {
-
- return(DB_SUCCESS);
- }
-
- dict_fs2utf8(old_name, old_db_utf8, sizeof(old_db_utf8),
- old_table_utf8, sizeof(old_table_utf8));
-
- dict_fs2utf8(new_name, new_db_utf8, sizeof(new_db_utf8),
- new_table_utf8, sizeof(new_table_utf8));
-
- rw_lock_x_lock(&dict_operation_lock);
- mutex_enter(&dict_sys->mutex);
-
- ulint n_attempts = 0;
- do {
- n_attempts++;
-
- ret = dict_stats_rename_in_table_stats(
- old_db_utf8, old_table_utf8,
- new_db_utf8, new_table_utf8);
-
- if (ret == DB_DUPLICATE_KEY) {
- dict_stats_delete_from_table_stats(
- new_db_utf8, new_table_utf8);
- }
-
- if (ret == DB_STATS_DO_NOT_EXIST) {
- ret = DB_SUCCESS;
- }
-
- if (ret != DB_SUCCESS) {
- mutex_exit(&dict_sys->mutex);
- rw_lock_x_unlock(&dict_operation_lock);
- os_thread_sleep(200000 /* 0.2 sec */);
- rw_lock_x_lock(&dict_operation_lock);
- mutex_enter(&dict_sys->mutex);
- }
- } while ((ret == DB_DEADLOCK
- || ret == DB_DUPLICATE_KEY
- || ret == DB_LOCK_WAIT_TIMEOUT)
- && n_attempts < 5);
-
- if (ret != DB_SUCCESS) {
- ut_snprintf(errstr, errstr_sz,
- "Unable to rename statistics from "
- "%s.%s to %s.%s in %s: %s. "
- "They can be renamed later using "
-
- "UPDATE %s SET "
- "database_name = '%s', "
- "table_name = '%s' "
- "WHERE "
- "database_name = '%s' AND "
- "table_name = '%s';",
-
- old_db_utf8, old_table_utf8,
- new_db_utf8, new_table_utf8,
- TABLE_STATS_NAME_PRINT,
- ut_strerr(ret),
-
- TABLE_STATS_NAME_PRINT,
- new_db_utf8, new_table_utf8,
- old_db_utf8, old_table_utf8);
- mutex_exit(&dict_sys->mutex);
- rw_lock_x_unlock(&dict_operation_lock);
- return(ret);
- }
- /* else */
-
- n_attempts = 0;
- do {
- n_attempts++;
-
- ret = dict_stats_rename_in_index_stats(
- old_db_utf8, old_table_utf8,
- new_db_utf8, new_table_utf8);
-
- if (ret == DB_DUPLICATE_KEY) {
- dict_stats_delete_from_index_stats(
- new_db_utf8, new_table_utf8);
- }
-
- if (ret == DB_STATS_DO_NOT_EXIST) {
- ret = DB_SUCCESS;
- }
-
- if (ret != DB_SUCCESS) {
- mutex_exit(&dict_sys->mutex);
- rw_lock_x_unlock(&dict_operation_lock);
- os_thread_sleep(200000 /* 0.2 sec */);
- rw_lock_x_lock(&dict_operation_lock);
- mutex_enter(&dict_sys->mutex);
- }
- } while ((ret == DB_DEADLOCK
- || ret == DB_DUPLICATE_KEY
- || ret == DB_LOCK_WAIT_TIMEOUT)
- && n_attempts < 5);
-
- mutex_exit(&dict_sys->mutex);
- rw_lock_x_unlock(&dict_operation_lock);
-
- if (ret != DB_SUCCESS) {
- ut_snprintf(errstr, errstr_sz,
- "Unable to rename statistics from "
- "%s.%s to %s.%s in %s: %s. "
- "They can be renamed later using "
-
- "UPDATE %s SET "
- "database_name = '%s', "
- "table_name = '%s' "
- "WHERE "
- "database_name = '%s' AND "
- "table_name = '%s';",
-
- old_db_utf8, old_table_utf8,
- new_db_utf8, new_table_utf8,
- INDEX_STATS_NAME_PRINT,
- ut_strerr(ret),
-
- INDEX_STATS_NAME_PRINT,
- new_db_utf8, new_table_utf8,
- old_db_utf8, old_table_utf8);
- }
-
- return(ret);
-}
-
-/*********************************************************************//**
-Save defragmentation result.
-@return DB_SUCCESS or error code */
-UNIV_INTERN
-dberr_t
-dict_stats_save_defrag_summary(
- dict_index_t* index) /*!< in: index */
-{
- dberr_t ret;
- lint now = (lint) ut_time();
- if (dict_index_is_univ(index)) {
- return DB_SUCCESS;
- }
- rw_lock_x_lock(&dict_operation_lock);
- mutex_enter(&dict_sys->mutex);
- ret = dict_stats_save_index_stat(index, now, "n_pages_freed",
- index->stat_defrag_n_pages_freed,
- NULL,
- "Number of pages freed during"
- " last defragmentation run.",
- NULL);
-
- mutex_exit(&dict_sys->mutex);
- rw_lock_x_unlock(&dict_operation_lock);
- return (ret);
-}
-
-/*********************************************************************//**
-Save defragmentation stats for a given index.
-@return DB_SUCCESS or error code */
-UNIV_INTERN
-dberr_t
-dict_stats_save_defrag_stats(
- dict_index_t* index) /*!< in: index */
-{
- dberr_t ret;
-
-
- if (index->is_readable()) {
- } else {
- return (dict_stats_report_error(index->table, true));
- }
-
- if (dict_index_is_univ(index)) {
- return DB_SUCCESS;
- }
-
- lint now = (lint) ut_time();
- mtr_t mtr;
- ulint n_leaf_pages;
- ulint n_leaf_reserved;
- mtr_start(&mtr);
- mtr_s_lock(dict_index_get_lock(index), &mtr);
- n_leaf_reserved = btr_get_size_and_reserved(index, BTR_N_LEAF_PAGES,
- &n_leaf_pages, &mtr);
- mtr_commit(&mtr);
-
- if (n_leaf_reserved == ULINT_UNDEFINED) {
- // The index name is different during fast index creation,
- // so the stats won't be associated with the right index
- // for later use. We just return without saving.
- return DB_SUCCESS;
- }
-
- rw_lock_x_lock(&dict_operation_lock);
-
- mutex_enter(&dict_sys->mutex);
- ret = dict_stats_save_index_stat(index, now, "n_page_split",
- index->stat_defrag_n_page_split,
- NULL,
- "Number of new page splits on leaves"
- " since last defragmentation.",
- NULL);
- if (ret != DB_SUCCESS) {
- goto end;
- }
-
- ret = dict_stats_save_index_stat(
- index, now, "n_leaf_pages_defrag",
- n_leaf_pages,
- NULL,
- "Number of leaf pages when this stat is saved to disk",
- NULL);
- if (ret != DB_SUCCESS) {
- goto end;
- }
-
- ret = dict_stats_save_index_stat(
- index, now, "n_leaf_pages_reserved",
- n_leaf_reserved,
- NULL,
- "Number of pages reserved for this index leaves when this stat "
- "is saved to disk",
- NULL);
-
-end:
- mutex_exit(&dict_sys->mutex);
- rw_lock_x_unlock(&dict_operation_lock);
-
- return (ret);
-}
-
-/* tests @{ */
-#ifdef UNIV_COMPILE_TEST_FUNCS
-
-/* The following unit tests test some of the functions in this file
-individually, such testing cannot be performed by the mysql-test framework
-via SQL. */
-
-/* test_dict_table_schema_check() @{ */
-void
-test_dict_table_schema_check()
-{
- /*
- CREATE TABLE tcheck (
- c01 VARCHAR(123),
- c02 INT,
- c03 INT NOT NULL,
- c04 INT UNSIGNED,
- c05 BIGINT,
- c06 BIGINT UNSIGNED NOT NULL,
- c07 TIMESTAMP
- ) ENGINE=INNODB;
- */
- /* definition for the table 'test/tcheck' */
- dict_col_meta_t columns[] = {
- {"c01", DATA_VARCHAR, 0, 123},
- {"c02", DATA_INT, 0, 4},
- {"c03", DATA_INT, DATA_NOT_NULL, 4},
- {"c04", DATA_INT, DATA_UNSIGNED, 4},
- {"c05", DATA_INT, 0, 8},
- {"c06", DATA_INT, DATA_NOT_NULL | DATA_UNSIGNED, 8},
- {"c07", DATA_INT, 0, 4},
- {"c_extra", DATA_INT, 0, 4}
- };
- dict_table_schema_t schema = {
- "test/tcheck",
- 0 /* will be set individually for each test below */,
- columns
- };
- char errstr[512];
-
- ut_snprintf(errstr, sizeof(errstr), "Table not found");
-
- /* prevent any data dictionary modifications while we are checking
- the tables' structure */
-
- mutex_enter(&(dict_sys->mutex));
-
- /* check that a valid table is reported as valid */
- schema.n_cols = 7;
- if (dict_table_schema_check(&schema, errstr, sizeof(errstr))
- == DB_SUCCESS) {
- printf("OK: test.tcheck ok\n");
- } else {
- printf("ERROR: %s\n", errstr);
- printf("ERROR: test.tcheck not present or corrupted\n");
- goto test_dict_table_schema_check_end;
- }
-
- /* check columns with wrong length */
- schema.columns[1].len = 8;
- if (dict_table_schema_check(&schema, errstr, sizeof(errstr))
- != DB_SUCCESS) {
- printf("OK: test.tcheck.c02 has different length and is "
- "reported as corrupted\n");
- } else {
- printf("OK: test.tcheck.c02 has different length but is "
- "reported as ok\n");
- goto test_dict_table_schema_check_end;
- }
- schema.columns[1].len = 4;
-
- /* request that c02 is NOT NULL while actually it does not have
- this flag set */
- schema.columns[1].prtype_mask |= DATA_NOT_NULL;
- if (dict_table_schema_check(&schema, errstr, sizeof(errstr))
- != DB_SUCCESS) {
- printf("OK: test.tcheck.c02 does not have NOT NULL while "
- "it should and is reported as corrupted\n");
- } else {
- printf("ERROR: test.tcheck.c02 does not have NOT NULL while "
- "it should and is not reported as corrupted\n");
- goto test_dict_table_schema_check_end;
- }
- schema.columns[1].prtype_mask &= ~DATA_NOT_NULL;
-
- /* check a table that contains some extra columns */
- schema.n_cols = 6;
- if (dict_table_schema_check(&schema, errstr, sizeof(errstr))
- == DB_SUCCESS) {
- printf("ERROR: test.tcheck has more columns but is not "
- "reported as corrupted\n");
- goto test_dict_table_schema_check_end;
- } else {
- printf("OK: test.tcheck has more columns and is "
- "reported as corrupted\n");
- }
-
- /* check a table that has some columns missing */
- schema.n_cols = 8;
- if (dict_table_schema_check(&schema, errstr, sizeof(errstr))
- != DB_SUCCESS) {
- printf("OK: test.tcheck has missing columns and is "
- "reported as corrupted\n");
- } else {
- printf("ERROR: test.tcheck has missing columns but is "
- "reported as ok\n");
- goto test_dict_table_schema_check_end;
- }
-
- /* check non-existent table */
- schema.table_name = "test/tcheck_nonexistent";
- if (dict_table_schema_check(&schema, errstr, sizeof(errstr))
- != DB_SUCCESS) {
- printf("OK: test.tcheck_nonexistent is not present\n");
- } else {
- printf("ERROR: test.tcheck_nonexistent is present!?\n");
- goto test_dict_table_schema_check_end;
- }
-
-test_dict_table_schema_check_end:
-
- mutex_exit(&(dict_sys->mutex));
-}
-/* @} */
-
-/* save/fetch aux macros @{ */
-#define TEST_DATABASE_NAME "foobardb"
-#define TEST_TABLE_NAME "test_dict_stats"
-
-#define TEST_N_ROWS 111
-#define TEST_CLUSTERED_INDEX_SIZE 222
-#define TEST_SUM_OF_OTHER_INDEX_SIZES 333
-
-#define TEST_IDX1_NAME "tidx1"
-#define TEST_IDX1_COL1_NAME "tidx1_col1"
-#define TEST_IDX1_INDEX_SIZE 123
-#define TEST_IDX1_N_LEAF_PAGES 234
-#define TEST_IDX1_N_DIFF1 50
-#define TEST_IDX1_N_DIFF1_SAMPLE_SIZE 500
-
-#define TEST_IDX2_NAME "tidx2"
-#define TEST_IDX2_COL1_NAME "tidx2_col1"
-#define TEST_IDX2_COL2_NAME "tidx2_col2"
-#define TEST_IDX2_COL3_NAME "tidx2_col3"
-#define TEST_IDX2_COL4_NAME "tidx2_col4"
-#define TEST_IDX2_INDEX_SIZE 321
-#define TEST_IDX2_N_LEAF_PAGES 432
-#define TEST_IDX2_N_DIFF1 60
-#define TEST_IDX2_N_DIFF1_SAMPLE_SIZE 600
-#define TEST_IDX2_N_DIFF2 61
-#define TEST_IDX2_N_DIFF2_SAMPLE_SIZE 610
-#define TEST_IDX2_N_DIFF3 62
-#define TEST_IDX2_N_DIFF3_SAMPLE_SIZE 620
-#define TEST_IDX2_N_DIFF4 63
-#define TEST_IDX2_N_DIFF4_SAMPLE_SIZE 630
-/* @} */
-
-/* test_dict_stats_save() @{ */
-void
-test_dict_stats_save()
-{
- dict_table_t table;
- dict_index_t index1;
- dict_field_t index1_fields[1];
- ib_uint64_t index1_stat_n_diff_key_vals[1];
- ib_uint64_t index1_stat_n_sample_sizes[1];
- dict_index_t index2;
- dict_field_t index2_fields[4];
- ib_uint64_t index2_stat_n_diff_key_vals[4];
- ib_uint64_t index2_stat_n_sample_sizes[4];
- dberr_t ret;
-
- /* craft a dummy dict_table_t */
- table.name = (char*) (TEST_DATABASE_NAME "/" TEST_TABLE_NAME);
- table.stat_n_rows = TEST_N_ROWS;
- table.stat_clustered_index_size = TEST_CLUSTERED_INDEX_SIZE;
- table.stat_sum_of_other_index_sizes = TEST_SUM_OF_OTHER_INDEX_SIZES;
- UT_LIST_INIT(table.indexes);
- UT_LIST_ADD_LAST(indexes, table.indexes, &index1);
- UT_LIST_ADD_LAST(indexes, table.indexes, &index2);
- ut_d(table.magic_n = DICT_TABLE_MAGIC_N);
- ut_d(index1.magic_n = DICT_INDEX_MAGIC_N);
-
- index1.name = TEST_IDX1_NAME;
- index1.table = &table;
- index1.cached = 1;
- index1.n_uniq = 1;
- index1.fields = index1_fields;
- index1.stat_n_diff_key_vals = index1_stat_n_diff_key_vals;
- index1.stat_n_sample_sizes = index1_stat_n_sample_sizes;
- index1.stat_index_size = TEST_IDX1_INDEX_SIZE;
- index1.stat_n_leaf_pages = TEST_IDX1_N_LEAF_PAGES;
- index1_fields[0].name = TEST_IDX1_COL1_NAME;
- index1_stat_n_diff_key_vals[0] = TEST_IDX1_N_DIFF1;
- index1_stat_n_sample_sizes[0] = TEST_IDX1_N_DIFF1_SAMPLE_SIZE;
-
- ut_d(index2.magic_n = DICT_INDEX_MAGIC_N);
- index2.name = TEST_IDX2_NAME;
- index2.table = &table;
- index2.cached = 1;
- index2.n_uniq = 4;
- index2.fields = index2_fields;
- index2.stat_n_diff_key_vals = index2_stat_n_diff_key_vals;
- index2.stat_n_sample_sizes = index2_stat_n_sample_sizes;
- index2.stat_index_size = TEST_IDX2_INDEX_SIZE;
- index2.stat_n_leaf_pages = TEST_IDX2_N_LEAF_PAGES;
- index2_fields[0].name = TEST_IDX2_COL1_NAME;
- index2_fields[1].name = TEST_IDX2_COL2_NAME;
- index2_fields[2].name = TEST_IDX2_COL3_NAME;
- index2_fields[3].name = TEST_IDX2_COL4_NAME;
- index2_stat_n_diff_key_vals[0] = TEST_IDX2_N_DIFF1;
- index2_stat_n_diff_key_vals[1] = TEST_IDX2_N_DIFF2;
- index2_stat_n_diff_key_vals[2] = TEST_IDX2_N_DIFF3;
- index2_stat_n_diff_key_vals[3] = TEST_IDX2_N_DIFF4;
- index2_stat_n_sample_sizes[0] = TEST_IDX2_N_DIFF1_SAMPLE_SIZE;
- index2_stat_n_sample_sizes[1] = TEST_IDX2_N_DIFF2_SAMPLE_SIZE;
- 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, NULL);
-
- ut_a(ret == DB_SUCCESS);
-
- printf("\nOK: stats saved successfully, now go ahead and read "
- "what's inside %s and %s:\n\n",
- TABLE_STATS_NAME_PRINT,
- INDEX_STATS_NAME_PRINT);
-
- printf("SELECT COUNT(*) = 1 AS table_stats_saved_successfully\n"
- "FROM %s\n"
- "WHERE\n"
- "database_name = '%s' AND\n"
- "table_name = '%s' AND\n"
- "n_rows = %d AND\n"
- "clustered_index_size = %d AND\n"
- "sum_of_other_index_sizes = %d;\n"
- "\n",
- TABLE_STATS_NAME_PRINT,
- TEST_DATABASE_NAME,
- TEST_TABLE_NAME,
- TEST_N_ROWS,
- TEST_CLUSTERED_INDEX_SIZE,
- TEST_SUM_OF_OTHER_INDEX_SIZES);
-
- printf("SELECT COUNT(*) = 3 AS tidx1_stats_saved_successfully\n"
- "FROM %s\n"
- "WHERE\n"
- "database_name = '%s' AND\n"
- "table_name = '%s' AND\n"
- "index_name = '%s' AND\n"
- "(\n"
- " (stat_name = 'size' AND stat_value = %d AND"
- " sample_size IS NULL) OR\n"
- " (stat_name = 'n_leaf_pages' AND stat_value = %d AND"
- " sample_size IS NULL) OR\n"
- " (stat_name = 'n_diff_pfx01' AND stat_value = %d AND"
- " sample_size = '%d' AND stat_description = '%s')\n"
- ");\n"
- "\n",
- INDEX_STATS_NAME_PRINT,
- TEST_DATABASE_NAME,
- TEST_TABLE_NAME,
- TEST_IDX1_NAME,
- TEST_IDX1_INDEX_SIZE,
- TEST_IDX1_N_LEAF_PAGES,
- TEST_IDX1_N_DIFF1,
- TEST_IDX1_N_DIFF1_SAMPLE_SIZE,
- TEST_IDX1_COL1_NAME);
-
- printf("SELECT COUNT(*) = 6 AS tidx2_stats_saved_successfully\n"
- "FROM %s\n"
- "WHERE\n"
- "database_name = '%s' AND\n"
- "table_name = '%s' AND\n"
- "index_name = '%s' AND\n"
- "(\n"
- " (stat_name = 'size' AND stat_value = %d AND"
- " sample_size IS NULL) OR\n"
- " (stat_name = 'n_leaf_pages' AND stat_value = %d AND"
- " sample_size IS NULL) OR\n"
- " (stat_name = 'n_diff_pfx01' AND stat_value = %d AND"
- " sample_size = '%d' AND stat_description = '%s') OR\n"
- " (stat_name = 'n_diff_pfx02' AND stat_value = %d AND"
- " sample_size = '%d' AND stat_description = '%s,%s') OR\n"
- " (stat_name = 'n_diff_pfx03' AND stat_value = %d AND"
- " sample_size = '%d' AND stat_description = '%s,%s,%s') OR\n"
- " (stat_name = 'n_diff_pfx04' AND stat_value = %d AND"
- " sample_size = '%d' AND stat_description = '%s,%s,%s,%s')\n"
- ");\n"
- "\n",
- INDEX_STATS_NAME_PRINT,
- TEST_DATABASE_NAME,
- TEST_TABLE_NAME,
- TEST_IDX2_NAME,
- TEST_IDX2_INDEX_SIZE,
- TEST_IDX2_N_LEAF_PAGES,
- TEST_IDX2_N_DIFF1,
- TEST_IDX2_N_DIFF1_SAMPLE_SIZE, TEST_IDX2_COL1_NAME,
- TEST_IDX2_N_DIFF2,
- TEST_IDX2_N_DIFF2_SAMPLE_SIZE,
- TEST_IDX2_COL1_NAME, TEST_IDX2_COL2_NAME,
- TEST_IDX2_N_DIFF3,
- TEST_IDX2_N_DIFF3_SAMPLE_SIZE,
- TEST_IDX2_COL1_NAME, TEST_IDX2_COL2_NAME, TEST_IDX2_COL3_NAME,
- TEST_IDX2_N_DIFF4,
- TEST_IDX2_N_DIFF4_SAMPLE_SIZE,
- TEST_IDX2_COL1_NAME, TEST_IDX2_COL2_NAME, TEST_IDX2_COL3_NAME,
- TEST_IDX2_COL4_NAME);
-}
-/* @} */
-
-/* test_dict_stats_fetch_from_ps() @{ */
-void
-test_dict_stats_fetch_from_ps()
-{
- dict_table_t table;
- dict_index_t index1;
- ib_uint64_t index1_stat_n_diff_key_vals[1];
- ib_uint64_t index1_stat_n_sample_sizes[1];
- dict_index_t index2;
- ib_uint64_t index2_stat_n_diff_key_vals[4];
- ib_uint64_t index2_stat_n_sample_sizes[4];
- dberr_t ret;
-
- /* craft a dummy dict_table_t */
- table.name = (char*) (TEST_DATABASE_NAME "/" TEST_TABLE_NAME);
- UT_LIST_INIT(table.indexes);
- UT_LIST_ADD_LAST(indexes, table.indexes, &index1);
- UT_LIST_ADD_LAST(indexes, table.indexes, &index2);
- ut_d(table.magic_n = DICT_TABLE_MAGIC_N);
-
- index1.name = TEST_IDX1_NAME;
- ut_d(index1.magic_n = DICT_INDEX_MAGIC_N);
- index1.cached = 1;
- index1.n_uniq = 1;
- index1.stat_n_diff_key_vals = index1_stat_n_diff_key_vals;
- index1.stat_n_sample_sizes = index1_stat_n_sample_sizes;
-
- index2.name = TEST_IDX2_NAME;
- ut_d(index2.magic_n = DICT_INDEX_MAGIC_N);
- index2.cached = 1;
- index2.n_uniq = 4;
- index2.stat_n_diff_key_vals = index2_stat_n_diff_key_vals;
- index2.stat_n_sample_sizes = index2_stat_n_sample_sizes;
-
- ret = dict_stats_fetch_from_ps(&table);
-
- ut_a(ret == DB_SUCCESS);
-
- ut_a(table.stat_n_rows == TEST_N_ROWS);
- ut_a(table.stat_clustered_index_size == TEST_CLUSTERED_INDEX_SIZE);
- ut_a(table.stat_sum_of_other_index_sizes
- == TEST_SUM_OF_OTHER_INDEX_SIZES);
-
- ut_a(index1.stat_index_size == TEST_IDX1_INDEX_SIZE);
- ut_a(index1.stat_n_leaf_pages == TEST_IDX1_N_LEAF_PAGES);
- ut_a(index1_stat_n_diff_key_vals[0] == TEST_IDX1_N_DIFF1);
- ut_a(index1_stat_n_sample_sizes[0] == TEST_IDX1_N_DIFF1_SAMPLE_SIZE);
-
- ut_a(index2.stat_index_size == TEST_IDX2_INDEX_SIZE);
- ut_a(index2.stat_n_leaf_pages == TEST_IDX2_N_LEAF_PAGES);
- ut_a(index2_stat_n_diff_key_vals[0] == TEST_IDX2_N_DIFF1);
- ut_a(index2_stat_n_sample_sizes[0] == TEST_IDX2_N_DIFF1_SAMPLE_SIZE);
- ut_a(index2_stat_n_diff_key_vals[1] == TEST_IDX2_N_DIFF2);
- ut_a(index2_stat_n_sample_sizes[1] == TEST_IDX2_N_DIFF2_SAMPLE_SIZE);
- ut_a(index2_stat_n_diff_key_vals[2] == TEST_IDX2_N_DIFF3);
- ut_a(index2_stat_n_sample_sizes[2] == TEST_IDX2_N_DIFF3_SAMPLE_SIZE);
- ut_a(index2_stat_n_diff_key_vals[3] == TEST_IDX2_N_DIFF4);
- ut_a(index2_stat_n_sample_sizes[3] == TEST_IDX2_N_DIFF4_SAMPLE_SIZE);
-
- printf("OK: fetch successful\n");
-}
-/* @} */
-
-/* test_dict_stats_all() @{ */
-void
-test_dict_stats_all()
-{
- test_dict_table_schema_check();
-
- test_dict_stats_save();
-
- test_dict_stats_fetch_from_ps();
-}
-/* @} */
-
-#endif /* UNIV_COMPILE_TEST_FUNCS */
-/* @} */
-
-#endif /* UNIV_HOTBACKUP */