/* names of the tables from the persistent statistics storage */ #define TABLE_STATS_NAME_PRINT "mysql.innodb_table_stats" #define INDEX_STATS_NAME_PRINT "mysql.innodb_index_stats"
/* 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 descendingtolowerlevelsandfetchN_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 ofkeys.Forexampleifabtreelevelis: 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, ut_allocator<ib_uint64_t> > boundaries_t;
/** Allocator type used for index_map_t. */ typedef ut_allocator<std::pair<constchar* const, dict_index_t*> >
index_map_t_allocator;
/** Auxiliary map used for sorting indexes by name in dict_stats_save(). */ typedef std::map<constchar*, dict_index_t*, ut_strcmp_functor,
index_map_t_allocator> index_map_t;
bool trx_t::has_stats_table_lock() const
{ for (const lock_t *l : lock.table_locks) if (l && l->un_member.tab_lock.table->is_stats_table()) returntrue; returnfalse;
}
/*********************************************************************//**
Checks whether an index should be ignored in stats manipulations:
* stats fetch
* stats recalc
* stats save
@returntrueif exists and all tables are ok */
UNIV_INLINE bool
dict_stats_should_ignore_index( /*===========================*/ const dict_index_t* index) /*!< in: index */
{ return !index->is_btree() || index->to_be_dropped || !index->is_committed();
}
/** expected column definition */ struct dict_col_meta_t
{ /** column name */ const LEX_CSTRING name; /** main type */ unsigned mtype; /** prtype mask; all these bits have to be set in prtype */ unsigned prtype_mask; /** column length in bytes */ unsigned len;
};
/** For checking whether a table exists and has a predefined schema */ struct dict_table_schema_t
{ /** table name */
span<constchar> table_name; /** table name in SQL */ constchar *table_name_sql; /** number of columns */ unsigned n_cols; /** columns */ const dict_col_meta_t columns[8];
};
/*********************************************************************//**
Checks whether a table exists and whether it has the given structure.
The table must have the same number of columns with the same names and
types. The order of the columns does not matter.
dict_table_schema_check() @{
@return DB_SUCCESS if the table exists and contains the necessary columns */ static
dberr_t
dict_table_schema_check( /*====================*/ const dict_table_schema_t* req_schema, /*!< in: required table
schema */ char* errstr, /*!< out: human readable error messageif!=DB_SUCCESSis
returned */
size_t errstr_sz) /*!< in: errstr size */
{ const dict_table_t* table= dict_sys.load_table(req_schema->table_name);
if (!table) { if (opt_bootstrap) return DB_STATS_DO_NOT_EXIST; if (req_schema == &table_stats_schema) { if (innodb_table_stats_not_found_reported) { return DB_STATS_DO_NOT_EXIST;
}
innodb_table_stats_not_found_reported = true;
} else {
ut_ad(req_schema == &index_stats_schema); if (innodb_index_stats_not_found_reported) { return DB_STATS_DO_NOT_EXIST;
}
innodb_index_stats_not_found = true;
innodb_index_stats_not_found_reported = true;
}
snprintf(errstr, errstr_sz, "Table %s not found.",
req_schema->table_name_sql); return DB_STATS_DO_NOT_EXIST;
}
if (!table->is_readable()) { /* table is not readable */
snprintf(errstr, errstr_sz, "Table %s is not readable.",
req_schema->table_name_sql); return DB_ERROR;
}
if (!table->space) { /* missing tablespace */
snprintf(errstr, errstr_sz, "Tablespace for table %s is missing.",
req_schema->table_name_sql); return DB_TABLE_NOT_FOUND;
}
if (unsigned(table->n_def - DATA_N_SYS_COLS) != req_schema->n_cols) { /* the table has a different number of columns than required */
snprintf(errstr, errstr_sz, "%s has %d columns but should have %u.",
req_schema->table_name_sql,
table->n_def - DATA_N_SYS_COLS,
req_schema->n_cols); return DB_ERROR;
}
/* For each column from req_schema->columns[] search whetheritispresentintable->cols[]. ThefollowingalgorithmisO(n_cols^2),butisoptimizedto
be O(n_cols) if the columns are in the same order in both arrays. */
for (unsigned i = 0; i < req_schema->n_cols; i++) {
ulint j = dict_table_has_column(
table, req_schema->columns[i].name, i);
if (j == table->n_def) {
snprintf(errstr, errstr_sz, "required column %s" " not found in table %s.",
req_schema->columns[i].name.str,
req_schema->table_name_sql);
return(DB_ERROR);
}
/* we found a column with the same name on j'th position,
compare column types and flags */
/* check length for exact match */ if (req_schema->columns[i].len != table->cols[j].len) {
snprintf(errstr, errstr_sz, "Unexpected length of %s.%s. Please run " "mariadb-upgrade or ALTER TABLE",
req_schema->table_name_sql,
req_schema->columns[i].name.str); return DB_ERROR;
}
int s = snprintf(errstr, errstr_sz, "Column %s in table %s is ",
req_schema->columns[i].name.str,
req_schema->table_name_sql); if (s < 0 || static_cast<size_t>(s) >= errstr_sz) { return DB_ERROR;
}
errstr += s;
errstr_sz -= s;
s = dtype_sql_name(table->cols[j].mtype, table->cols[j].prtype,
table->cols[j].len, errstr, errstr_sz); if (s < 0 || static_cast<size_t>(s) + sizeof" but should be "
>= errstr_sz) { return DB_ERROR;
}
errstr += s;
memcpy(errstr, " but should be ", sizeof" but should be ");
errstr += (sizeof" but should be ") - 1;
errstr_sz -= s + (sizeof" but should be ") - 1;
s = dtype_sql_name(req_schema->columns[i].mtype,
req_schema->columns[i].prtype_mask,
req_schema->columns[i].len,
errstr, errstr_sz); return DB_ERROR;
}
if (size_t n_foreign = table->foreign_set.size()) {
snprintf(errstr, errstr_sz, "Table %s has %zu foreign key(s) pointing" " to other tables, but it must have 0.",
req_schema->table_name_sql, n_foreign); return DB_ERROR;
}
if (size_t n_referenced = table->referenced_set.size()) {
snprintf(errstr, errstr_sz, "There are %zu foreign key(s) pointing to %s, " "but there must be 0.", n_referenced,
req_schema->table_name_sql); return DB_ERROR;
}
if (!dict_already_locked) {
dict_sys.lock(SRW_LOCK_CALL);
}
ut_ad(dict_sys.locked());
/* 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 (!dict_already_locked) {
dict_sys.unlock();
}
switch (ret) { case DB_SUCCESS: return SCHEMA_OK; case DB_STATS_DO_NOT_EXIST: return SCHEMA_NOT_EXIST; default: if (!opt_bootstrap) {
sql_print_error("InnoDB: %s", errstr);
} return SCHEMA_INVALID;
}
}
/** Executes a given SQL statement using the InnoDB internal SQL parser. Thisfunctionwillfreethepinfoobject. @param[in,out]pinfopinfotopasstoque_eval_sql()mustalready haveanyliteralsboundtoit @param[in]sqlSQLstringtoexecute @param[in,out]trxtransaction
@return DB_SUCCESS or error code */ static
dberr_t dict_stats_exec_sql(pars_info_t *pinfo, constchar* sql, trx_t *trx)
{
ut_ad(dict_sys.locked());
switch (dict_stats_persistent_storage_check(true)) { case SCHEMA_OK: return que_eval_sql(pinfo, sql, trx); case SCHEMA_INVALID: case SCHEMA_NOT_EXIST: break;
}
/*********************************************************************//**
Write all zeros (or1 where it makes sense) into an index
statistics members. The resulting stats correspond to an empty index. */ staticvoid dict_stats_empty_index(dict_index_t *index)
{
ut_ad(!(index->type & DICT_FTS));
ut_ad(index->table->stats_mutex_is_owner());
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;
}
/** Write all zeros (or 1 where it makes sense) into a table and its indexes' statisticsmembers.Theresultingstatscorrespondtoanemptytable.
@param table table statistics to be emptied */ void dict_stats_empty_table(dict_table_t *table)
{ /* Initialize table/index level stats is now protected by
table level lock_mutex.*/
table->stats_mutex_lock();
/* Zero the stats members */
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
= uint32_t(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)) {
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);
}
}
}
/** Statistics for one field of an index. */ struct index_field_stats_t
{
ib_uint64_t n_diff_key_vals;
ib_uint64_t n_sample_sizes;
ib_uint64_t n_non_null_key_vals;
/*******************************************************************//**
Record the number of non_null key values in a given index for
each n-column prefix of the index where 1 <= n <= dict_index_get_n_unique(index).
The estimates are eventually stored in the array:
index->stat_n_non_null_key_vals[], which is indexed from 0 to n-1. */ static void
btr_record_not_null_field_in_rec( /*=============================*/
ulint n_unique, /*!< in: dict_index_get_n_unique(index), numberofcolumnsuniquelydetermine
an index entry */ const rec_offs* offsets, /*!< in: rec_get_offsets(rec, index), itssizecouldbeforallfieldsor
that of "n_unique" */
ib_uint64_t* n_not_null) /*!< in/out: array to record number of
not null rows for n-column prefix */
{
ulint i;
ut_ad(rec_offs_n_fields(offsets) >= n_unique);
if (n_not_null == NULL) { return;
}
for (i = 0; i < n_unique; i++) { if (rec_offs_nth_sql_null(offsets, i)) { break;
}
/* Check srv_innodb_stats_method setting, and decide whether we needtorecordnon-nullvalueandalsodecideifNULLis
considered equal (by setting stats_null_not_equal value) */ switch (srv_innodb_stats_method) { case SRV_STATS_NULLS_IGNORED:
n_not_null = (ib_uint64_t*) mem_heap_zalloc(
heap, n_cols * sizeof *n_not_null); /* fall through */
case SRV_STATS_NULLS_UNEQUAL: /* for both SRV_STATS_NULLS_IGNORED and SRV_STATS_NULLS_UNEQUAL
case, we will treat NULLs as unequal value */
stats_null_not_equal = true; break;
case SRV_STATS_NULLS_EQUAL:
stats_null_not_equal = false; break;
default:
ut_error;
}
if (srv_stats_sample_traditional) { /* It makes no sense to test more pages than are contained
in the index, thus we lower the number if it is too high */ if (srv_stats_transient_sample_pages > index->stat_index_size) { if (index->stat_index_size > 0) {
n_sample_pages = index->stat_index_size;
}
} else {
n_sample_pages = srv_stats_transient_sample_pages;
}
} else { /* New logarithmic number of pages that are estimated. Numberofpagesestimatedshouldbebetween1and index->stat_index_size.
/* Count the number of different key values for each prefix of thekeyonthisindexpage.Iftheprefixdoesnotdetermine theindexrecorduniquelyintheB-tree,thenwesubtractone becauseotherwiseouralgorithmwouldgiveawrongestimate
for an index where there is just one key value. */
rec = next_rec; /* Initialize offsets_rec for the next round andassigntheoldoffsets_recbufferto
offsets_next_rec. */
{
rec_offs* offsets_tmp = offsets_rec;
offsets_rec = offsets_next_rec;
offsets_next_rec = offsets_tmp;
}
}
if (n_cols == dict_index_get_n_unique_in_tree(index)
&& page_has_siblings(page)) {
/* If there is more than one leaf page in the tree, weaddonebecauseweknowthatthefirstrecord onthepagecertainlyhadadifferentprefixthanthe lastrecordonthepreviousindexpageinthe alphabeticalorder.Beforethisfix,iftherewas justonebigrecordoneachclusteredindexpage,the algorithmgrosslyunderestimatedthenumberofrows
in the table. */
n_diff[n_cols - 1]++;
}
mtr.commit();
}
exit_loop: /* If we saw k borders between different key values on n_sample_pagesleafpages,wecanestimatehowmany
there will be in index->stat_n_leaf_pages */
/* We must take into account that our sample actually represents alsothepagesusedforexternalstorageoffields(thosepagesare
included in index->stat_n_leaf_pages) */
/* If the tree is small, smaller than 10*n_sample_pages+total_external_size,then theaboveestimateisok.Forbiggertreesitiscommonthatwe donotseeanybordersbetweenkeyvaluesinthefewpages wepick.Butstilltheremayben_sample_pages differentkeyvalues,orevenmore.Letustrytoapproximate
that: */
/*********************************************************************//**
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.
@param trx transaction
@param index B-tree
@return error code
@retval DB_SUCCESS_LOCKED_REC if the table under bulk insert operation */ static dberr_t
dict_stats_update_transient_for_index(trx_t *trx, dict_index_t* index) noexcept
{
dberr_t err = DB_SUCCESS; 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,donotcalculatestatistics,asabadly corruptedindexcancauseacrashinit. Initializesomebogusindexcardinality statistics,sothatthedatacanbequeriedin
various means, also via secondary indexes. */
dummy_empty:
index->table->stats_mutex_lock();
dict_stats_empty_index(index);
index->table->stats_mutex_unlock(); return err;
} elseif (dict_index_is_online_ddl(index) || !index->is_committed()
|| !index->is_btree()
|| !index->table->space) { goto dummy_empty;
} else {
mtr_t mtr{trx};
/* Do not continue if table decryption has failed or
table is already marked as corrupted. */ if (index->is_readable()) {
std::vector<index_field_stats_t> stats
= btr_estimate_number_of_different_key_vals(
index, bulk_trx_id);
if (!stats.empty()) {
index->table->stats_mutex_lock(); for (size_t i = 0; i < stats.size(); ++i) {
index->stat_n_diff_key_vals[i]
= stats[i].n_diff_key_vals;
index->stat_n_sample_sizes[i]
= stats[i].n_sample_sizes;
index->stat_n_non_null_key_vals[i]
= stats[i].n_non_null_key_vals;
}
index->table->stats_mutex_unlock();
}
}
}
/** Open a cursor at the first page in a tree level. @parampage_curcursor @paramlevelleveltosearchfor(0=leaf)
@param mtr mini-transaction */ static dberr_t page_cur_open_level(page_cur_t *page_cur, ulint level,
mtr_t *mtr)
{
mem_heap_t *heap= nullptr;
rec_offs offsets_[REC_OFFS_NORMAL_SIZE];
rec_offs *offsets= offsets_;
dberr_t err;
/* Release all page latches except the one on the desired page. */ constauto end= mtr->get_savepoint(); if (end > 1)
mtr->rollback_to_savepoint(1, end - 1);
return err;
}
/** Open a cursor at the first page in a tree level. @parampage_curcursor @paramlevelleveltosearchfor(0=leaf) @parammtrmini-transaction
@param index index tree */ static dberr_t btr_pcur_open_level(btr_pcur_t *pcur, ulint level, mtr_t *mtr,
dict_index_t *index)
{
pcur->latch_mode= BTR_SEARCH_LEAF;
pcur->search_mode= PAGE_CUR_G;
pcur->pos_state= BTR_PCUR_IS_POSITIONED;
pcur->btr_cur.page_cur.index= index; return page_cur_open_level(&pcur->btr_cur.page_cur, level, mtr);
}
/* @{ Pseudo code about the relation between the following functions
letN=N_SAMPLE_PAGES(index)
dict_stats_analyze_index() foreachn_prefix searchforgoodenoughlevel: dict_stats_analyze_index_level()// only called if level has <= N pages // full scan of the level in one mtr collectstatisticsaboutthegivenlevel ifwearenotsatisfiedwiththelevel,searchnextlowerlevel wehavefoundagoodenoughlevelhere dict_stats_analyze_index_for_n_prefix(thatlevel,statscollectedabove) // full scan of the level in one mtr divebelowsomerecordsandanalyzetheleafpagethere: 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;
rec_offs* rec_offsets;
rec_offs* prev_rec_offsets;
ulint i;
/* 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]andtheREC_OFFS_HEADER_SIZEbytes),andn_uniq+1, sothatthiswillneverbelessthanthesizecalculatedin
rec_get_offsets_func(). */
i = (REC_OFFS_HEADER_SIZE + 1 + 1) + n_uniq;
/* 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));
prev_rec = NULL;
prev_rec_is_copied = false;
if (REC_INFO_MIN_REC_FLAG & rec_get_info_bits(
btr_pcur_get_rec(&pcur), page_is_comp(page))) {
ut_ad(btr_pcur_is_on_user_rec(&pcur)); if (level == 0) { /* Skip the metadata pseudo-record */
ut_ad(index->is_instant());
btr_pcur_move_to_next_user_rec(&pcur, mtr);
}
} elseif (UNIV_UNLIKELY(level != 0)) { /* The first record on the leftmost page must be
marked as such on each level except the leaf level. */ goto func_exit;
}
/* iterate over all user records on this level andcompareeachtwoadjacentones,eventhelastonpage
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)) {
bool rec_is_last_on_page;
rec = btr_pcur_get_rec(&pcur);
/* If rec and prev_rec are on different pages, then prev_rec musthavebeencopied,becauseweholdlatchonlyonthepage
where rec resides. */
ut_ad(!prev_rec
|| btr_pcur_get_page(&pcur) == page_align(prev_rec)
|| prev_rec_is_copied);
/* 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 donotskipthem,thenANALYZEquicklyafterDELETE couldcountthemornot(purgemayhavealreadywiped themaway)whichbringsnon-determinism.Weskiponly leaf-leveldeletemarksbecausedeletemarkson
non-leaf level do not make sense. */
if (n_diff_boundaries != NULL) { /* push the index of the previous record,thatis-thelastonefrom
a group of equal keys */
ib_uint64_t idx;
/* the index of the current record istotal_recs-1,theindexofthe previousrecordistotal_recs-2; weknowthatidxisnotgoingto becomenegativeherebecauseifwe areinthisbranchthenthereisa previousrecordandthus
total_recs >= 2 */
idx = *total_recs - 2;
n_diff_boundaries[i].push_back(idx);
}
/* increment the number of different keys forn_prefix=i+1(e.g.ifi=0thenweincrement
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 likeprev_rec=rec;becausewhenwetraversethe recordsonthislevelatsomepointwewilljumpfrom onepagetothenextandthenrecandprev_recwill beondifferentpagesand btr_cur_move_to_next_user_rec()willreleasethe
latch on the page that prev_rec is on */
prev_rec = rec_copy_prefix_to_buf(
rec, index, n_uniq,
&prev_rec_buf, &prev_rec_buf_size);
prev_rec_is_copied = true;
} else { /* still on the same page, the next call to btr_cur_move_to_next_user_rec()willnotjump onthenextpage,wecansimplyassignpointers
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 enteredatallandthereisonepageinthewholetreewhichis emptyortheloopwasenteredbutthisislevel0,containsonepage
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 lastonefromthelastgroupofequalkeys;thisholdsfor
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[" ULINTPF "]: " UINT64PF "\n",
__func__, *total_recs,
*total_pages,
i, n_diff[i]);
#if0 if (n_diff_boundaries != NULL) {
ib_uint64_t j;
/************************************************************//**
Gets the pointer to the next non delete-marked record on the page. If all subsequent records are delete-marked, then this function
will return the supremum record.
@return pointer to next non delete-marked record or pointer to supremum */ template<bool comp> static const rec_t*
page_rec_get_next_non_del_marked(const page_t *page, const rec_t *rec)
{
ut_ad(!!page_is_comp(page) == comp);
ut_ad(page_align(rec) == page);
if (should_count_external_pages) {
*n_external_pages = 0;
}
rec = get_next(page, page_get_infimum_rec(page));
if (!rec || rec == page_get_supremum_rec(page)) { /* the page is empty or contains only delete-marked records */
*n_diff = 0;
*out_rec = NULL; return(NULL);
}
if (should_count_external_pages) {
*n_external_pages += btr_rec_get_externally_stored_len(
rec, offsets_rec);
}
next_rec = get_next(page, next_rec);
}
/* 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 distinctrecordsontheleafpage,whenlookingatthefistn_prefix columns.Alsocalculatethenumberofexternalpagespointedbyrecords ontheleafpage. @param[in]curcursor @param[in,out]mtrmini-transaction @param[in]n_prefixlookatthefirstn_prefixcolumns whencomparingrecords @param[out]n_diffnumberofdistinctrecords @param[out]n_external_pagesnumberofexternalpages
@return number of distinct records on the leaf page */ static void
dict_stats_analyze_index_below_cur( const btr_cur_t* cur,
mtr_t* mtr,
ulint n_prefix,
ib_uint64_t* n_diff,
ib_uint64_t* n_external_pages)
{
dict_index_t* index;
buf_block_t* block; const page_t* page;
mem_heap_t* heap; const rec_t* rec;
rec_offs* offsets1;
rec_offs* offsets2;
rec_offs* offsets_rec;
ulint size;
constauto sp = mtr->get_savepoint();
index = btr_cur_get_index(cur);
/* Allocate offsets for the record and the node pointer, for nodepointerrecords.Inasecondaryindex,thenodepointer recordwillconsistofallindexfieldsfollowedbyachild pagenumber. Allocatespacefortheoffsetsheader(theallocationsizeat offsets[0]andtheREC_OFFS_HEADER_SIZEbytes),andn_fields+1, sothatthiswillneverbelessthanthesizecalculatedin
rec_get_offsets_func(). */
size = (1 + REC_OFFS_HEADER_SIZE) + 1 + dict_index_get_n_fields(index);
/* search for the first non-boring record on the page */
offsets_rec = dict_stats_scan_page(
&rec, offsets1, offsets2, index, page, n_prefix, 0, 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) { /* page has all keys equal and the end of the page wasreachedbydict_stats_scan_page(),noneedto
descend to the leaf level */ /* can't get an estimate for n_external_pages here becausewedonotdivetotheleaflevel,assumeno externalpages(*n_external_pageswasassignedto0
above). */ goto func_exit;
} /* else */
/* when we instruct dict_stats_scan_page() to quit on the firstnon-boringrecorditfinds,thenthereturnedn_diff caneitherbe0(emptypage),1(pagehasallkeysequal)or
2 (non-boring record was found) */
ut_a(*n_diff == 2);
/* we have a non-boring record in rec, descend below it */
/* 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, whenlookingonlyatthefirstn_prefixcolumns;alsoestimate thenumberofexternallystoredpagespointedbyrecordsonthis
page */
/** 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.level0istheleaflevel.Thisis>=1becausewe avoidscanningtheleaflevelbecauseitmaycontaintoomany pagesanddoingsoisuselesswhencombinedwiththerandomdives- ifwearetoscantheleaflevel,thismeansafullscanandwecan simplydothatinsteadoffiddlingwithpickingrandomrecordshigher inthetreeandtodivebelowthem.Atthestartoftheanalyzing wemaydecidetodofullscanoftheleaflevel,butthenthis
structure is not used in that code path. */
ulint level;
/** Number of records on the level where the descend through the btree stopped.Whenwescanthebtreefromtheroot,westopatsomemid level,choosesomerecordsfromitanddivebelowthemtowardsaleaf
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 thenumberofrecordsthatwepickfromthemidlevelanddivebelow
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 thefirstn_prefixcolumns.Foragivenlevelinanindexselect n_diff_data->n_leaf_pages_to_analyzerecordsfromthatlevelanddivebelow themtothecorrespondingleafpages,thenscanthoseleafpagesandsavethe samplingresultsinn_diff_data->n_diff_all_analyzed_pages. @param[in]indexindex @param[in]n_prefixlookatfirst'n_prefix'columnswhen comparingrecords @param[in]boundariesavectorthatcontains n_diff_data->n_diff_on_levelintegerseachofwhichrepresentstheindex(on level'level',countingfromleft/smallesttoright/biggestfrom0)ofthe lastrecordfromeachgroupofdistinctkeys @param[in,out]n_diff_datan_diff_all_analyzed_pagesand n_external_pages_suminthisstructurewillbesetbythisfunction.The memberslevel,n_diff_on_levelandn_leaf_pages_to_analyzemustbesetbythe callerinadvance-theyareusedbysomecalculationsinsidethisfunction
@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;
/* The page must not be empty, except when
it is the root page (and the whole index is empty). */ if (page_has_prev(page)
|| !btr_pcur_is_on_user_rec(&pcur)
|| btr_page_get_level(page) != n_diff_data->level
|| first_rec != page_rec_get_next_const(page_get_infimum_rec(page))
|| !(rec_get_info_bits(first_rec, page_is_comp(page))
& REC_INFO_MIN_REC_FLAG)) { return;
}
for (i = 0; i < n_diff_data->n_leaf_pages_to_analyze; i++) { /* there are n_diff_on_level elements in'boundaries'andwedividethoseelements inton_leaf_pages_to_analyzesegments,forexample:
/* if the level has finished before the record we are searchingfor,thismeansthattheB-treehaschangedin themeantime,quitoursamplingandusewhateverstats
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 therecordunderdive_below_idxisthesupremumrecord,in thiscaserec_idx==dive_below_idxandpcurispositioned
on the supremum, we do not want to dive below it */ if (!btr_pcur_is_on_user_rec(&pcur)) { break;
}
/* We adjust n_diff_on_leaf_page here to avoid counting onevaluetwice-onceasthelastonsomepageandonce asthefirstonanotherpage.Considerthefollowingexample: Leaflevel: page:(2,2,2,2,3,3) ...manypageslike(3,3,3,3,3,3)... page:(3,3,3,3,5,5) ...manypageslike(5,5,5,5,5,5)... page:(5,5,5,5,8,8) page:(8,8,8,8,9,9) ouralgowould(correctly)getanestimatethatthereare 2distinctrecordsperpage(average).Having4pagesbelow non-boringrecords,itwould(wrongly)estimatethenumber
of distinct records to 8. */ if (n_diff_on_leaf_page > 0) {
n_diff_on_leaf_page--;
}
bool is_bulk_operation() const
{ for (auto &s : stats) if (!s.is_bulk_operation()) returnfalse; returntrue;
}
};
/** Set dict_index_t::stat_n_diff_key_vals[] and stat_n_sample_sizes[]. @param[in]n_diff_datainputdatatousetoderivetheresults
@param[in,out] index_stats index stats to set */
UNIV_INLINE void
dict_stats_index_set_n_diff( const n_diff_data_t* n_diff_data,
index_stats_t& index_stats)
{ for (ulint n_prefix = index_stats.stats.size();
n_prefix >= 1;
n_prefix--) { /* n_diff_all_analyzed_pages can be 0 here if alltheleafpagessampledcontainedonly delete-markedrecords.Inthiscaseweshouldassign 0toindex->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];
if (data->level == 1) { /* If we know the number of records on level 1, then thisnumberisthesameasthenumberofpageson
level 0 (leaf). */
n_ordinary_leaf_pages = data->n_recs_on_level;
} else { /* If we analyzed D ordinary leaf pages and found E externalpagesintotallinkedfromthoseDordinary leafpages,thenthismeansthattheratio ordinary/externalisD/E.Thentheratioordinary/total isD/(D+E).Knowingthatthetotalnumberofpages isT(includingordinaryandexternal)thenweestimate thatthetotalnumberofordinaryleafpagesis
T * D / (D + E). */
n_ordinary_leaf_pages
= index_stats.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_stats.stats[n_prefix - 1].n_diff_key_vals
= n_ordinary_leaf_pages
/** Calculates new statistics for a given index and saves them to the index membersstat_n_diff_key_vals[],stat_n_sample_sizes[],stat_index_sizeand stat_n_leaf_pages.Thisfunctioncanbeslow. @param[in]indexindextoanalyze
@return index stats */ static index_stats_t dict_stats_analyze_index(trx_t *trx, dict_index_t* index)
{ bool level_is_analyzed;
ulint n_uniq;
ulint n_prefix;
ib_uint64_t total_recs;
ib_uint64_t total_pages;
mtr_t mtr{trx};
index_stats_t result(index->n_uniq);
DBUG_ENTER("dict_stats_analyze_index");
/* If the tree has just one level (and one page) or if the user hasrequestedtosampletoomanypagesthendofullscan.
Foreachn-columnprefix(forn=1..n_uniq)N_SAMPLE_PAGES(index) willbesampled,sointotalN_SAMPLE_PAGES(index)*n_uniqleaf pageswillbesampled.Ifthatnumberisbiggerthanthetotal numberofleafpagesthendofullscanoftheleaflevelinstead
since it will be faster and will give better results. */
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 */
index->table->stats_mutex_lock(); for (ulint i = 0; i < n_uniq; i++) {
result.stats[i].n_diff_key_vals = index->stat_n_diff_key_vals[i];
result.stats[i].n_sample_sizes = total_pages;
result.stats[i].n_non_null_key_vals = index->stat_n_non_null_key_vals[i];
}
result.n_leaf_pages = index->stat_n_leaf_pages;
index->table->stats_mutex_unlock();
DBUG_RETURN(result);
}
/* 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 = UT_NEW_ARRAY(
ib_uint64_t, n_uniq, mem_key_dict_stats_n_diff_on_level);
/* For each level that is being scanned in the btree, this contains the indexofthelastrecordfromeachgroupofequalrecords(when
comparing only the first n columns, n=1..n_uniq). */
boundaries_t* n_diff_boundaries = UT_NEW_ARRAY_NOKEY(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 = UT_NEW_ARRAY_NOKEY(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: IfwefindthatlevelListhefirstone(searchingfromthe root)thatcontainsatleastDdistinctkeyswhenlookingat thefirstn_prefixcolumns,then: ifwelookatthefirstn_prefix-1columnsthenthefirst levelthatcontainsDdistinctkeyswillbeeitherLora lowerone. SoifwefindthatthefirstlevelcontainingDdistinct keys(onn_prefixcolumns)isL,wecontinuefromLwhen
searching for D distinct keys on n_prefix-1 columns. */ auto level = root_level;
level_is_analyzed = false;
for (n_prefix = n_uniq; n_prefix >= 1; n_prefix--) {
/* Commit the mtr to release the tree S lock to allow
other threads to do some work too. */
mtr.commit();
mtr.start();
mtr_sx_lock_index(index, &mtr);
ut_ad(mtr.get_savepoint() == 1);
buf_block_t *root = btr_root_block_get(index, RW_S_LATCH,
&mtr, &err); if (!root || root_level != btr_page_get_level(root->page.frame)
|| index->table->bulk_trx_id != bulk_trx_id) { /* Just quit if the tree has changed beyond recognitionhere.Theoldstatsfromprevious runswillremaininthevaluesthatwehave notcalculatedyet.Initiallywhentheindex objectiscreatedthestatsmembersaregiven somesensiblevaluessoleavingthemuntouched hereeventhefirsttimewillnotcauseusto
read uninitialized memory later. */ break;
}
mtr.rollback_to_savepoint(1);
/* check whether we should pick the current level; wepicklevel1evenifitdoesnothaveenough distinctrecordsbecausewedonotwanttoscanthe
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. Hereweusethefollowingfact: thenumberofpagesonlevelLequalsthenumber ofrecordsonlevelL+1,thuswededucethatthe followingcallwouldscantotal_recspages,because total_recsisleftfromthepreviousiterationwhen wescannedonelevelupperorwehavenotscannedany
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 notattherootlevelsinceontheroot leveltotal_recs==1(setbeforewe enterthen-prefixloop)andcannot
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;
if (level == 1
|| n_diff_on_level[n_prefix - 1]
>= N_DIFF_REQUIRED(index)) { /* we have reached the last level we could scan orwefoundagoodlevelwithmanydistinct
records */ break;
}
DEBUG_PRINTF(" %s(): found level " ULINTPF " that has " UINT64PF " distinct records for n_prefix=" ULINTPF "\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_REQUIREDdistinctkeysorwedidnotscan
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-treeanditisemptyandweshouldhavedonefullscanand
should not be here */
ut_ad(total_recs > 0);
ut_ad(n_diff_on_level[n_prefix - 1] > 0);
/* 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, result);
}
if (UNIV_UNLIKELY(ret != DB_SUCCESS)) { if (innodb_index_stats_not_found == false
&& !index->table->stats_error_printed) {
index->table->stats_error_printed = true;
ib::error() << "Cannot save index statistics for table "
<< index->table->name
<< ", index " << index->name
<< ", stat name \"" << stat_name << "\": "
<< ret;
}
}
return(ret);
}
/** Report an error if updating table statistics failed because .ibdfileismissing,tabledecryptionfailedortableiscorrupted. @param[in,out]tableTable @retvalDB_DECRYPTION_FAILEDifdecryptionofthetablefailed @retvalDB_TABLESPACE_DELETEDif.ibdfileismissing
@retval DB_CORRUPTION if table is marked as corrupted */ static dberr_t dict_stats_report_error(dict_table_t* table)
{
dberr_t err;
if (!table->space) {
ib::warn() << "Cannot save statistics for table "
<< table->name
<< " because the .ibd file is missing. "
<< TROUBLESHOOTING_MSG;
err = DB_TABLESPACE_DELETED;
} else {
ib::warn() << "Cannot save statistics for table "
<< table->name
<< " because file "
<< table->space->chain.start->name
<< (table->corrupted
? " is corrupted."
: " cannot be decrypted.");
err = table->corrupted ? DB_CORRUPTION : DB_DECRYPTION_FAILED;
}
dict_stats_empty_table(table); return err;
}
/** Save the persistent statistics of a table or an index. @paramtabletablewhosestatstosave @paramonly_for_indextheindexIDtosavestatisticsfor(0=all)
@return DB_SUCCESS or error code */
dberr_t dict_stats_save(dict_table_t* table, index_id_t index_id)
{
pars_info_t* pinfo; char db_utf8[MAX_DB_UTF8_LEN]; char table_utf8[MAX_TABLE_UTF8_LEN];
THD* const thd = current_thd;
/* Below we do all the modifications in innodb_index_stats in a single transactionforperformancereasons.Modifyingmorethanonerowina singletransactionmaydeadlockwithothertransactionsifthey locktherowsindifferentorder.Othertransactioncouldbefor examplewhenweDROPatableanddo DELETEFROMinnodb_index_statsWHEREdatabase_name='...' ANDtable_name='...';whichwillaffectmorethanonerow.To preventdeadlockswealwayslocktherowsinthesameorder-the orderofthePK,whichis(database_name,table_name,index_name, stat_name).Thisiswhybelowwesorttheindexesbynameandthen
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) {
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 rollback_and_exit;
}
}
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 rollback_and_exit;
}
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 rollback_and_exit;
}
}
ret= trx->bulk_insert_apply(); if (ret != DB_SUCCESS) { goto rollback_and_exit;
}
/*********************************************************************//**
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));
/* if i < 3 this means someone changed the SELECTn_rows,clustered_index_size,sum_of_other_index_sizes toselectlesscolumnsfrominnodb_table_statswithoutadjustinghere;
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; constchar* 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));
/* search for index in table's indexes whose name matchesdata;thefetchedindexnameisindata,
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 (index->is_committed()
&& 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_statscontainsmorerowsthanthe numberofindexesinthetable;thisisok,wejust returnignoringthoseextrarows;inotherwords dict_stats_fetch_index_stats_step()hasbeencalled forarowfromindex_statswithunknownindex_name
column */ if (index == NULL) {
/* if i < 4 this means someone changed the SELECTindex_name,stat_name,stat_value,sample_size toselectlesscolumnsfrominnodb_index_statswithoutadjustinghere;
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 */
/* 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') {
ib::info out;
out << "Ignoring strange row from "
<< INDEX_STATS_NAME_PRINT << " WHERE" " database_name = '" << db_utf8
<< "' AND table_name = '" << table_utf8
<< "' AND index_name = '" << index->name()
<< "' AND stat_name = '";
out.write(stat_name, stat_name_len);
out << "'; because stat_name is malformed"; return(TRUE);
} /* else */
/* extract 12 from "n_diff_pfx12..." into n_pfx
note that stat_name does not have a terminating '\0' */
n_pfx = ulong(num_ptr[0] - '0') * 10 + ulong(num_ptr[1] - '0');
if (sample_size != UINT64_UNDEFINED) {
index->stat_n_sample_sizes[n_pfx - 1] =
std::max<ib_uint64_t>(sample_size, 1);
} 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 the stored persistent statistics of a table. */
dberr_t dict_stats_fetch_from_ps(dict_table_t *table)
{
index_fetch_t index_fetch_arg;
pars_info_t* pinfo; char db_utf8[MAX_DB_UTF8_LEN]; char table_utf8[MAX_TABLE_UTF8_LEN];
/* Initialize all stats to dummy values before fetching because if thepersistentstoragecontainsincompletestats(e.g.missingstats forsomeindex)thenwewouldendupwith(partially)uninitialized
stats. */
dict_stats_empty_table(table);
/* Delete the stats only if renaming the table from old table to
intermediate table during COPY algorithm */ if (dict_table_t::is_temporary_name(new_name))
{ if (dberr_t e= dict_stats_delete_from_table_stats(old_db, old_table, trx)) return e; return dict_stats_delete_from_index_stats(old_db, old_table, trx);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.