summaryrefslogtreecommitdiff
path: root/storage/innobase/dict
diff options
context:
space:
mode:
Diffstat (limited to 'storage/innobase/dict')
-rw-r--r--storage/innobase/dict/dict0boot.cc49
-rw-r--r--storage/innobase/dict/dict0crea.cc577
-rw-r--r--storage/innobase/dict/dict0dict.cc1488
-rw-r--r--storage/innobase/dict/dict0load.cc857
-rw-r--r--storage/innobase/dict/dict0mem.cc195
-rw-r--r--storage/innobase/dict/dict0stats.cc2841
-rw-r--r--storage/innobase/dict/dict0stats_bg.cc392
7 files changed, 4542 insertions, 1857 deletions
diff --git a/storage/innobase/dict/dict0boot.cc b/storage/innobase/dict/dict0boot.cc
index 8e305364ac8..eea10759fcd 100644
--- a/storage/innobase/dict/dict0boot.cc
+++ b/storage/innobase/dict/dict0boot.cc
@@ -1,6 +1,6 @@
/*****************************************************************************
-Copyright (c) 1996, 2011, Oracle and/or its affiliates. All Rights Reserved.
+Copyright (c) 1996, 2012, Oracle and/or its affiliates. All Rights Reserved.
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
@@ -241,9 +241,10 @@ dict_hdr_create(
/*****************************************************************//**
Initializes the data dictionary memory structures when the database is
-started. This function is also called when the data dictionary is created. */
+started. This function is also called when the data dictionary is created.
+@return DB_SUCCESS or error code. */
UNIV_INTERN
-void
+dberr_t
dict_boot(void)
/*===========*/
{
@@ -252,7 +253,7 @@ dict_boot(void)
dict_hdr_t* dict_hdr;
mem_heap_t* heap;
mtr_t mtr;
- ulint error;
+ dberr_t error;
/* Be sure these constants do not ever change. To avoid bloat,
only check the *NUM_FIELDS* in each table */
@@ -307,9 +308,7 @@ dict_boot(void)
dict_mem_table_add_col(table, heap, "ID", DATA_BINARY, 0, 0);
/* ROW_FORMAT = (N_COLS >> 31) ? COMPACT : REDUNDANT */
dict_mem_table_add_col(table, heap, "N_COLS", DATA_INT, 0, 4);
- /* If the format is UNIV_FORMAT_A, table->flags == 0, and
- TYPE == 1, which is defined as SYS_TABLE_TYPE_ANTELOPE.
- The low order bit of TYPE is always set to 1. If the format
+ /* The low order bit of TYPE is always set to 1. If the format
is UNIV_FORMAT_B or higher, this field matches table->flags. */
dict_mem_table_add_col(table, heap, "TYPE", DATA_INT, 0, 4);
dict_mem_table_add_col(table, heap, "MIX_ID", DATA_BINARY, 0, 0);
@@ -454,14 +453,27 @@ dict_boot(void)
ibuf_init_at_db_start();
- /* Load definitions of other indexes on system tables */
+ dberr_t err = DB_SUCCESS;
+
+ if (srv_read_only_mode && !ibuf_is_empty()) {
+
+ ib_logf(IB_LOG_LEVEL_ERROR,
+ "Change buffer must be empty when --innodb-read-only "
+ "is set!");
- dict_load_sys_table(dict_sys->sys_tables);
- dict_load_sys_table(dict_sys->sys_columns);
- dict_load_sys_table(dict_sys->sys_indexes);
- dict_load_sys_table(dict_sys->sys_fields);
+ err = DB_ERROR;
+ } else {
+ /* Load definitions of other indexes on system tables */
+
+ dict_load_sys_table(dict_sys->sys_tables);
+ dict_load_sys_table(dict_sys->sys_columns);
+ dict_load_sys_table(dict_sys->sys_indexes);
+ dict_load_sys_table(dict_sys->sys_fields);
+ }
mutex_exit(&(dict_sys->mutex));
+
+ return(err);
}
/*****************************************************************//**
@@ -476,9 +488,10 @@ dict_insert_initial_data(void)
}
/*****************************************************************//**
-Creates and initializes the data dictionary at the database creation. */
+Creates and initializes the data dictionary at the server bootstrap.
+@return DB_SUCCESS or error code. */
UNIV_INTERN
-void
+dberr_t
dict_create(void)
/*=============*/
{
@@ -490,7 +503,11 @@ dict_create(void)
mtr_commit(&mtr);
- dict_boot();
+ dberr_t err = dict_boot();
+
+ if (err == DB_SUCCESS) {
+ dict_insert_initial_data();
+ }
- dict_insert_initial_data();
+ return(err);
}
diff --git a/storage/innobase/dict/dict0crea.cc b/storage/innobase/dict/dict0crea.cc
index d58b304ab92..864150b324a 100644
--- a/storage/innobase/dict/dict0crea.cc
+++ b/storage/innobase/dict/dict0crea.cc
@@ -1,6 +1,6 @@
/*****************************************************************************
-Copyright (c) 1996, 2011, Oracle and/or its affiliates. All Rights Reserved.
+Copyright (c) 1996, 2012, Oracle and/or its affiliates. All Rights Reserved.
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
@@ -43,6 +43,7 @@ Created 1/8/1996 Heikki Tuuri
#include "usr0sess.h"
#include "ut0vec.h"
#include "dict0priv.h"
+#include "fts0priv.h"
/*****************************************************************//**
Based on a table object, this function builds the entry to be inserted
@@ -244,8 +245,8 @@ dict_create_sys_columns_tuple(
/***************************************************************//**
Builds a table definition to insert.
@return DB_SUCCESS or error code */
-static
-ulint
+static __attribute__((nonnull, warn_unused_result))
+dberr_t
dict_build_table_def_step(
/*======================*/
que_thr_t* thr, /*!< in: query thread */
@@ -253,9 +254,8 @@ dict_build_table_def_step(
{
dict_table_t* table;
dtuple_t* row;
- ulint error;
- const char* path_or_name;
- ibool is_path;
+ dberr_t error;
+ const char* path;
mtr_t mtr;
ulint space = 0;
bool use_tablespace;
@@ -263,7 +263,7 @@ dict_build_table_def_step(
ut_ad(mutex_own(&(dict_sys->mutex)));
table = node->table;
- use_tablespace = !!(table->flags2 & DICT_TF2_USE_TABLESPACE);
+ use_tablespace = DICT_TF2_FLAG_IS_SET(table, DICT_TF2_USE_TABLESPACE);
dict_hdr_get_new_id(&table->id, NULL, NULL);
@@ -274,6 +274,11 @@ dict_build_table_def_step(
Get a new space id. */
dict_hdr_get_new_id(NULL, NULL, &space);
+ DBUG_EXECUTE_IF(
+ "ib_create_table_fail_out_of_space_ids",
+ space = ULINT_UNDEFINED;
+ );
+
if (UNIV_UNLIKELY(space == ULINT_UNDEFINED)) {
return(DB_ERROR);
}
@@ -286,26 +291,19 @@ dict_build_table_def_step(
- page 3 will contain the root of the clustered index of the
table we create here. */
- if (table->dir_path_of_temp_table) {
- /* We place tables created with CREATE TEMPORARY
- TABLE in the tmp dir of mysqld server */
-
- path_or_name = table->dir_path_of_temp_table;
- is_path = TRUE;
- } else {
- path_or_name = table->name;
- is_path = FALSE;
- }
+ path = table->data_dir_path ? table->data_dir_path
+ : table->dir_path_of_temp_table;
ut_ad(dict_table_get_format(table) <= UNIV_FORMAT_MAX);
ut_ad(!dict_table_zip_size(table)
|| dict_table_get_format(table) >= UNIV_FORMAT_B);
error = fil_create_new_single_table_tablespace(
- space, path_or_name, is_path,
+ space, table->name, path,
dict_tf_to_fsp_flags(table->flags),
table->flags2,
FIL_IBD_FILE_INITIAL_SIZE);
+
table->space = (unsigned int) space;
if (error != DB_SUCCESS) {
@@ -333,10 +331,9 @@ dict_build_table_def_step(
}
/***************************************************************//**
-Builds a column definition to insert.
-@return DB_SUCCESS */
+Builds a column definition to insert. */
static
-ulint
+void
dict_build_col_def_step(
/*====================*/
tab_node_t* node) /*!< in: table create node */
@@ -346,8 +343,6 @@ dict_build_col_def_step(
row = dict_create_sys_columns_tuple(node->table, node->col_no,
node->heap);
ins_node_set_new_row(node->col_def, row);
-
- return(DB_SUCCESS);
}
/*****************************************************************//**
@@ -571,8 +566,8 @@ dict_create_search_tuple(
/***************************************************************//**
Builds an index definition row to insert.
@return DB_SUCCESS or error code */
-static
-ulint
+static __attribute__((nonnull, warn_unused_result))
+dberr_t
dict_build_index_def_step(
/*======================*/
que_thr_t* thr, /*!< in: query thread */
@@ -595,7 +590,10 @@ dict_build_index_def_step(
return(DB_TABLE_NOT_FOUND);
}
- trx->table_id = table->id;
+ if (!trx->table_id) {
+ /* Record only the first table id. */
+ trx->table_id = table->id;
+ }
node->table = table;
@@ -616,15 +614,16 @@ dict_build_index_def_step(
/* Note that the index was created by this transaction. */
index->trx_id = trx->id;
+ ut_ad(table->def_trx_id <= trx->id);
+ table->def_trx_id = trx->id;
return(DB_SUCCESS);
}
/***************************************************************//**
-Builds a field definition row to insert.
-@return DB_SUCCESS */
+Builds a field definition row to insert. */
static
-ulint
+void
dict_build_field_def_step(
/*======================*/
ind_node_t* node) /*!< in: index create node */
@@ -637,15 +636,13 @@ dict_build_field_def_step(
row = dict_create_sys_fields_tuple(index, node->field_no, node->heap);
ins_node_set_new_row(node->field_def, row);
-
- return(DB_SUCCESS);
}
/***************************************************************//**
Creates an index tree for the index if it is not a member of a cluster.
@return DB_SUCCESS or DB_OUT_OF_FILE_SPACE */
-static
-ulint
+static __attribute__((nonnull, warn_unused_result))
+dberr_t
dict_create_index_tree_step(
/*========================*/
ind_node_t* node) /*!< in: index create node */
@@ -653,7 +650,6 @@ dict_create_index_tree_step(
dict_index_t* index;
dict_table_t* sys_indexes;
dtuple_t* search_tuple;
- ulint zip_size;
btr_pcur_t pcur;
mtr_t mtr;
@@ -682,25 +678,37 @@ dict_create_index_tree_step(
btr_pcur_move_to_next_user_rec(&pcur, &mtr);
- zip_size = dict_table_zip_size(index->table);
- node->page_no = btr_create(index->type, index->space, zip_size,
- index->id, index, &mtr);
- /* printf("Created a new index tree in space %lu root page %lu\n",
- index->space, node->page_no); */
+ dberr_t err = DB_SUCCESS;
+ ulint zip_size = dict_table_zip_size(index->table);
- page_rec_write_field(btr_pcur_get_rec(&pcur),
- DICT_FLD__SYS_INDEXES__PAGE_NO,
- node->page_no, &mtr);
- btr_pcur_close(&pcur);
- mtr_commit(&mtr);
+ if (node->index->table->ibd_file_missing
+ || dict_table_is_discarded(node->index->table)) {
+
+ node->page_no = FIL_NULL;
+ } else {
+ node->page_no = btr_create(
+ index->type, index->space, zip_size,
+ index->id, index, &mtr);
- if (node->page_no == FIL_NULL) {
+ if (node->page_no == FIL_NULL) {
+ err = DB_OUT_OF_FILE_SPACE;
+ }
- return(DB_OUT_OF_FILE_SPACE);
+ DBUG_EXECUTE_IF("ib_import_create_index_failure_1",
+ node->page_no = FIL_NULL;
+ err = DB_OUT_OF_FILE_SPACE; );
}
- return(DB_SUCCESS);
+ page_rec_write_field(
+ btr_pcur_get_rec(&pcur), DICT_FLD__SYS_INDEXES__PAGE_NO,
+ node->page_no, &mtr);
+
+ btr_pcur_close(&pcur);
+
+ mtr_commit(&mtr);
+
+ return(err);
}
/*******************************************************************//**
@@ -883,7 +891,7 @@ create:
for (index = UT_LIST_GET_FIRST(table->indexes);
index;
index = UT_LIST_GET_NEXT(indexes, index)) {
- if (index->id == index_id) {
+ if (index->id == index_id && !(index->type & DICT_FTS)) {
root_page_no = btr_create(type, space, zip_size,
index_id, index, mtr);
index->page = (unsigned int) root_page_no;
@@ -910,7 +918,9 @@ tab_create_graph_create(
/*====================*/
dict_table_t* table, /*!< in: table to create, built as a memory data
structure */
- mem_heap_t* heap) /*!< in: heap where created */
+ mem_heap_t* heap, /*!< in: heap where created */
+ bool commit) /*!< in: true if the commit node should be
+ added to the query graph */
{
tab_node_t* node;
@@ -932,8 +942,12 @@ tab_create_graph_create(
heap);
node->col_def->common.parent = node;
- node->commit_node = trx_commit_node_create(heap);
- node->commit_node->common.parent = node;
+ if (commit) {
+ node->commit_node = trx_commit_node_create(heap);
+ node->commit_node->common.parent = node;
+ } else {
+ node->commit_node = 0;
+ }
return(node);
}
@@ -947,7 +961,9 @@ ind_create_graph_create(
/*====================*/
dict_index_t* index, /*!< in: index to create, built as a memory data
structure */
- mem_heap_t* heap) /*!< in: heap where created */
+ mem_heap_t* heap, /*!< in: heap where created */
+ bool commit) /*!< in: true if the commit node should be
+ added to the query graph */
{
ind_node_t* node;
@@ -970,8 +986,12 @@ ind_create_graph_create(
dict_sys->sys_fields, heap);
node->field_def->common.parent = node;
- node->commit_node = trx_commit_node_create(heap);
- node->commit_node->common.parent = node;
+ if (commit) {
+ node->commit_node = trx_commit_node_create(heap);
+ node->commit_node->common.parent = node;
+ } else {
+ node->commit_node = 0;
+ }
return(node);
}
@@ -986,7 +1006,7 @@ dict_create_table_step(
que_thr_t* thr) /*!< in: query thread */
{
tab_node_t* node;
- ulint err = DB_ERROR;
+ dberr_t err = DB_ERROR;
trx_t* trx;
ut_ad(thr);
@@ -1025,12 +1045,7 @@ dict_create_table_step(
if (node->col_no < (node->table)->n_def) {
- err = dict_build_col_def_step(node);
-
- if (err != DB_SUCCESS) {
-
- goto function_exit;
- }
+ dict_build_col_def_step(node);
node->col_no++;
@@ -1063,7 +1078,7 @@ dict_create_table_step(
}
function_exit:
- trx->error_state = (enum db_err) err;
+ trx->error_state = err;
if (err == DB_SUCCESS) {
/* Ok: do nothing */
@@ -1093,7 +1108,7 @@ dict_create_index_step(
que_thr_t* thr) /*!< in: query thread */
{
ind_node_t* node;
- ulint err = DB_ERROR;
+ dberr_t err = DB_ERROR;
trx_t* trx;
ut_ad(thr);
@@ -1130,12 +1145,7 @@ dict_create_index_step(
if (node->field_no < (node->index)->n_fields) {
- err = dict_build_field_def_step(node);
-
- if (err != DB_SUCCESS) {
-
- goto function_exit;
- }
+ dict_build_field_def_step(node);
node->field_no++;
@@ -1172,7 +1182,37 @@ dict_create_index_step(
err = dict_create_index_tree_step(node);
+ DBUG_EXECUTE_IF("ib_dict_create_index_tree_fail",
+ err = DB_OUT_OF_MEMORY;);
+
if (err != DB_SUCCESS) {
+ /* If this is a FTS index, we will need to remove
+ it from fts->cache->indexes list as well */
+ if ((node->index->type & DICT_FTS)
+ && node->table->fts) {
+ fts_index_cache_t* index_cache;
+
+ rw_lock_x_lock(
+ &node->table->fts->cache->init_lock);
+
+ index_cache = (fts_index_cache_t*)
+ fts_find_index_cache(
+ node->table->fts->cache,
+ node->index);
+
+ if (index_cache->words) {
+ rbt_free(index_cache->words);
+ index_cache->words = 0;
+ }
+
+ ib_vector_remove(
+ node->table->fts->cache->indexes,
+ *reinterpret_cast<void**>(index_cache));
+
+ rw_lock_x_unlock(
+ &node->table->fts->cache->init_lock);
+ }
+
dict_index_remove_from_cache(node->table, node->index);
node->index = NULL;
@@ -1180,6 +1220,11 @@ dict_create_index_step(
}
node->index->page = node->page_no;
+ /* These should have been set in
+ dict_build_index_def_step() and
+ dict_index_add_to_cache(). */
+ ut_ad(node->index->trx_id == trx->id);
+ ut_ad(node->index->table->def_trx_id == trx->id);
node->state = INDEX_COMMIT_WORK;
}
@@ -1197,7 +1242,7 @@ dict_create_index_step(
}
function_exit:
- trx->error_state = static_cast<enum db_err>(err);
+ trx->error_state = err;
if (err == DB_SUCCESS) {
/* Ok: do nothing */
@@ -1217,93 +1262,107 @@ function_exit:
}
/****************************************************************//**
-Check whether the system foreign key tables exist. Additionally, If
-they exist then move them to non-LRU end of the table LRU list.
-@return TRUE if they exist. */
+Check whether a system table exists. Additionally, if it exists,
+move it to the non-LRU end of the table LRU list. This is oly used
+for system tables that can be upgraded or added to an older database,
+which include SYS_FOREIGN, SYS_FOREIGN_COLS, SYS_TABLESPACES and
+SYS_DATAFILES.
+@return DB_SUCCESS if the sys table exists, DB_CORRUPTION if it exists
+but is not current, DB_TABLE_NOT_FOUND if it does not exist*/
static
-ibool
-dict_check_sys_foreign_tables_exist(void)
-/*=====================================*/
+dberr_t
+dict_check_if_system_table_exists(
+/*==============================*/
+ const char* tablename, /*!< in: name of table */
+ ulint num_fields, /*!< in: number of fields */
+ ulint num_indexes) /*!< in: number of indexes */
{
- dict_table_t* sys_foreign;
- ibool exists = FALSE;
- dict_table_t* sys_foreign_cols;
+ dict_table_t* sys_table;
+ dberr_t error = DB_SUCCESS;
ut_a(srv_get_active_thread_type() == SRV_NONE);
mutex_enter(&dict_sys->mutex);
- sys_foreign = dict_table_get_low("SYS_FOREIGN");
- sys_foreign_cols = dict_table_get_low("SYS_FOREIGN_COLS");
+ sys_table = dict_table_get_low(tablename);
- if (sys_foreign != NULL
- && sys_foreign_cols != NULL
- && UT_LIST_GET_LEN(sys_foreign->indexes) == 3
- && UT_LIST_GET_LEN(sys_foreign_cols->indexes) == 1) {
+ if (sys_table == NULL) {
+ error = DB_TABLE_NOT_FOUND;
- /* Foreign constraint system tables have already been
- created, and they are ok. Ensure that they can't be
- evicted from the table LRU cache. */
+ } else if (UT_LIST_GET_LEN(sys_table->indexes) != num_indexes
+ || sys_table->n_cols != num_fields) {
+ error = DB_CORRUPTION;
- dict_table_move_from_lru_to_non_lru(sys_foreign);
- dict_table_move_from_lru_to_non_lru(sys_foreign_cols);
+ } else {
+ /* This table has already been created, and it is OK.
+ Ensure that it can't be evicted from the table LRU cache. */
- exists = TRUE;
+ dict_table_move_from_lru_to_non_lru(sys_table);
}
mutex_exit(&dict_sys->mutex);
- return(exists);
+ return(error);
}
/****************************************************************//**
Creates the foreign key constraints system tables inside InnoDB
-at database creation or database start if they are not found or are
+at server bootstrap or server start if they are not found or are
not of the right form.
@return DB_SUCCESS or error code */
UNIV_INTERN
-ulint
+dberr_t
dict_create_or_check_foreign_constraint_tables(void)
/*================================================*/
{
trx_t* trx;
- ulint error;
- ibool success;
- ibool srv_file_per_table_backup;
+ my_bool srv_file_per_table_backup;
+ dberr_t err;
+ dberr_t sys_foreign_err;
+ dberr_t sys_foreign_cols_err;
ut_a(srv_get_active_thread_type() == SRV_NONE);
/* Note: The master thread has not been started at this point. */
- if (dict_check_sys_foreign_tables_exist()) {
+
+ sys_foreign_err = dict_check_if_system_table_exists(
+ "SYS_FOREIGN", DICT_NUM_FIELDS__SYS_FOREIGN + 1, 3);
+ sys_foreign_cols_err = dict_check_if_system_table_exists(
+ "SYS_FOREIGN_COLS", DICT_NUM_FIELDS__SYS_FOREIGN_COLS + 1, 1);
+
+ if (sys_foreign_err == DB_SUCCESS
+ && sys_foreign_cols_err == DB_SUCCESS) {
return(DB_SUCCESS);
}
trx = trx_allocate_for_mysql();
+ trx_set_dict_operation(trx, TRX_DICT_OP_TABLE);
+
trx->op_info = "creating foreign key sys tables";
row_mysql_lock_data_dictionary(trx);
/* Check which incomplete table definition to drop. */
- if (dict_table_get_low("SYS_FOREIGN") != NULL) {
- fprintf(stderr,
- "InnoDB: dropping incompletely created"
- " SYS_FOREIGN table\n");
+ if (sys_foreign_err == DB_CORRUPTION) {
+ ib_logf(IB_LOG_LEVEL_WARN,
+ "Dropping incompletely created "
+ "SYS_FOREIGN table.");
row_drop_table_for_mysql("SYS_FOREIGN", trx, TRUE);
}
- if (dict_table_get_low("SYS_FOREIGN_COLS") != NULL) {
- fprintf(stderr,
- "InnoDB: dropping incompletely created"
- " SYS_FOREIGN_COLS table\n");
+ if (sys_foreign_cols_err == DB_CORRUPTION) {
+ ib_logf(IB_LOG_LEVEL_WARN,
+ "Dropping incompletely created "
+ "SYS_FOREIGN_COLS table.");
row_drop_table_for_mysql("SYS_FOREIGN_COLS", trx, TRUE);
}
- fprintf(stderr,
- "InnoDB: Creating foreign key constraint system tables\n");
+ ib_logf(IB_LOG_LEVEL_WARN,
+ "Creating foreign key constraint system tables.");
/* NOTE: in dict_load_foreigns we use the fact that
there are 2 secondary indexes on SYS_FOREIGN, and they
@@ -1315,50 +1374,50 @@ dict_create_or_check_foreign_constraint_tables(void)
VARBINARY, like in other InnoDB system tables, to get a clean
design. */
- srv_file_per_table_backup = (ibool) srv_file_per_table;
+ srv_file_per_table_backup = srv_file_per_table;
/* We always want SYSTEM tables to be created inside the system
tablespace. */
srv_file_per_table = 0;
- error = que_eval_sql(NULL,
- "PROCEDURE CREATE_FOREIGN_SYS_TABLES_PROC () IS\n"
- "BEGIN\n"
- "CREATE TABLE\n"
- "SYS_FOREIGN(ID CHAR, FOR_NAME CHAR,"
- " REF_NAME CHAR, N_COLS INT);\n"
- "CREATE UNIQUE CLUSTERED INDEX ID_IND"
- " ON SYS_FOREIGN (ID);\n"
- "CREATE INDEX FOR_IND"
- " ON SYS_FOREIGN (FOR_NAME);\n"
- "CREATE INDEX REF_IND"
- " ON SYS_FOREIGN (REF_NAME);\n"
- "CREATE TABLE\n"
- "SYS_FOREIGN_COLS(ID CHAR, POS INT,"
- " FOR_COL_NAME CHAR, REF_COL_NAME CHAR);\n"
- "CREATE UNIQUE CLUSTERED INDEX ID_IND"
- " ON SYS_FOREIGN_COLS (ID, POS);\n"
- "END;\n"
- , FALSE, trx);
-
- if (error != DB_SUCCESS) {
- fprintf(stderr, "InnoDB: error %lu in creation\n",
- (ulong) error);
-
- ut_a(error == DB_OUT_OF_FILE_SPACE
- || error == DB_TOO_MANY_CONCURRENT_TRXS);
-
- fprintf(stderr,
- "InnoDB: creation failed\n"
- "InnoDB: tablespace is full\n"
- "InnoDB: dropping incompletely created"
- " SYS_FOREIGN tables\n");
+ err = que_eval_sql(
+ NULL,
+ "PROCEDURE CREATE_FOREIGN_SYS_TABLES_PROC () IS\n"
+ "BEGIN\n"
+ "CREATE TABLE\n"
+ "SYS_FOREIGN(ID CHAR, FOR_NAME CHAR,"
+ " REF_NAME CHAR, N_COLS INT);\n"
+ "CREATE UNIQUE CLUSTERED INDEX ID_IND"
+ " ON SYS_FOREIGN (ID);\n"
+ "CREATE INDEX FOR_IND"
+ " ON SYS_FOREIGN (FOR_NAME);\n"
+ "CREATE INDEX REF_IND"
+ " ON SYS_FOREIGN (REF_NAME);\n"
+ "CREATE TABLE\n"
+ "SYS_FOREIGN_COLS(ID CHAR, POS INT,"
+ " FOR_COL_NAME CHAR, REF_COL_NAME CHAR);\n"
+ "CREATE UNIQUE CLUSTERED INDEX ID_IND"
+ " ON SYS_FOREIGN_COLS (ID, POS);\n"
+ "END;\n",
+ FALSE, trx);
+
+ if (err != DB_SUCCESS) {
+ ib_logf(IB_LOG_LEVEL_ERROR,
+ "Creation of SYS_FOREIGN and SYS_FOREIGN_COLS "
+ "has failed with error %lu. Tablespace is full. "
+ "Dropping incompletely created tables.",
+ (ulong) err);
+
+ ut_ad(err == DB_OUT_OF_FILE_SPACE
+ || err == DB_TOO_MANY_CONCURRENT_TRXS);
row_drop_table_for_mysql("SYS_FOREIGN", trx, TRUE);
row_drop_table_for_mysql("SYS_FOREIGN_COLS", trx, TRUE);
- error = DB_MUST_GET_MORE_FILE_SPACE;
+ if (err == DB_OUT_OF_FILE_SPACE) {
+ err = DB_MUST_GET_MORE_FILE_SPACE;
+ }
}
trx_commit_for_mysql(trx);
@@ -1367,28 +1426,31 @@ dict_create_or_check_foreign_constraint_tables(void)
trx_free_for_mysql(trx);
- if (error == DB_SUCCESS) {
- fprintf(stderr,
- "InnoDB: Foreign key constraint system tables"
- " created\n");
+ srv_file_per_table = srv_file_per_table_backup;
+
+ if (err == DB_SUCCESS) {
+ ib_logf(IB_LOG_LEVEL_INFO,
+ "Foreign key constraint system tables created");
}
/* Note: The master thread has not been started at this point. */
/* Confirm and move to the non-LRU part of the table LRU list. */
+ sys_foreign_err = dict_check_if_system_table_exists(
+ "SYS_FOREIGN", DICT_NUM_FIELDS__SYS_FOREIGN + 1, 3);
+ ut_a(sys_foreign_err == DB_SUCCESS);
- success = dict_check_sys_foreign_tables_exist();
- ut_a(success);
-
- srv_file_per_table = (my_bool) srv_file_per_table_backup;
+ sys_foreign_cols_err = dict_check_if_system_table_exists(
+ "SYS_FOREIGN_COLS", DICT_NUM_FIELDS__SYS_FOREIGN_COLS + 1, 1);
+ ut_a(sys_foreign_cols_err == DB_SUCCESS);
- return(error);
+ return(err);
}
/****************************************************************//**
Evaluate the given foreign key SQL statement.
@return error code or DB_SUCCESS */
-static
-ulint
+static __attribute__((nonnull, warn_unused_result))
+dberr_t
dict_foreign_eval_sql(
/*==================*/
pars_info_t* info, /*!< in: info struct, or NULL */
@@ -1397,8 +1459,8 @@ dict_foreign_eval_sql(
dict_foreign_t* foreign,/*!< in: foreign */
trx_t* trx) /*!< in: transaction */
{
- ulint error;
- FILE* ef = dict_foreign_err_file;
+ dberr_t error;
+ FILE* ef = dict_foreign_err_file;
error = que_eval_sql(info, sql, FALSE, trx);
@@ -1453,8 +1515,8 @@ dict_foreign_eval_sql(
Add a single foreign key field definition to the data dictionary tables in
the database.
@return error code or DB_SUCCESS */
-static
-ulint
+static __attribute__((nonnull, warn_unused_result))
+dberr_t
dict_create_add_foreign_field_to_dictionary(
/*========================================*/
ulint field_nr, /*!< in: foreign field number */
@@ -1492,17 +1554,17 @@ databasename/tablename_ibfk_NUMBER, where the numbers start from 1, and
are given locally for this table, that is, the number is not global, as in
the old format constraints < 4.0.18 it used to be.
@return error code or DB_SUCCESS */
-static
-ulint
+UNIV_INTERN
+dberr_t
dict_create_add_foreign_to_dictionary(
/*==================================*/
ulint* id_nr, /*!< in/out: number to use in id generation;
incremented if used */
dict_table_t* table, /*!< in: table */
dict_foreign_t* foreign,/*!< in: foreign */
- trx_t* trx) /*!< in: transaction */
+ trx_t* trx) /*!< in/out: dictionary transaction */
{
- ulint error;
+ dberr_t error;
ulint i;
pars_info_t* info = pars_info_create();
@@ -1553,12 +1615,6 @@ dict_create_add_foreign_to_dictionary(
}
}
- trx->op_info = "committing foreign key definitions";
-
- trx_commit(trx);
-
- trx->op_info = "";
-
return(error);
}
@@ -1566,7 +1622,7 @@ dict_create_add_foreign_to_dictionary(
Adds foreign key definitions to data dictionary tables in the database.
@return error code or DB_SUCCESS */
UNIV_INTERN
-ulint
+dberr_t
dict_create_add_foreigns_to_dictionary(
/*===================================*/
ulint start_id,/*!< in: if we are actually doing ALTER TABLE
@@ -1582,7 +1638,7 @@ dict_create_add_foreigns_to_dictionary(
{
dict_foreign_t* foreign;
ulint number = start_id + 1;
- ulint error;
+ dberr_t error;
ut_ad(mutex_own(&(dict_sys->mutex)));
@@ -1607,5 +1663,188 @@ dict_create_add_foreigns_to_dictionary(
}
}
+ trx->op_info = "committing foreign key definitions";
+
+ trx_commit(trx);
+
+ trx->op_info = "";
+
return(DB_SUCCESS);
}
+
+/****************************************************************//**
+Creates the tablespaces and datafiles system tables inside InnoDB
+at server bootstrap or server start if they are not found or are
+not of the right form.
+@return DB_SUCCESS or error code */
+UNIV_INTERN
+dberr_t
+dict_create_or_check_sys_tablespace(void)
+/*=====================================*/
+{
+ trx_t* trx;
+ my_bool srv_file_per_table_backup;
+ dberr_t err;
+ dberr_t sys_tablespaces_err;
+ dberr_t sys_datafiles_err;
+
+ ut_a(srv_get_active_thread_type() == SRV_NONE);
+
+ /* Note: The master thread has not been started at this point. */
+
+ sys_tablespaces_err = dict_check_if_system_table_exists(
+ "SYS_TABLESPACES", DICT_NUM_FIELDS__SYS_TABLESPACES + 1, 1);
+ sys_datafiles_err = dict_check_if_system_table_exists(
+ "SYS_DATAFILES", DICT_NUM_FIELDS__SYS_DATAFILES + 1, 1);
+
+ if (sys_tablespaces_err == DB_SUCCESS
+ && sys_datafiles_err == DB_SUCCESS) {
+ return(DB_SUCCESS);
+ }
+
+ trx = trx_allocate_for_mysql();
+
+ trx_set_dict_operation(trx, TRX_DICT_OP_TABLE);
+
+ trx->op_info = "creating tablepace and datafile sys tables";
+
+ row_mysql_lock_data_dictionary(trx);
+
+ /* Check which incomplete table definition to drop. */
+
+ if (sys_tablespaces_err == DB_CORRUPTION) {
+ ib_logf(IB_LOG_LEVEL_WARN,
+ "Dropping incompletely created "
+ "SYS_TABLESPACES table.");
+ row_drop_table_for_mysql("SYS_TABLESPACES", trx, TRUE);
+ }
+
+ if (sys_datafiles_err == DB_CORRUPTION) {
+ ib_logf(IB_LOG_LEVEL_WARN,
+ "Dropping incompletely created "
+ "SYS_DATAFILES table.");
+
+ row_drop_table_for_mysql("SYS_DATAFILES", trx, TRUE);
+ }
+
+ ib_logf(IB_LOG_LEVEL_INFO,
+ "Creating tablespace and datafile system tables.");
+
+ /* We always want SYSTEM tables to be created inside the system
+ tablespace. */
+ srv_file_per_table_backup = srv_file_per_table;
+ srv_file_per_table = 0;
+
+ err = que_eval_sql(
+ NULL,
+ "PROCEDURE CREATE_SYS_TABLESPACE_PROC () IS\n"
+ "BEGIN\n"
+ "CREATE TABLE SYS_TABLESPACES(\n"
+ " SPACE INT, NAME CHAR, FLAGS INT);\n"
+ "CREATE UNIQUE CLUSTERED INDEX SYS_TABLESPACES_SPACE"
+ " ON SYS_TABLESPACES (SPACE);\n"
+ "CREATE TABLE SYS_DATAFILES(\n"
+ " SPACE INT, PATH CHAR);\n"
+ "CREATE UNIQUE CLUSTERED INDEX SYS_DATAFILES_SPACE"
+ " ON SYS_DATAFILES (SPACE);\n"
+ "END;\n",
+ FALSE, trx);
+
+ if (err != DB_SUCCESS) {
+ ib_logf(IB_LOG_LEVEL_ERROR,
+ "Creation of SYS_TABLESPACES and SYS_DATAFILES "
+ "has failed with error %lu. Tablespace is full. "
+ "Dropping incompletely created tables.",
+ (ulong) err);
+
+ ut_a(err == DB_OUT_OF_FILE_SPACE
+ || err == DB_TOO_MANY_CONCURRENT_TRXS);
+
+ row_drop_table_for_mysql("SYS_TABLESPACES", trx, TRUE);
+ row_drop_table_for_mysql("SYS_DATAFILES", trx, TRUE);
+
+ if (err == DB_OUT_OF_FILE_SPACE) {
+ err = DB_MUST_GET_MORE_FILE_SPACE;
+ }
+ }
+
+ trx_commit_for_mysql(trx);
+
+ row_mysql_unlock_data_dictionary(trx);
+
+ trx_free_for_mysql(trx);
+
+ srv_file_per_table = srv_file_per_table_backup;
+
+ if (err == DB_SUCCESS) {
+ ib_logf(IB_LOG_LEVEL_INFO,
+ "Tablespace and datafile system tables created.");
+ }
+
+ /* Note: The master thread has not been started at this point. */
+ /* Confirm and move to the non-LRU part of the table LRU list. */
+
+ sys_tablespaces_err = dict_check_if_system_table_exists(
+ "SYS_TABLESPACES", DICT_NUM_FIELDS__SYS_TABLESPACES + 1, 1);
+ ut_a(sys_tablespaces_err == DB_SUCCESS);
+
+ sys_datafiles_err = dict_check_if_system_table_exists(
+ "SYS_DATAFILES", DICT_NUM_FIELDS__SYS_DATAFILES + 1, 1);
+ ut_a(sys_datafiles_err == DB_SUCCESS);
+
+ return(err);
+}
+
+/********************************************************************//**
+Add a single tablespace definition to the data dictionary tables in the
+database.
+@return error code or DB_SUCCESS */
+UNIV_INTERN
+dberr_t
+dict_create_add_tablespace_to_dictionary(
+/*=====================================*/
+ ulint space, /*!< in: tablespace id */
+ const char* name, /*!< in: tablespace name */
+ ulint flags, /*!< in: tablespace flags */
+ const char* path, /*!< in: tablespace path */
+ trx_t* trx, /*!< in/out: transaction */
+ bool commit) /*!< in: if true then commit the
+ transaction */
+{
+ dberr_t error;
+
+ pars_info_t* info = pars_info_create();
+
+ ut_a(space > TRX_SYS_SPACE);
+
+ pars_info_add_int4_literal(info, "space", space);
+
+ pars_info_add_str_literal(info, "name", name);
+
+ pars_info_add_int4_literal(info, "flags", flags);
+
+ pars_info_add_str_literal(info, "path", path);
+
+ error = que_eval_sql(info,
+ "PROCEDURE P () IS\n"
+ "BEGIN\n"
+ "INSERT INTO SYS_TABLESPACES VALUES"
+ "(:space, :name, :flags);\n"
+ "INSERT INTO SYS_DATAFILES VALUES"
+ "(:space, :path);\n"
+ "END;\n",
+ FALSE, trx);
+
+ if (error != DB_SUCCESS) {
+ return(error);
+ }
+
+ if (commit) {
+ trx->op_info = "committing tablespace and datafile definition";
+ trx_commit(trx);
+ }
+
+ trx->op_info = "";
+
+ return(error);
+}
diff --git a/storage/innobase/dict/dict0dict.cc b/storage/innobase/dict/dict0dict.cc
index 8282dafda0c..8e111645880 100644
--- a/storage/innobase/dict/dict0dict.cc
+++ b/storage/innobase/dict/dict0dict.cc
@@ -1,6 +1,7 @@
/*****************************************************************************
-Copyright (c) 1996, 2011, Oracle and/or its affiliates. All Rights Reserved.
+Copyright (c) 1996, 2012, Oracle and/or its affiliates. All Rights Reserved.
+Copyright (c) 2012, Facebook Inc.
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
@@ -25,6 +26,7 @@ Created 1/8/1996 Heikki Tuuri
#include "dict0dict.h"
#include "fts0fts.h"
+#include "fil0fil.h"
#ifdef UNIV_NONINL
#include "dict0dict.ic"
@@ -56,7 +58,6 @@ UNIV_INTERN dict_index_t* dict_ind_compact;
#include "rem0cmp.h"
#include "fts0fts.h"
#include "fts0types.h"
-#include "row0merge.h"
#include "m_ctype.h" /* my_isspace() */
#include "ha_prototypes.h" /* innobase_strcasecmp(), innobase_casedn_str() */
#include "srv0mon.h"
@@ -64,6 +65,14 @@ UNIV_INTERN dict_index_t* dict_ind_compact;
#include "lock0lock.h"
#include "dict0priv.h"
#include "row0upd.h"
+#include "row0mysql.h"
+#include "row0merge.h"
+#include "row0log.h"
+#include "ut0ut.h" /* ut_format_name() */
+#include "m_string.h"
+#include "my_sys.h"
+#include "mysqld.h" /* system_charset_info */
+#include "strfunc.h" /* strconvert() */
#include <ctype.h>
@@ -77,17 +86,27 @@ backround operations purge, rollback, foreign key checks reserve this
in S-mode; we cannot trust that MySQL protects implicit or background
operations a table drop since MySQL does not know of them; therefore
we need this; NOTE: a transaction which reserves this must keep book
-on the mode in trx_struct::dict_operation_lock_mode */
+on the mode in trx_t::dict_operation_lock_mode */
UNIV_INTERN rw_lock_t dict_operation_lock;
+/** Percentage of compression failures that are allowed in a single
+round */
+UNIV_INTERN ulong zip_failure_threshold_pct = 5;
+
+/** Maximum percentage of a page that can be allowed as a pad to avoid
+compression failures */
+UNIV_INTERN ulong zip_pad_max = 50;
+
/* Keys to register rwlocks and mutexes with performance schema */
#ifdef UNIV_PFS_RWLOCK
UNIV_INTERN mysql_pfs_key_t dict_operation_lock_key;
UNIV_INTERN mysql_pfs_key_t index_tree_rw_lock_key;
+UNIV_INTERN mysql_pfs_key_t index_online_log_key;
UNIV_INTERN mysql_pfs_key_t dict_table_stats_latch_key;
#endif /* UNIV_PFS_RWLOCK */
#ifdef UNIV_PFS_MUTEX
+UNIV_INTERN mysql_pfs_key_t zip_pad_mutex_key;
UNIV_INTERN mysql_pfs_key_t dict_sys_mutex_key;
UNIV_INTERN mysql_pfs_key_t dict_foreign_err_mutex_key;
#endif /* UNIV_PFS_MUTEX */
@@ -157,13 +176,6 @@ dict_index_build_internal_fts(
dict_table_t* table, /*!< in: table */
dict_index_t* index); /*!< in: user representation of an FTS index */
/**********************************************************************//**
-Removes a foreign constraint struct from the dictionary cache. */
-static
-void
-dict_foreign_remove_from_cache(
-/*===========================*/
- dict_foreign_t* foreign); /*!< in, own: foreign constraint */
-/**********************************************************************//**
Prints a column data. */
static
void
@@ -185,14 +197,6 @@ void
dict_field_print_low(
/*=================*/
const dict_field_t* field); /*!< in: field */
-#ifndef UNIV_HOTBACKUP
-/*********************************************************************//**
-Frees a foreign key struct. */
-static
-void
-dict_foreign_free(
-/*==============*/
- dict_foreign_t* foreign); /*!< in, own: foreign key struct */
/**********************************************************************//**
Removes an index from the dictionary cache. */
@@ -216,14 +220,14 @@ dict_table_remove_from_cache_low(
/**********************************************************************//**
Validate the dictionary table LRU list.
@return TRUE if validate OK */
-UNIV_INTERN
+static
ibool
dict_lru_validate(void);
/*===================*/
/**********************************************************************//**
Check if table is in the dictionary table LRU list.
@return TRUE if table found */
-UNIV_INTERN
+static
ibool
dict_lru_find_table(
/*================*/
@@ -239,11 +243,11 @@ dict_non_lru_find_table(
#endif /* UNIV_DEBUG */
/* Stream for storing detailed information about the latest foreign key
-and unique key errors */
+and unique key errors. Only created if !srv_read_only_mode */
UNIV_INTERN FILE* dict_foreign_err_file = NULL;
/* mutex protecting the foreign and unique error buffers */
-UNIV_INTERN mutex_t dict_foreign_err_mutex;
-#endif /* !UNIV_HOTBACKUP */
+UNIV_INTERN ib_mutex_t dict_foreign_err_mutex;
+
/******************************************************************//**
Makes all characters in a NUL-terminated UTF-8 string lower case. */
UNIV_INTERN
@@ -330,7 +334,7 @@ dict_mutex_exit_for_mysql(void)
/** Get the latch that protects the stats of a given table */
#define GET_TABLE_STATS_LATCH(table) \
- (&dict_table_stats_latches[ut_fold_ull(table->id) \
+ (&dict_table_stats_latches[ut_fold_ull((ib_uint64_t) table) \
% DICT_TABLE_STATS_LATCHES_SIZE])
/**********************************************************************//**
@@ -389,6 +393,75 @@ dict_table_stats_unlock(
}
}
+/**********************************************************************//**
+Try to drop any indexes after an aborted index creation.
+This can also be after a server kill during DROP INDEX. */
+static
+void
+dict_table_try_drop_aborted(
+/*========================*/
+ dict_table_t* table, /*!< in: table, or NULL if it
+ needs to be looked up again */
+ table_id_t table_id, /*!< in: table identifier */
+ ulint ref_count) /*!< in: expected table->n_ref_count */
+{
+ trx_t* trx;
+
+ trx = trx_allocate_for_background();
+ trx->op_info = "try to drop any indexes after an aborted index creation";
+ row_mysql_lock_data_dictionary(trx);
+ trx_set_dict_operation(trx, TRX_DICT_OP_INDEX);
+
+ if (table == NULL) {
+ table = dict_table_open_on_id_low(table_id);
+ } else {
+ ut_ad(table->id == table_id);
+ }
+
+ if (table && table->n_ref_count == ref_count && table->drop_aborted) {
+ /* Silence a debug assertion in row_merge_drop_indexes(). */
+ ut_d(table->n_ref_count++);
+ row_merge_drop_indexes(trx, table, TRUE);
+ ut_d(table->n_ref_count--);
+ ut_ad(table->n_ref_count == ref_count);
+ trx_commit_for_mysql(trx);
+ }
+
+ row_mysql_unlock_data_dictionary(trx);
+ trx_free_for_background(trx);
+}
+
+/**********************************************************************//**
+When opening a table,
+try to drop any indexes after an aborted index creation.
+Release the dict_sys->mutex. */
+static
+void
+dict_table_try_drop_aborted_and_mutex_exit(
+/*=======================================*/
+ dict_table_t* table, /*!< in: table (may be NULL) */
+ ibool try_drop) /*!< in: FALSE if should try to
+ drop indexes whose online creation
+ was aborted */
+{
+ if (try_drop
+ && table != NULL
+ && table->drop_aborted
+ && table->n_ref_count == 1
+ && dict_table_get_first_index(table)) {
+
+ /* Attempt to drop the indexes whose online creation
+ was aborted. */
+ table_id_t table_id = table->id;
+
+ mutex_exit(&dict_sys->mutex);
+
+ dict_table_try_drop_aborted(table, table_id, 1);
+ } else {
+ mutex_exit(&dict_sys->mutex);
+ }
+}
+
/********************************************************************//**
Decrements the count of open handles to a table. */
UNIV_INTERN
@@ -396,7 +469,10 @@ void
dict_table_close(
/*=============*/
dict_table_t* table, /*!< in/out: table */
- ibool dict_locked) /*!< in: TRUE=data dictionary locked */
+ ibool dict_locked, /*!< in: TRUE=data dictionary locked */
+ ibool try_drop) /*!< in: TRUE=try to drop any orphan
+ indexes after an aborted online
+ index creation */
{
if (!dict_locked) {
mutex_enter(&dict_sys->mutex);
@@ -407,6 +483,18 @@ dict_table_close(
--table->n_ref_count;
+ /* Force persistent stats re-read upon next open of the table
+ so that FLUSH TABLE can be used to forcibly fetch stats from disk
+ if they have been manually modified. We reset table->stat_initialized
+ only if table reference count is 0 because we do not want too frequent
+ stats re-reads (e.g. in other cases than FLUSH TABLE). */
+ if (strchr(table->name, '/') != NULL
+ && table->n_ref_count == 0
+ && dict_stats_is_persistent_enabled(table)) {
+
+ dict_stats_deinit(table);
+ }
+
MONITOR_DEC(MONITOR_TABLE_REFERENCE);
ut_ad(dict_lru_validate());
@@ -420,7 +508,19 @@ dict_table_close(
#endif /* UNIV_DEBUG */
if (!dict_locked) {
+ table_id_t table_id = table->id;
+ ibool drop_aborted;
+
+ drop_aborted = try_drop
+ && table->drop_aborted
+ && table->n_ref_count == 1
+ && dict_table_get_first_index(table);
+
mutex_exit(&dict_sys->mutex);
+
+ if (drop_aborted) {
+ dict_table_try_drop_aborted(NULL, table_id, 0);
+ }
}
}
#endif /* !UNIV_HOTBACKUP */
@@ -550,33 +650,6 @@ dict_table_autoinc_unlock(
{
mutex_exit(&table->autoinc_mutex);
}
-
-/**********************************************************************//**
-Looks for an index with the given table and index id.
-Note: Does not reserve the dictionary mutex.
-@return index or NULL if not found in cache */
-UNIV_INTERN
-dict_index_t*
-dict_index_get_on_id_low(
-/*=====================*/
- dict_table_t* table, /*!< in: table */
- index_id_t id) /*!< in: index id */
-{
- dict_index_t* index;
-
- for (index = dict_table_get_first_index(table);
- index != NULL;
- index = dict_table_get_next_index(index)) {
-
- if (id == index->id) {
- /* Found */
-
- return(index);
- }
- }
-
- return(NULL);
-}
#endif /* !UNIV_HOTBACKUP */
/********************************************************************//**
@@ -712,7 +785,10 @@ dict_table_t*
dict_table_open_on_id(
/*==================*/
table_id_t table_id, /*!< in: table id */
- ibool dict_locked) /*!< in: TRUE=data dictionary locked */
+ ibool dict_locked, /*!< in: TRUE=data dictionary locked */
+ ibool try_drop) /*!< in: TRUE=try to drop any orphan
+ indexes after an aborted online
+ index creation */
{
dict_table_t* table;
@@ -736,7 +812,7 @@ dict_table_open_on_id(
}
if (!dict_locked) {
- mutex_exit(&dict_sys->mutex);
+ dict_table_try_drop_aborted_and_mutex_exit(table, try_drop);
}
return(table);
@@ -815,11 +891,13 @@ dict_init(void)
rw_lock_create(dict_operation_lock_key,
&dict_operation_lock, SYNC_DICT_OPERATION);
- dict_foreign_err_file = os_file_create_tmpfile();
- ut_a(dict_foreign_err_file);
+ if (!srv_read_only_mode) {
+ dict_foreign_err_file = os_file_create_tmpfile();
+ ut_a(dict_foreign_err_file);
- mutex_create(dict_foreign_err_mutex_key,
- &dict_foreign_err_mutex, SYNC_NO_ORDER_CHECK);
+ mutex_create(dict_foreign_err_mutex_key,
+ &dict_foreign_err_mutex, SYNC_NO_ORDER_CHECK);
+ }
for (i = 0; i < DICT_TABLE_STATS_LATCHES_SIZE; i++) {
rw_lock_create(dict_table_stats_latch_key,
@@ -849,14 +927,20 @@ dict_move_to_mru(
}
/**********************************************************************//**
-Returns a table object and increments its open handle count.
+Returns a table object and increment its open handle count.
+NOTE! This is a high-level function to be used mainly from outside the
+'dict' module. Inside this directory dict_table_get_low
+is usually the appropriate function.
@return table, NULL if does not exist */
-static
+UNIV_INTERN
dict_table_t*
-dict_table_open_on_name_low(
-/*========================*/
+dict_table_open_on_name(
+/*====================*/
const char* table_name, /*!< in: table name */
ibool dict_locked, /*!< in: TRUE=data dictionary locked */
+ ibool try_drop, /*!< in: TRUE=try to drop any orphan
+ indexes after an aborted online
+ index creation */
dict_err_ignore_t
ignore_err) /*!< in: error to be ignored when
loading a table definition */
@@ -915,61 +999,11 @@ dict_table_open_on_name_low(
ut_ad(dict_lru_validate());
if (!dict_locked) {
- mutex_exit(&(dict_sys->mutex));
+ dict_table_try_drop_aborted_and_mutex_exit(table, try_drop);
}
return(table);
}
-
-/**********************************************************************//**
-Returns a table object and increment its open handle count.
-NOTE! This is a high-level function to be used mainly from outside the
-'dict' directory. Inside this directory dict_table_get_low
-is usually the appropriate function.
-@return table, NULL if does not exist */
-UNIV_INTERN
-dict_table_t*
-dict_table_open_on_name(
-/*====================*/
- const char* table_name, /*!< in: table name */
- ibool dict_locked) /*!< in: TRUE=data dictionary locked */
-{
- dict_table_t* table;
-
- table = dict_table_open_on_name_low(table_name, dict_locked,
- DICT_ERR_IGNORE_NONE);
-
- if (table != NULL) {
- /* If table->ibd_file_missing == TRUE, this will
- print an error message and return without doing
- anything. */
- dict_stats_update(table,
- DICT_STATS_FETCH_ONLY_IF_NOT_IN_MEMORY,
- dict_locked);
- }
-
- return(table);
-}
-
-/**********************************************************************//**
-Returns a table object and increment its open handle count. Table
-statistics will not be updated if they are not initialized.
-Call this function when dropping a table.
-@return table, NULL if does not exist */
-UNIV_INTERN
-dict_table_t*
-dict_table_open_on_name_no_stats(
-/*=============================*/
- const char* table_name, /*!< in: table name */
- ibool dict_locked, /*!< in: TRUE=data dictionary locked */
- dict_err_ignore_t
- ignore_err) /*!< in: error to be ignored during
- table open */
-{
- return(dict_table_open_on_name_low(table_name, dict_locked,
- ignore_err));
-}
-
#endif /* !UNIV_HOTBACKUP */
/**********************************************************************//**
@@ -1156,7 +1190,7 @@ dict_table_can_be_evicted(
index != NULL;
index = dict_table_get_next_index(index)) {
- btr_search_t* info = index->search_info;
+ btr_search_t* info = btr_search_get_info(index);
/* We are not allowed to free the in-memory index
struct dict_index_t until all entries in the adaptive
@@ -1358,7 +1392,7 @@ dict_index_find_on_id_low(
Renames a table object.
@return TRUE if success */
UNIV_INTERN
-ibool
+dberr_t
dict_table_rename_in_cache(
/*=======================*/
dict_table_t* table, /*!< in/out: table */
@@ -1372,7 +1406,6 @@ dict_table_rename_in_cache(
ulint fold;
char old_name[MAX_FULL_NAME_LEN + 1];
- ut_ad(table);
ut_ad(mutex_own(&(dict_sys->mutex)));
/* store the old/current name to an automatic variable */
@@ -1389,28 +1422,59 @@ dict_table_rename_in_cache(
fold = ut_fold_string(new_name);
/* Look for a table with the same name: error if such exists */
- {
- dict_table_t* table2;
- HASH_SEARCH(name_hash, dict_sys->table_hash, fold,
- dict_table_t*, table2, ut_ad(table2->cached),
- (ut_strcmp(table2->name, new_name) == 0));
- if (UNIV_LIKELY_NULL(table2)) {
- ut_print_timestamp(stderr);
- fputs(" InnoDB: Error: dictionary cache"
- " already contains a table ", stderr);
- ut_print_name(stderr, NULL, TRUE, new_name);
- fputs("\n"
- "InnoDB: cannot rename table ", stderr);
- ut_print_name(stderr, NULL, TRUE, old_name);
- putc('\n', stderr);
- return(FALSE);
- }
+ dict_table_t* table2;
+ HASH_SEARCH(name_hash, dict_sys->table_hash, fold,
+ dict_table_t*, table2, ut_ad(table2->cached),
+ (ut_strcmp(table2->name, new_name) == 0));
+ DBUG_EXECUTE_IF("dict_table_rename_in_cache_failure",
+ if (table2 == NULL) {
+ table2 = (dict_table_t*) -1;
+ } );
+ if (table2) {
+ ib_logf(IB_LOG_LEVEL_ERROR,
+ "Cannot rename table '%s' to '%s' since the "
+ "dictionary cache already contains '%s'.",
+ old_name, new_name, new_name);
+ return(DB_ERROR);
}
/* If the table is stored in a single-table tablespace, rename the
- .ibd file */
+ .ibd file and rebuild the .isl file if needed. */
+
+ if (dict_table_is_discarded(table)) {
+ os_file_type_t type;
+ ibool exists;
+ char* filepath;
+
+ ut_ad(table->space != TRX_SYS_SPACE);
+
+ if (DICT_TF_HAS_DATA_DIR(table->flags)) {
+
+ dict_get_and_save_data_dir_path(table, true);
+ ut_a(table->data_dir_path);
+
+ filepath = os_file_make_remote_pathname(
+ table->data_dir_path, table->name, "ibd");
+ } else {
+ filepath = fil_make_ibd_name(table->name, false);
+ }
+
+ fil_delete_tablespace(table->space, BUF_REMOVE_FLUSH_NO_WRITE);
+
+ /* Delete any temp file hanging around. */
+ if (os_file_status(filepath, &exists, &type)
+ && exists
+ && !os_file_delete_if_exists(filepath)) {
+
+ ib_logf(IB_LOG_LEVEL_INFO,
+ "Delete of %s failed.", filepath);
+ }
+
+ mem_free(filepath);
+
+ } else if (table->space != TRX_SYS_SPACE) {
+ char* new_path = NULL;
- if (table->space != 0) {
if (table->dir_path_of_temp_table != NULL) {
ut_print_timestamp(stderr);
fputs(" InnoDB: Error: trying to rename a"
@@ -1420,10 +1484,40 @@ dict_table_rename_in_cache(
ut_print_filename(stderr,
table->dir_path_of_temp_table);
fputs(" )\n", stderr);
- return(FALSE);
- } else if (!fil_rename_tablespace(old_name, table->space,
- new_name)) {
- return(FALSE);
+ return(DB_ERROR);
+
+ } else if (DICT_TF_HAS_DATA_DIR(table->flags)) {
+ char* old_path;
+
+ old_path = fil_space_get_first_path(table->space);
+
+ new_path = os_file_make_new_pathname(
+ old_path, new_name);
+
+ mem_free(old_path);
+
+ dberr_t err = fil_create_link_file(
+ new_name, new_path);
+
+ if (err != DB_SUCCESS) {
+ mem_free(new_path);
+ return(DB_TABLESPACE_EXISTS);
+ }
+ }
+
+ ibool success = fil_rename_tablespace(
+ old_name, table->space, new_name, new_path);
+
+ /* If the tablespace is remote, a new .isl file was created
+ If success, delete the old one. If not, delete the new one. */
+ if (new_path) {
+
+ mem_free(new_path);
+ fil_delete_link_file(success ? old_name : new_name);
+ }
+
+ if (!success) {
+ return(DB_ERROR);
}
}
@@ -1450,12 +1544,11 @@ dict_table_rename_in_cache(
ut_a(dict_sys->size > 0);
/* Update the table_name field in indexes */
- index = dict_table_get_first_index(table);
+ for (index = dict_table_get_first_index(table);
+ index != NULL;
+ index = dict_table_get_next_index(index)) {
- while (index != NULL) {
index->table_name = table->name;
-
- index = dict_table_get_next_index(index);
}
if (!rename_also_foreigns) {
@@ -1490,7 +1583,7 @@ dict_table_rename_in_cache(
UT_LIST_INIT(table->referenced_list);
- return(TRUE);
+ return(DB_SUCCESS);
}
/* Update the table name fields in foreign constraints, and update also
@@ -1571,9 +1664,10 @@ dict_table_rename_in_cache(
foreign = UT_LIST_GET_NEXT(foreign_list, foreign);
}
- foreign = UT_LIST_GET_FIRST(table->referenced_list);
+ for (foreign = UT_LIST_GET_FIRST(table->referenced_list);
+ foreign != NULL;
+ foreign = UT_LIST_GET_NEXT(referenced_list, foreign)) {
- while (foreign != NULL) {
if (ut_strlen(foreign->referenced_table_name)
< ut_strlen(table->name)) {
/* Allocate a longer name buffer;
@@ -1581,16 +1675,19 @@ dict_table_rename_in_cache(
foreign->referenced_table_name = mem_heap_strdup(
foreign->heap, table->name);
- dict_mem_referenced_table_name_lookup_set(foreign, TRUE);
+
+ dict_mem_referenced_table_name_lookup_set(
+ foreign, TRUE);
} else {
/* Use the same buffer */
strcpy(foreign->referenced_table_name, table->name);
- dict_mem_referenced_table_name_lookup_set(foreign, FALSE);
+
+ dict_mem_referenced_table_name_lookup_set(
+ foreign, FALSE);
}
- foreign = UT_LIST_GET_NEXT(referenced_list, foreign);
}
- return(TRUE);
+ return(DB_SUCCESS);
}
/**********************************************************************//**
@@ -1692,6 +1789,30 @@ dict_table_remove_from_cache_low(
ut_ad(dict_lru_validate());
+ if (lru_evict && table->drop_aborted) {
+ /* Do as dict_table_try_drop_aborted() does. */
+
+ trx_t* trx = trx_allocate_for_background();
+
+ ut_ad(mutex_own(&dict_sys->mutex));
+#ifdef UNIV_SYNC_DEBUG
+ ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
+#endif /* UNIV_SYNC_DEBUG */
+ /* Mimic row_mysql_lock_data_dictionary(). */
+ trx->dict_operation_lock_mode = RW_X_LATCH;
+
+ trx_set_dict_operation(trx, TRX_DICT_OP_INDEX);
+
+ /* Silence a debug assertion in row_merge_drop_indexes(). */
+ ut_d(table->n_ref_count++);
+ row_merge_drop_indexes(trx, table, TRUE);
+ ut_d(table->n_ref_count--);
+ ut_ad(table->n_ref_count == 0);
+ trx_commit_for_mysql(trx);
+ trx->dict_operation_lock_mode = 0;
+ trx_free_for_background(trx);
+ }
+
size = mem_heap_get_size(table->heap) + strlen(table->name) + 1;
ut_ad(dict_sys->size >= size);
@@ -1777,6 +1898,12 @@ dict_index_too_big_for_undo(
+ 10 + FIL_PAGE_DATA_END /* trx_undo_left() */
+ 2/* pointer to previous undo log record */;
+ /* FTS index consists of auxiliary tables, they shall be excluded from
+ index row size check */
+ if (new_index->type & DICT_FTS) {
+ return(false);
+ }
+
if (!clust_index) {
ut_a(dict_index_is_clust(new_index));
clust_index = new_index;
@@ -1900,6 +2027,12 @@ dict_index_too_big_for_tree(
/* maximum allowed size of a node pointer record */
ulint page_ptr_max;
+ /* FTS index consists of auxiliary tables, they shall be excluded from
+ index row size check */
+ if (new_index->type & DICT_FTS) {
+ return(false);
+ }
+
comp = dict_table_is_comp(table);
zip_size = dict_table_zip_size(table);
@@ -2032,7 +2165,7 @@ add_field_size:
Adds an index to the dictionary cache.
@return DB_SUCCESS, DB_TOO_BIG_RECORD, or DB_CORRUPTION */
UNIV_INTERN
-ulint
+dberr_t
dict_index_add_to_cache(
/*====================*/
dict_table_t* table, /*!< in: table on which the index is */
@@ -2051,6 +2184,7 @@ dict_index_add_to_cache(
ut_ad(mutex_own(&(dict_sys->mutex)));
ut_ad(index->n_def == index->n_fields);
ut_ad(index->magic_n == DICT_INDEX_MAGIC_N);
+ ut_ad(!dict_index_is_online_ddl(index));
ut_ad(mem_heap_validate(index->heap));
ut_a(!dict_index_is_clust(index)
@@ -2077,6 +2211,7 @@ dict_index_add_to_cache(
number of fields in the cache internal representation */
new_index->n_fields = new_index->n_def;
+ new_index->trx_id = index->trx_id;
if (strict && dict_index_too_big_for_tree(table, new_index)) {
too_big:
@@ -2169,51 +2304,41 @@ undo_size_ok:
}
}
- /* Add the new index as the last index for the table */
-
- UT_LIST_ADD_LAST(indexes, table->indexes, new_index);
- new_index->table = table;
- new_index->table_name = table->name;
-
- new_index->search_info = btr_search_info_create(new_index->heap);
-
- new_index->stat_index_size = 1;
- new_index->stat_n_leaf_pages = 1;
-
- new_index->page = page_no;
- rw_lock_create(index_tree_rw_lock_key, &new_index->lock,
- dict_index_is_ibuf(index)
- ? SYNC_IBUF_INDEX_TREE : SYNC_INDEX_TREE);
-
if (!dict_index_is_univ(new_index)) {
new_index->stat_n_diff_key_vals =
- static_cast<ib_uint64_t*>(mem_heap_alloc(
+ static_cast<ib_uint64_t*>(mem_heap_zalloc(
new_index->heap,
- (1 + dict_index_get_n_unique(new_index))
+ dict_index_get_n_unique(new_index)
* sizeof(*new_index->stat_n_diff_key_vals)));
new_index->stat_n_sample_sizes =
- static_cast<ib_uint64_t*>(mem_heap_alloc(
+ static_cast<ib_uint64_t*>(mem_heap_zalloc(
new_index->heap,
- (1 + dict_index_get_n_unique(new_index))
+ dict_index_get_n_unique(new_index)
* sizeof(*new_index->stat_n_sample_sizes)));
new_index->stat_n_non_null_key_vals =
static_cast<ib_uint64_t*>(mem_heap_zalloc(
new_index->heap,
- (1 + dict_index_get_n_unique(new_index))
+ dict_index_get_n_unique(new_index)
* sizeof(*new_index->stat_n_non_null_key_vals)));
+ }
- /* Give some sensible values to stat_n_... in case we do
- not calculate statistics quickly enough */
+ new_index->stat_index_size = 1;
+ new_index->stat_n_leaf_pages = 1;
- for (i = 0; i <= dict_index_get_n_unique(new_index); i++) {
+ /* Add the new index as the last index for the table */
- new_index->stat_n_diff_key_vals[i] = 100;
- new_index->stat_n_sample_sizes[i] = 0;
- }
- }
+ UT_LIST_ADD_LAST(indexes, table->indexes, new_index);
+ new_index->table = table;
+ new_index->table_name = table->name;
+ new_index->search_info = btr_search_info_create(new_index->heap);
+
+ new_index->page = page_no;
+ rw_lock_create(index_tree_rw_lock_key, &new_index->lock,
+ dict_index_is_ibuf(index)
+ ? SYNC_IBUF_INDEX_TREE : SYNC_INDEX_TREE);
dict_sys->size += mem_heap_get_size(new_index->heap);
@@ -2242,9 +2367,17 @@ dict_index_remove_from_cache_low(
ut_ad(index->magic_n == DICT_INDEX_MAGIC_N);
ut_ad(mutex_own(&(dict_sys->mutex)));
+ /* No need to acquire the dict_index_t::lock here because
+ there can't be any active operations on this index (or table). */
+
+ if (index->online_log) {
+ ut_ad(index->online_status == ONLINE_INDEX_CREATION);
+ row_log_free(index->online_log);
+ }
+
/* We always create search info whether or not adaptive
hash index is enabled or not. */
- info = index->search_info;
+ info = btr_search_get_info(index);
ut_ad(info);
/* We are not allowed to free the in-memory index struct
@@ -2270,15 +2403,15 @@ dict_index_remove_from_cache_low(
if (retries % 500 == 0) {
/* No luck after 5 seconds of wait. */
fprintf(stderr, "InnoDB: Error: Waited for"
- " %lu secs for hash index"
- " ref_count (%lu) to drop"
- " to 0.\n"
- "index: \"%s\""
- " table: \"%s\"\n",
- retries/100,
- ref_count,
- index->name,
- table->name);
+ " %lu secs for hash index"
+ " ref_count (%lu) to drop"
+ " to 0.\n"
+ "index: \"%s\""
+ " table: \"%s\"\n",
+ retries/100,
+ ref_count,
+ index->name,
+ table->name);
}
/* To avoid a hang here we commit suicide if the
@@ -2821,8 +2954,6 @@ dict_index_build_internal_fts(
return(new_index);
}
-
-#ifndef UNIV_HOTBACKUP
/*====================== FOREIGN KEY PROCESSING ========================*/
/*********************************************************************//**
@@ -2889,8 +3020,7 @@ dict_table_get_foreign_constraint(
foreign;
foreign = UT_LIST_GET_NEXT(foreign_list, foreign)) {
- if (foreign->foreign_index == index
- || foreign->referenced_index == index) {
+ if (foreign->foreign_index == index) {
return(foreign);
}
@@ -2901,7 +3031,7 @@ dict_table_get_foreign_constraint(
/*********************************************************************//**
Frees a foreign key struct. */
-static
+UNIV_INTERN
void
dict_foreign_free(
/*==============*/
@@ -2912,7 +3042,7 @@ dict_foreign_free(
/**********************************************************************//**
Removes a foreign constraint struct from the dictionary cache. */
-static
+UNIV_INTERN
void
dict_foreign_remove_from_cache(
/*===========================*/
@@ -2976,84 +3106,50 @@ dict_foreign_find(
return(NULL);
}
+
/*********************************************************************//**
Tries to find an index whose first fields are the columns in the array,
in the same order and is not marked for deletion and is not the same
as types_idx.
@return matching index, NULL if not found */
-static
+UNIV_INTERN
dict_index_t*
dict_foreign_find_index(
/*====================*/
- dict_table_t* table, /*!< in: table */
- const char** columns,/*!< in: array of column names */
- ulint n_cols, /*!< in: number of columns */
- dict_index_t* types_idx, /*!< in: NULL or an index to whose types the
- column types must match */
- ibool check_charsets,
- /*!< in: whether to check charsets.
- only has an effect if types_idx != NULL */
- ulint check_null)
- /*!< in: nonzero if none of the columns must
- be declared NOT NULL */
+ const dict_table_t* table, /*!< in: table */
+ const char** columns,/*!< in: array of column names */
+ ulint n_cols, /*!< in: number of columns */
+ const dict_index_t* types_idx,
+ /*!< in: NULL or an index
+ whose types the column types
+ must match */
+ ibool check_charsets,
+ /*!< in: whether to check
+ charsets. only has an effect
+ if types_idx != NULL */
+ ulint check_null)
+ /*!< in: nonzero if none of
+ the columns must be declared
+ NOT NULL */
{
dict_index_t* index;
+ ut_ad(mutex_own(&dict_sys->mutex));
+
index = dict_table_get_first_index(table);
while (index != NULL) {
/* Ignore matches that refer to the same instance
- or the index is to be dropped */
- if (index->to_be_dropped || types_idx == index
- || index->type & DICT_FTS) {
+ (or the index is to be dropped) */
+ if (types_idx == index || index->type & DICT_FTS
+ || index->to_be_dropped) {
goto next_rec;
- } else if (dict_index_get_n_fields(index) >= n_cols) {
- ulint i;
-
- for (i = 0; i < n_cols; i++) {
- dict_field_t* field;
- const char* col_name;
-
- field = dict_index_get_nth_field(index, i);
-
- col_name = dict_table_get_col_name(
- table, dict_col_get_no(field->col));
-
- if (field->prefix_len != 0) {
- /* We do not accept column prefix
- indexes here */
-
- break;
- }
-
- if (0 != innobase_strcasecmp(columns[i],
- col_name)) {
- break;
- }
-
- if (check_null
- && (field->col->prtype & DATA_NOT_NULL)) {
-
- return(NULL);
- }
-
- if (types_idx && !cmp_cols_are_equal(
- dict_index_get_nth_col(index, i),
- dict_index_get_nth_col(types_idx,
- i),
- check_charsets)) {
-
- break;
- }
- }
-
- if (i == n_cols) {
- /* We found a matching index */
-
- return(index);
- }
+ } else if (dict_foreign_qualify_index(
+ table, columns, n_cols, index, types_idx,
+ check_charsets, check_null)) {
+ return(index);
}
next_rec:
@@ -3064,90 +3160,6 @@ next_rec:
}
/**********************************************************************//**
-Find an index that is equivalent to the one passed in and is not marked
-for deletion.
-@return index equivalent to foreign->foreign_index, or NULL */
-UNIV_INTERN
-dict_index_t*
-dict_foreign_find_equiv_index(
-/*==========================*/
- dict_foreign_t* foreign)/*!< in: foreign key */
-{
- ut_a(foreign != NULL);
-
- /* Try to find an index which contains the columns as the
- first fields and in the right order, and the types are the
- same as in foreign->foreign_index */
-
- return(dict_foreign_find_index(
- foreign->foreign_table,
- foreign->foreign_col_names, foreign->n_fields,
- foreign->foreign_index, TRUE, /* check types */
- FALSE/* allow columns to be NULL */));
-}
-
-#endif /* !UNIV_HOTBACKUP */
-/**********************************************************************//**
-Returns an index object by matching on the name and column names and
-if more than one index matches return the index with the max id
-@return matching index, NULL if not found */
-UNIV_INTERN
-dict_index_t*
-dict_table_get_index_by_max_id(
-/*===========================*/
- dict_table_t* table, /*!< in: table */
- const char* name, /*!< in: the index name to find */
- const char** columns,/*!< in: array of column names */
- ulint n_cols) /*!< in: number of columns */
-{
- dict_index_t* index;
- dict_index_t* found;
-
- found = NULL;
- index = dict_table_get_first_index(table);
-
- while (index != NULL) {
- if (ut_strcmp(index->name, name) == 0
- && dict_index_get_n_ordering_defined_by_user(index)
- == n_cols) {
-
- ulint i;
-
- for (i = 0; i < n_cols; i++) {
- dict_field_t* field;
- const char* col_name;
-
- field = dict_index_get_nth_field(index, i);
-
- col_name = dict_table_get_col_name(
- table, dict_col_get_no(field->col));
-
- if (0 != innobase_strcasecmp(
- columns[i], col_name)) {
-
- break;
- }
- }
-
- if (i == n_cols) {
- /* We found a matching index, select
- the index with the higher id*/
-
- if (!found || index->id > found->id) {
-
- found = index;
- }
- }
- }
-
- index = dict_table_get_next_index(index);
- }
-
- return(found);
-}
-
-#ifndef UNIV_HOTBACKUP
-/**********************************************************************//**
Report an error in a foreign key definition. */
static
void
@@ -3196,7 +3208,7 @@ At least one of the foreign table and the referenced table must already
be in the dictionary cache!
@return DB_SUCCESS or error code */
UNIV_INTERN
-ulint
+dberr_t
dict_foreign_add_to_cache(
/*======================*/
dict_foreign_t* foreign, /*!< in, own: foreign key constraint */
@@ -3325,7 +3337,6 @@ dict_foreign_add_to_cache(
return(DB_SUCCESS);
}
-#endif /* !UNIV_HOTBACKUP */
/*********************************************************************//**
Scans from pointer onwards. Stops if is at the start of a copy of
'string' where characters are compared without case sensitivity, and
@@ -3579,6 +3590,67 @@ dict_scan_col(
return(ptr);
}
+
+/*********************************************************************//**
+Open a table from its database and table name, this is currently used by
+foreign constraint parser to get the referenced table.
+@return complete table name with database and table name, allocated from
+heap memory passed in */
+UNIV_INTERN
+char*
+dict_get_referenced_table(
+/*======================*/
+ const char* name, /*!< in: foreign key table name */
+ const char* database_name, /*!< in: table db name */
+ ulint database_name_len, /*!< in: db name length */
+ const char* table_name, /*!< in: table name */
+ ulint table_name_len, /*!< in: table name length */
+ dict_table_t** table, /*!< out: table object or NULL */
+ mem_heap_t* heap) /*!< in/out: heap memory */
+{
+ char* ref;
+ const char* db_name;
+
+ if (!database_name) {
+ /* Use the database name of the foreign key table */
+
+ db_name = name;
+ database_name_len = dict_get_db_name_len(name);
+ } else {
+ db_name = database_name;
+ }
+
+ /* Copy database_name, '/', table_name, '\0' */
+ ref = static_cast<char*>(
+ mem_heap_alloc(heap, database_name_len + table_name_len + 2));
+
+ memcpy(ref, db_name, database_name_len);
+ ref[database_name_len] = '/';
+ memcpy(ref + database_name_len + 1, table_name, table_name_len + 1);
+
+ /* Values; 0 = Store and compare as given; case sensitive
+ 1 = Store and compare in lower; case insensitive
+ 2 = Store as given, compare in lower; case semi-sensitive */
+ if (innobase_get_lower_case_table_names() == 2) {
+ innobase_casedn_str(ref);
+ *table = dict_table_get_low(ref);
+ memcpy(ref, db_name, database_name_len);
+ ref[database_name_len] = '/';
+ memcpy(ref + database_name_len + 1, table_name, table_name_len + 1);
+
+ } else {
+#ifndef __WIN__
+ if (innobase_get_lower_case_table_names() == 1) {
+ innobase_casedn_str(ref);
+ }
+#else
+ innobase_casedn_str(ref);
+#endif /* !__WIN__ */
+ *table = dict_table_get_low(ref);
+ }
+
+ return(ref);
+}
/*********************************************************************//**
Scans a table name from an SQL string.
@return scanned to */
@@ -3598,9 +3670,7 @@ dict_scan_table_name(
const char* database_name = NULL;
ulint database_name_len = 0;
const char* table_name = NULL;
- ulint table_name_len;
const char* scan_name;
- char* ref;
*success = FALSE;
*table = NULL;
@@ -3648,46 +3718,11 @@ dict_scan_table_name(
table_name = scan_name;
}
- if (database_name == NULL) {
- /* Use the database name of the foreign key table */
-
- database_name = name;
- database_name_len = dict_get_db_name_len(name);
- }
-
- table_name_len = strlen(table_name);
-
- /* Copy database_name, '/', table_name, '\0' */
- ref = static_cast<char*>(
- mem_heap_alloc(heap, database_name_len + table_name_len + 2));
-
- memcpy(ref, database_name, database_name_len);
- ref[database_name_len] = '/';
- memcpy(ref + database_name_len + 1, table_name, table_name_len + 1);
-
- /* Values; 0 = Store and compare as given; case sensitive
- 1 = Store and compare in lower; case insensitive
- 2 = Store as given, compare in lower; case semi-sensitive */
- if (innobase_get_lower_case_table_names() == 2) {
- innobase_casedn_str(ref);
- *table = dict_table_get_low(ref);
- memcpy(ref, database_name, database_name_len);
- ref[database_name_len] = '/';
- memcpy(ref + database_name_len + 1, table_name, table_name_len + 1);
-
- } else {
-#ifndef __WIN__
- if (innobase_get_lower_case_table_names() == 1) {
- innobase_casedn_str(ref);
- }
-#else
- innobase_casedn_str(ref);
-#endif /* !__WIN__ */
- *table = dict_table_get_low(ref);
- }
+ *ref_name = dict_get_referenced_table(
+ name, database_name, database_name_len,
+ table_name, strlen(table_name), table, heap);
*success = TRUE;
- *ref_name = ref;
return(ptr);
}
@@ -3810,13 +3845,12 @@ end_of_string:
}
}
-#ifndef UNIV_HOTBACKUP
/*********************************************************************//**
Finds the highest [number] for foreign key constraints of the table. Looks
only at the >= 4.0.18-format id's, which are of the form
databasename/tablename_ibfk_[number].
@return highest number, 0 if table has no new format foreign key constraints */
-static
+UNIV_INTERN
ulint
dict_table_get_highest_foreign_id(
/*==============================*/
@@ -3871,6 +3905,8 @@ dict_foreign_report_syntax_err(
in the SQL string */
const char* ptr) /*!< in: place of the syntax error */
{
+ ut_ad(!srv_read_only_mode);
+
FILE* ef = dict_foreign_err_file;
mutex_enter(&dict_foreign_err_mutex);
@@ -3888,7 +3924,7 @@ be accompanied with indexes in both participating tables. The indexes are
allowed to contain more fields than mentioned in the constraint.
@return error code or DB_SUCCESS */
static
-ulint
+dberr_t
dict_create_foreign_constraints_low(
/*================================*/
trx_t* trx, /*!< in: transaction */
@@ -3919,7 +3955,7 @@ dict_create_foreign_constraints_low(
FILE* ef = dict_foreign_err_file;
const char* constraint_name;
ibool success;
- ulint error;
+ dberr_t error;
const char* ptr1;
const char* ptr2;
ulint i;
@@ -3931,6 +3967,7 @@ dict_create_foreign_constraints_low(
const char* column_names[500];
const char* referenced_table_name;
+ ut_ad(!srv_read_only_mode);
ut_ad(mutex_own(&(dict_sys->mutex)));
table = dict_table_get_low(name);
@@ -4470,11 +4507,11 @@ UNIV_INTERN
ibool
dict_str_starts_with_keyword(
/*=========================*/
- void* mysql_thd, /*!< in: MySQL thread handle */
+ THD* thd, /*!< in: MySQL thread handle */
const char* str, /*!< in: string to scan for keyword */
const char* keyword) /*!< in: keyword to look for */
{
- struct charset_info_st* cs = innobase_get_charset(mysql_thd);
+ struct charset_info_st* cs = innobase_get_charset(thd);
ibool success;
dict_accept(cs, str, keyword, &success);
@@ -4489,7 +4526,7 @@ be accompanied with indexes in both participating tables. The indexes are
allowed to contain more fields than mentioned in the constraint.
@return error code or DB_SUCCESS */
UNIV_INTERN
-ulint
+dberr_t
dict_create_foreign_constraints(
/*============================*/
trx_t* trx, /*!< in: transaction */
@@ -4509,9 +4546,9 @@ dict_create_foreign_constraints(
code DB_CANNOT_ADD_CONSTRAINT if
any foreign keys are found. */
{
- char* str;
- ulint err;
- mem_heap_t* heap;
+ char* str;
+ dberr_t err;
+ mem_heap_t* heap;
ut_a(trx);
ut_a(trx->mysql_thd);
@@ -4534,7 +4571,7 @@ Parses the CONSTRAINT id's to be dropped in an ALTER TABLE statement.
@return DB_SUCCESS or DB_CANNOT_DROP_CONSTRAINT if syntax error or the
constraint id does not match */
UNIV_INTERN
-ulint
+dberr_t
dict_foreign_parse_drop_constraints(
/*================================*/
mem_heap_t* heap, /*!< in: heap from which we can
@@ -4552,7 +4589,6 @@ dict_foreign_parse_drop_constraints(
size_t len;
const char* ptr;
const char* id;
- FILE* ef = dict_foreign_err_file;
struct charset_info_st* cs;
ut_a(trx);
@@ -4618,10 +4654,11 @@ loop:
foreign = UT_LIST_GET_FIRST(table->foreign_list);
while (foreign != NULL) {
- if (0 == strcmp(foreign->id, id)
+ if (0 == innobase_strcasecmp(foreign->id, id)
|| (strchr(foreign->id, '/')
- && 0 == strcmp(id,
- dict_remove_db_name(foreign->id)))) {
+ && 0 == innobase_strcasecmp(
+ id,
+ dict_remove_db_name(foreign->id)))) {
/* Found */
break;
}
@@ -4629,20 +4666,26 @@ loop:
foreign = UT_LIST_GET_NEXT(foreign_list, foreign);
}
+
if (foreign == NULL) {
- mutex_enter(&dict_foreign_err_mutex);
- rewind(ef);
- ut_print_timestamp(ef);
- fputs(" Error in dropping of a foreign key constraint"
- " of table ", ef);
- ut_print_name(ef, NULL, TRUE, table->name);
- fputs(",\n"
- "in SQL command\n", ef);
- fputs(str, ef);
- fputs("\nCannot find a constraint with the given id ", ef);
- ut_print_name(ef, NULL, FALSE, id);
- fputs(".\n", ef);
- mutex_exit(&dict_foreign_err_mutex);
+
+ if (!srv_read_only_mode) {
+ FILE* ef = dict_foreign_err_file;
+
+ mutex_enter(&dict_foreign_err_mutex);
+ rewind(ef);
+ ut_print_timestamp(ef);
+ fputs(" Error in dropping of a foreign key "
+ "constraint of table ", ef);
+ ut_print_name(ef, NULL, TRUE, table->name);
+ fputs(",\nin SQL command\n", ef);
+ fputs(str, ef);
+ fputs("\nCannot find a constraint with the "
+ "given id ", ef);
+ ut_print_name(ef, NULL, FALSE, id);
+ fputs(".\n", ef);
+ mutex_exit(&dict_foreign_err_mutex);
+ }
mem_free(str);
@@ -4652,15 +4695,19 @@ loop:
goto loop;
syntax_error:
- mutex_enter(&dict_foreign_err_mutex);
- rewind(ef);
- ut_print_timestamp(ef);
- fputs(" Syntax error in dropping of a"
- " foreign key constraint of table ", ef);
- ut_print_name(ef, NULL, TRUE, table->name);
- fprintf(ef, ",\n"
- "close to:\n%s\n in SQL command\n%s\n", ptr, str);
- mutex_exit(&dict_foreign_err_mutex);
+ if (!srv_read_only_mode) {
+ FILE* ef = dict_foreign_err_file;
+
+ mutex_enter(&dict_foreign_err_mutex);
+ rewind(ef);
+ ut_print_timestamp(ef);
+ fputs(" Syntax error in dropping of a"
+ " foreign key constraint of table ", ef);
+ ut_print_name(ef, NULL, TRUE, table->name);
+ fprintf(ef, ",\n"
+ "close to:\n%s\n in SQL command\n%s\n", ptr, str);
+ mutex_exit(&dict_foreign_err_mutex);
+ }
mem_free(str);
@@ -4668,7 +4715,7 @@ syntax_error:
}
/*==================== END OF FOREIGN KEY PROCESSING ====================*/
-#endif /* !UNIV_HOTBACKUP */
+
/**********************************************************************//**
Returns an index object if it is found in the dictionary cache.
Assumes that dict_sys->mutex is already being held.
@@ -4908,7 +4955,6 @@ dict_index_calc_min_rec_len(
return(sum);
}
-#ifndef UNIV_HOTBACKUP
/**********************************************************************//**
Prints info of a foreign key constraint. */
static
@@ -4939,7 +4985,6 @@ dict_foreign_print_low(
fputs(" )\n", stderr);
}
-#endif /* !UNIV_HOTBACKUP */
/**********************************************************************//**
Prints a table data. */
UNIV_INTERN
@@ -4948,60 +4993,29 @@ dict_table_print(
/*=============*/
dict_table_t* table) /*!< in: table */
{
- mutex_enter(&(dict_sys->mutex));
- dict_table_print_low(table);
- mutex_exit(&(dict_sys->mutex));
-}
-
-/**********************************************************************//**
-Prints a table data when we know the table name. */
-UNIV_INTERN
-void
-dict_table_print_by_name(
-/*=====================*/
- const char* name) /*!< in: table name */
-{
- dict_table_t* table;
-
- mutex_enter(&(dict_sys->mutex));
-
- table = dict_table_get_low(name);
-
- ut_a(table);
-
- dict_table_print_low(table);
- mutex_exit(&(dict_sys->mutex));
-}
-
-/**********************************************************************//**
-Prints a table data. */
-UNIV_INTERN
-void
-dict_table_print_low(
-/*=================*/
- dict_table_t* table) /*!< in: table */
-{
dict_index_t* index;
dict_foreign_t* foreign;
ulint i;
ut_ad(mutex_own(&(dict_sys->mutex)));
- dict_stats_update(table, DICT_STATS_FETCH, TRUE);
+ dict_table_stats_lock(table, RW_X_LATCH);
- dict_table_stats_lock(table, RW_S_LATCH);
+ if (!table->stat_initialized) {
+ dict_stats_update_transient(table);
+ }
fprintf(stderr,
"--------------------------------------\n"
"TABLE: name %s, id %llu, flags %lx, columns %lu,"
- " indexes %lu, appr.rows %lu\n"
+ " indexes %lu, appr.rows " UINT64PF "\n"
" COLUMNS: ",
table->name,
(ullint) table->id,
(ulong) table->flags,
(ulong) table->n_cols,
(ulong) UT_LIST_GET_LEN(table->indexes),
- (ulong) table->stat_n_rows);
+ table->stat_n_rows);
for (i = 0; i < (ulint) table->n_cols; i++) {
dict_col_print_low(table, dict_table_get_nth_col(table, i));
@@ -5017,7 +5031,9 @@ dict_table_print_low(
index = UT_LIST_GET_NEXT(indexes, index);
}
- dict_table_stats_unlock(table, RW_S_LATCH);
+ table->stat_initialized = FALSE;
+
+ dict_table_stats_unlock(table, RW_X_LATCH);
foreign = UT_LIST_GET_FIRST(table->foreign_list);
@@ -5065,13 +5081,15 @@ dict_index_print_low(
ib_int64_t n_vals;
ulint i;
+ ut_a(index->table->stat_initialized);
+
ut_ad(mutex_own(&(dict_sys->mutex)));
if (index->n_user_defined_cols > 0) {
n_vals = index->stat_n_diff_key_vals[
- index->n_user_defined_cols];
+ index->n_user_defined_cols - 1];
} else {
- n_vals = index->stat_n_diff_key_vals[1];
+ n_vals = index->stat_n_diff_key_vals[0];
}
fprintf(stderr,
@@ -5121,7 +5139,6 @@ dict_field_print_low(
}
}
-#ifndef UNIV_HOTBACKUP
/**********************************************************************//**
Outputs info on a foreign key of a table in a format suitable for
CREATE TABLE. */
@@ -5310,7 +5327,6 @@ dict_print_info_on_foreign_keys(
mutex_exit(&(dict_sys->mutex));
}
-#endif /* !UNIV_HOTBACKUP */
/********************************************************************//**
Displays the names of the index and the table. */
UNIV_INTERN
@@ -5318,7 +5334,7 @@ void
dict_index_name_print(
/*==================*/
FILE* file, /*!< in: output stream */
- trx_t* trx, /*!< in: transaction */
+ const trx_t* trx, /*!< in: transaction */
const dict_index_t* index) /*!< in: index to print */
{
fputs("index ", file);
@@ -5393,7 +5409,9 @@ UNIV_INTERN
void
dict_set_corrupted(
/*===============*/
- dict_index_t* index) /*!< in/out: index */
+ dict_index_t* index, /*!< in/out: index */
+ trx_t* trx, /*!< in/out: transaction */
+ const char* ctx) /*!< in: context */
{
mem_heap_t* heap;
mtr_t mtr;
@@ -5401,8 +5419,14 @@ dict_set_corrupted(
dtuple_t* tuple;
dfield_t* dfield;
byte* buf;
+ char* table_name;
const char* status;
btr_cur_t cursor;
+ bool locked = RW_X_LATCH == trx->dict_operation_lock_mode;
+
+ if (!locked) {
+ row_mysql_lock_data_dictionary(trx);
+ }
ut_ad(index);
ut_ad(mutex_own(&dict_sys->mutex));
@@ -5422,7 +5446,7 @@ dict_set_corrupted(
if (index->type & DICT_CORRUPT) {
/* The index was already flagged corrupted. */
ut_ad(!dict_index_is_clust(index) || index->table->corrupted);
- return;
+ goto func_exit;
}
heap = mem_heap_create(sizeof(dtuple_t) + 2 * (sizeof(dfield_t)
@@ -5463,19 +5487,29 @@ dict_set_corrupted(
goto fail;
}
mlog_write_ulint(field, index->type, MLOG_4BYTES, &mtr);
- status = " InnoDB: Flagged corruption of ";
+ status = "Flagged";
} else {
fail:
- status = " InnoDB: Unable to flag corruption of ";
+ status = "Unable to flag";
}
mtr_commit(&mtr);
+ mem_heap_empty(heap);
+ table_name = static_cast<char*>(mem_heap_alloc(heap, FN_REFLEN + 1));
+ *innobase_convert_name(
+ table_name, FN_REFLEN,
+ index->table_name, strlen(index->table_name),
+ NULL, TRUE) = 0;
+
+ ib_logf(IB_LOG_LEVEL_ERROR, "%s corruption of %s in table %s in %s",
+ status, index->name, table_name, ctx);
+
mem_heap_free(heap);
- ut_print_timestamp(stderr);
- fputs(status, stderr);
- dict_index_name_print(stderr, NULL, index);
- putc('\n', stderr);
+func_exit:
+ if (!locked) {
+ row_mysql_unlock_data_dictionary(trx);
+ }
}
/**********************************************************************//**
@@ -5582,7 +5616,7 @@ dict_table_get_index_on_name(
/* If name is NULL, just return */
if (!name) {
- return NULL;
+ return(NULL);
}
index = dict_table_get_first_index(table);
@@ -5597,42 +5631,47 @@ dict_table_get_index_on_name(
}
return(NULL);
-
}
/**********************************************************************//**
-Replace the index passed in with another equivalent index in the tables
-foreign key list. */
+Replace the index passed in with another equivalent index in the
+foreign key lists of the table. */
UNIV_INTERN
void
-dict_table_replace_index_in_foreign_list(
-/*=====================================*/
- dict_table_t* table, /*!< in/out: table */
- dict_index_t* index, /*!< in: index to be replaced */
- const trx_t* trx) /*!< in: transaction handle */
+dict_foreign_replace_index(
+/*=======================*/
+ dict_table_t* table, /*!< in/out: table */
+ const dict_index_t* index, /*!< in: index to be replaced */
+ const trx_t* trx) /*!< in: transaction handle */
{
dict_foreign_t* foreign;
+ ut_ad(index->to_be_dropped);
+
for (foreign = UT_LIST_GET_FIRST(table->foreign_list);
foreign;
foreign = UT_LIST_GET_NEXT(foreign_list, foreign)) {
- if (foreign->foreign_index == index) {
- dict_index_t* new_index
- = dict_foreign_find_equiv_index(foreign);
+ dict_index_t* new_index;
- /* There must exist an alternative index if
- check_foreigns (FOREIGN_KEY_CHECKS) is on,
- since ha_innobase::prepare_drop_index had done
- the check before we reach here. */
+ if (foreign->foreign_index == index) {
+ ut_ad(foreign->foreign_table == index->table);
+ new_index = dict_foreign_find_index(
+ foreign->foreign_table,
+ foreign->foreign_col_names,
+ foreign->n_fields, index,
+ /*check_charsets=*/TRUE, /*check_null=*/FALSE);
+ /* There must exist an alternative index,
+ since this must have been checked earlier. */
ut_a(new_index || !trx->check_foreigns);
+ ut_ad(!new_index || new_index->table == index->table);
+ ut_ad(!new_index || !new_index->to_be_dropped);
foreign->foreign_index = new_index;
}
}
-
for (foreign = UT_LIST_GET_FIRST(table->referenced_list);
foreign;
foreign = UT_LIST_GET_NEXT(referenced_list, foreign)) {
@@ -5647,8 +5686,11 @@ dict_table_replace_index_in_foreign_list(
foreign->referenced_col_names,
foreign->n_fields, index,
/*check_charsets=*/TRUE, /*check_null=*/FALSE);
- ut_ad(new_index || !trx->check_foreigns);
+ /* There must exist an alternative index,
+ since this must have been checked earlier. */
+ ut_a(new_index || !trx->check_foreigns);
ut_ad(!new_index || new_index->table == index->table);
+ ut_ad(!new_index || !new_index->to_be_dropped);
foreign->referenced_index = new_index;
}
@@ -5696,8 +5738,8 @@ dict_table_check_for_dup_indexes(
/*=============================*/
const dict_table_t* table, /*!< in: Check for dup indexes
in this table */
- ibool tmp_ok) /*!< in: TRUE=allow temporary
- index names */
+ enum check_name check) /*!< in: whether and when to allow
+ temporary index names */
{
/* Check for duplicates, ignoring indexes that are marked
as to be dropped */
@@ -5713,17 +5755,32 @@ dict_table_check_for_dup_indexes(
index1 = UT_LIST_GET_FIRST(table->indexes);
do {
- ut_ad(tmp_ok || *index1->name != TEMP_INDEX_PREFIX);
-
- index2 = UT_LIST_GET_NEXT(indexes, index1);
-
- while (index2) {
-
- if (!index2->to_be_dropped) {
- ut_ad(ut_strcmp(index1->name, index2->name));
+ if (*index1->name == TEMP_INDEX_PREFIX) {
+ ut_a(!dict_index_is_clust(index1));
+
+ switch (check) {
+ case CHECK_ALL_COMPLETE:
+ ut_error;
+ case CHECK_ABORTED_OK:
+ switch (dict_index_get_online_status(index1)) {
+ case ONLINE_INDEX_COMPLETE:
+ case ONLINE_INDEX_CREATION:
+ ut_error;
+ break;
+ case ONLINE_INDEX_ABORTED:
+ case ONLINE_INDEX_ABORTED_DROPPED:
+ break;
+ }
+ /* fall through */
+ case CHECK_PARTIAL_OK:
+ break;
}
+ }
- index2 = UT_LIST_GET_NEXT(indexes, index2);
+ for (index2 = UT_LIST_GET_NEXT(indexes, index1);
+ index2 != NULL;
+ index2 = UT_LIST_GET_NEXT(indexes, index2)) {
+ ut_ad(ut_strcmp(index1->name, index2->name));
}
index1 = UT_LIST_GET_NEXT(indexes, index1);
@@ -5739,17 +5796,17 @@ The caller must own the dictionary mutex.
dict_table_schema_check() @{
@return DB_SUCCESS if the table exists and contains the necessary columns */
UNIV_INTERN
-enum db_err
+dberr_t
dict_table_schema_check(
/*====================*/
dict_table_schema_t* req_schema, /*!< in/out: required table
schema */
char* errstr, /*!< out: human readable error
- message if != DB_SUCCESS and
- != DB_TABLE_NOT_FOUND is
+ message if != DB_SUCCESS is
returned */
size_t errstr_sz) /*!< in: errstr size */
{
+ char buf[MAX_FULL_NAME_LEN];
dict_table_t* table;
ulint i;
@@ -5757,8 +5814,24 @@ dict_table_schema_check(
table = dict_table_get_low(req_schema->table_name);
- if (table == NULL || table->ibd_file_missing) {
- /* no such table or missing tablespace */
+ if (table == NULL) {
+ /* no such table */
+
+ ut_snprintf(errstr, errstr_sz,
+ "Table %s not found.",
+ ut_format_name(req_schema->table_name,
+ TRUE, buf, sizeof(buf)));
+
+ return(DB_TABLE_NOT_FOUND);
+ }
+
+ if (table->ibd_file_missing) {
+ /* missing tablespace */
+
+ ut_snprintf(errstr, errstr_sz,
+ "Tablespace for table %s is missing.",
+ ut_format_name(req_schema->table_name,
+ TRUE, buf, sizeof(buf)));
return(DB_TABLE_NOT_FOUND);
}
@@ -5769,7 +5842,8 @@ dict_table_schema_check(
ut_snprintf(errstr, errstr_sz,
"%s has %d columns but should have %lu.",
- req_schema->table_name,
+ ut_format_name(req_schema->table_name,
+ TRUE, buf, sizeof(buf)),
table->n_def - DATA_N_SYS_COLS,
req_schema->n_cols);
@@ -5814,9 +5888,12 @@ dict_table_schema_check(
if (j == table->n_def) {
ut_snprintf(errstr, errstr_sz,
- "required column %s.%s not found.",
- req_schema->table_name,
- req_schema->columns[i].name);
+ "required column %s "
+ "not found in table %s.",
+ req_schema->columns[i].name,
+ ut_format_name(
+ req_schema->table_name,
+ TRUE, buf, sizeof(buf)));
return(DB_ERROR);
}
@@ -5839,10 +5916,11 @@ dict_table_schema_check(
if (req_schema->columns[i].len != table->cols[j].len) {
ut_snprintf(errstr, errstr_sz,
- "Column %s.%s is %s but should be %s "
- "(length mismatch).",
- req_schema->table_name,
+ "Column %s in table %s is %s "
+ "but should be %s (length mismatch).",
req_schema->columns[i].name,
+ ut_format_name(req_schema->table_name,
+ TRUE, buf, sizeof(buf)),
actual_type, req_type);
return(DB_ERROR);
@@ -5852,10 +5930,11 @@ dict_table_schema_check(
if (req_schema->columns[i].mtype != table->cols[j].mtype) {
ut_snprintf(errstr, errstr_sz,
- "Column %s.%s is %s but should be %s "
- "(type mismatch).",
- req_schema->table_name,
+ "Column %s in table %s is %s "
+ "but should be %s (type mismatch).",
req_schema->columns[i].name,
+ ut_format_name(req_schema->table_name,
+ TRUE, buf, sizeof(buf)),
actual_type, req_type);
return(DB_ERROR);
@@ -5868,20 +5947,110 @@ dict_table_schema_check(
!= req_schema->columns[i].prtype_mask) {
ut_snprintf(errstr, errstr_sz,
- "Column %s.%s is %s but should be %s "
- "(flags mismatch).",
- req_schema->table_name,
+ "Column %s in table %s is %s "
+ "but should be %s (flags mismatch).",
req_schema->columns[i].name,
+ ut_format_name(req_schema->table_name,
+ TRUE, buf, sizeof(buf)),
actual_type, req_type);
return(DB_ERROR);
}
}
+ if (req_schema->n_foreign != UT_LIST_GET_LEN(table->foreign_list)) {
+ ut_snprintf(
+ errstr, errstr_sz,
+ "Table %s has %lu foreign key(s) pointing to other "
+ "tables, but it must have %lu.",
+ ut_format_name(req_schema->table_name,
+ TRUE, buf, sizeof(buf)),
+ UT_LIST_GET_LEN(table->foreign_list),
+ req_schema->n_foreign);
+ return(DB_ERROR);
+ }
+
+ if (req_schema->n_referenced != UT_LIST_GET_LEN(table->referenced_list)) {
+ ut_snprintf(
+ errstr, errstr_sz,
+ "There are %lu foreign key(s) pointing to %s, "
+ "but there must be %lu.",
+ UT_LIST_GET_LEN(table->referenced_list),
+ ut_format_name(req_schema->table_name,
+ TRUE, buf, sizeof(buf)),
+ req_schema->n_referenced);
+ return(DB_ERROR);
+ }
+
return(DB_SUCCESS);
}
/* @} */
+/*********************************************************************//**
+Converts a database and table name from filesystem encoding
+(e.g. d@i1b/a@q1b@1Kc, same format as used in dict_table_t::name) in two
+strings in UTF8 encoding (e.g. dцb and aюbØc). The output buffers must be
+at least MAX_DB_UTF8_LEN and MAX_TABLE_UTF8_LEN bytes. */
+UNIV_INTERN
+void
+dict_fs2utf8(
+/*=========*/
+ const char* db_and_table, /*!< in: database and table names,
+ e.g. d@i1b/a@q1b@1Kc */
+ char* db_utf8, /*!< out: database name, e.g. dцb */
+ size_t db_utf8_size, /*!< in: dbname_utf8 size */
+ char* table_utf8, /*!< out: table name, e.g. aюbØc */
+ size_t table_utf8_size)/*!< in: table_utf8 size */
+{
+ char db[MAX_DATABASE_NAME_LEN + 1];
+ ulint db_len;
+ uint errors;
+
+ db_len = dict_get_db_name_len(db_and_table);
+
+ ut_a(db_len <= sizeof(db));
+
+ memcpy(db, db_and_table, db_len);
+ db[db_len] = '\0';
+
+ strconvert(
+ &my_charset_filename, db,
+ system_charset_info, db_utf8, db_utf8_size,
+ &errors);
+
+ /* convert each # to @0023 in table name and store the result in buf */
+ const char* table = dict_remove_db_name(db_and_table);
+ const char* table_p;
+ char buf[MAX_TABLE_NAME_LEN * 5 + 1];
+ char* buf_p;
+ for (table_p = table, buf_p = buf; table_p[0] != '\0'; table_p++) {
+ if (table_p[0] != '#') {
+ buf_p[0] = table_p[0];
+ buf_p++;
+ } else {
+ buf_p[0] = '@';
+ buf_p[1] = '0';
+ buf_p[2] = '0';
+ buf_p[3] = '2';
+ buf_p[4] = '3';
+ buf_p += 5;
+ }
+ ut_a((size_t) (buf_p - buf) < sizeof(buf));
+ }
+ buf_p[0] = '\0';
+
+ errors = 0;
+ strconvert(
+ &my_charset_filename, buf,
+ system_charset_info, table_utf8, table_utf8_size,
+ &errors);
+
+ if (errors != 0) {
+ ut_snprintf(table_utf8, table_utf8_size, "%s%s",
+ srv_mysql50_table_name_prefix, table);
+ }
+}
+
/**********************************************************************//**
Closes the data dictionary module. */
UNIV_INTERN
@@ -5929,7 +6098,9 @@ dict_close(void)
rw_lock_free(&dict_operation_lock);
memset(&dict_operation_lock, 0x0, sizeof(dict_operation_lock));
- mutex_free(&dict_foreign_err_mutex);
+ if (!srv_read_only_mode) {
+ mutex_free(&dict_foreign_err_mutex);
+ }
mem_free(dict_sys);
dict_sys = NULL;
@@ -5943,7 +6114,7 @@ dict_close(void)
/**********************************************************************//**
Validate the dictionary table LRU list.
@return TRUE if valid */
-UNIV_INTERN
+static
ibool
dict_lru_validate(void)
/*===================*/
@@ -5972,7 +6143,7 @@ dict_lru_validate(void)
/**********************************************************************//**
Check if a table exists in the dict table LRU list.
@return TRUE if table found in LRU list */
-UNIV_INTERN
+static
ibool
dict_lru_find_table(
/*================*/
@@ -6025,4 +6196,279 @@ dict_non_lru_find_table(
return(FALSE);
}
# endif /* UNIV_DEBUG */
+/*********************************************************************//**
+Check an index to see whether its first fields are the columns in the array,
+in the same order and is not marked for deletion and is not the same
+as types_idx.
+@return true if the index qualifies, otherwise false */
+UNIV_INTERN
+bool
+dict_foreign_qualify_index(
+/*=======================*/
+ const dict_table_t* table, /*!< in: table */
+ const char** columns,/*!< in: array of column names */
+ ulint n_cols, /*!< in: number of columns */
+ const dict_index_t* index, /*!< in: index to check */
+ const dict_index_t* types_idx,
+ /*!< in: NULL or an index
+ whose types the column types
+ must match */
+ ibool check_charsets,
+ /*!< in: whether to check
+ charsets. only has an effect
+ if types_idx != NULL */
+ ulint check_null)
+ /*!< in: nonzero if none of
+ the columns must be declared
+ NOT NULL */
+{
+ ulint i;
+
+ if (dict_index_get_n_fields(index) < n_cols) {
+ return(false);
+ }
+
+ for (i= 0; i < n_cols; i++) {
+ dict_field_t* field;
+ const char* col_name;
+
+ field = dict_index_get_nth_field(index, i);
+
+ col_name = dict_table_get_col_name(
+ table, dict_col_get_no(field->col));
+
+ if (field->prefix_len != 0) {
+ /* We do not accept column prefix
+ indexes here */
+
+ break;
+ }
+
+ if (0 != innobase_strcasecmp(columns[i],
+ col_name)) {
+ break;
+ }
+
+ if (check_null
+ && (field->col->prtype & DATA_NOT_NULL)) {
+
+ break;
+ }
+
+ if (types_idx && !cmp_cols_are_equal(
+ dict_index_get_nth_col(index, i),
+ dict_index_get_nth_col(types_idx,
+ i),
+ check_charsets)) {
+
+ break;
+ }
+ }
+
+ return((i == n_cols) ? true : false);
+}
+
+/*********************************************************************//**
+Update the state of compression failure padding heuristics. This is
+called whenever a compression operation succeeds or fails.
+The caller must be holding info->mutex */
+static
+void
+dict_index_zip_pad_update(
+/*======================*/
+ zip_pad_info_t* info, /*<! in/out: info to be updated */
+ ulint zip_threshold) /*<! in: zip threshold value */
+{
+ ulint total;
+ ulint fail_pct;
+
+ ut_ad(info);
+
+ total = info->success + info->failure;
+
+ ut_ad(total > 0);
+
+ if(zip_threshold == 0) {
+ /* User has just disabled the padding. */
+ return;
+ }
+
+ if (total < ZIP_PAD_ROUND_LEN) {
+ /* We are in middle of a round. Do nothing. */
+ return;
+ }
+
+ /* We are at a 'round' boundary. Reset the values but first
+ calculate fail rate for our heuristic. */
+ fail_pct = (info->failure * 100) / total;
+ info->failure = 0;
+ info->success = 0;
+
+ if (fail_pct > zip_threshold) {
+ /* Compression failures are more then user defined
+ threshold. Increase the pad size to reduce chances of
+ compression failures. */
+ ut_ad(info->pad % ZIP_PAD_INCR == 0);
+
+ /* Only do increment if it won't increase padding
+ beyond max pad size. */
+ if (info->pad + ZIP_PAD_INCR
+ < (UNIV_PAGE_SIZE * zip_pad_max) / 100) {
+#ifdef HAVE_ATOMIC_BUILTINS
+ /* Use atomics even though we have the mutex.
+ This is to ensure that we are able to read
+ info->pad atomically where atomics are
+ supported. */
+ os_atomic_increment_ulint(&info->pad, ZIP_PAD_INCR);
+#else /* HAVE_ATOMIC_BUILTINS */
+ info->pad += ZIP_PAD_INCR;
+#endif /* HAVE_ATOMIC_BUILTINS */
+
+ MONITOR_INC(MONITOR_PAD_INCREMENTS);
+ }
+
+ info->n_rounds = 0;
+
+ } else {
+ /* Failure rate was OK. Another successful round
+ completed. */
+ ++info->n_rounds;
+
+ /* If enough successful rounds are completed with
+ compression failure rate in control, decrease the
+ padding. */
+ if (info->n_rounds >= ZIP_PAD_SUCCESSFUL_ROUND_LIMIT
+ && info->pad > 0) {
+
+ ut_ad(info->pad % ZIP_PAD_INCR == 0);
+#ifdef HAVE_ATOMIC_BUILTINS
+ /* Use atomics even though we have the mutex.
+ This is to ensure that we are able to read
+ info->pad atomically where atomics are
+ supported. */
+ os_atomic_decrement_ulint(&info->pad, ZIP_PAD_INCR);
+#else /* HAVE_ATOMIC_BUILTINS */
+ info->pad -= ZIP_PAD_INCR;
+#endif /* HAVE_ATOMIC_BUILTINS */
+
+ info->n_rounds = 0;
+
+ MONITOR_INC(MONITOR_PAD_DECREMENTS);
+ }
+ }
+}
+
+/*********************************************************************//**
+This function should be called whenever a page is successfully
+compressed. Updates the compression padding information. */
+UNIV_INTERN
+void
+dict_index_zip_success(
+/*===================*/
+ dict_index_t* index) /*!< in/out: index to be updated. */
+{
+ ut_ad(index);
+
+ ulint zip_threshold = zip_failure_threshold_pct;
+ if (!zip_threshold) {
+ /* Disabled by user. */
+ return;
+ }
+
+ os_fast_mutex_lock(&index->zip_pad.mutex);
+ ++index->zip_pad.success;
+ dict_index_zip_pad_update(&index->zip_pad, zip_threshold);
+ os_fast_mutex_unlock(&index->zip_pad.mutex);
+}
+
+/*********************************************************************//**
+This function should be called whenever a page compression attempt
+fails. Updates the compression padding information. */
+UNIV_INTERN
+void
+dict_index_zip_failure(
+/*===================*/
+ dict_index_t* index) /*!< in/out: index to be updated. */
+{
+ ut_ad(index);
+
+ ulint zip_threshold = zip_failure_threshold_pct;
+ if (!zip_threshold) {
+ /* Disabled by user. */
+ return;
+ }
+
+ os_fast_mutex_lock(&index->zip_pad.mutex);
+ ++index->zip_pad.failure;
+ dict_index_zip_pad_update(&index->zip_pad, zip_threshold);
+ os_fast_mutex_unlock(&index->zip_pad.mutex);
+}
+
+
+/*********************************************************************//**
+Return the optimal page size, for which page will likely compress.
+@return page size beyond which page might not compress */
+UNIV_INTERN
+ulint
+dict_index_zip_pad_optimal_page_size(
+/*=================================*/
+ dict_index_t* index) /*!< in: index for which page size
+ is requested */
+{
+ ulint pad;
+ ulint min_sz;
+ ulint sz;
+
+ ut_ad(index);
+
+ if (!zip_failure_threshold_pct) {
+ /* Disabled by user. */
+ return(UNIV_PAGE_SIZE);
+ }
+
+ /* We use atomics to read index->zip_pad.pad. Here we use zero
+ as increment as are not changing the value of the 'pad'. On
+ platforms where atomics are not available we grab the mutex. */
+
+#ifdef HAVE_ATOMIC_BUILTINS
+ pad = os_atomic_increment_ulint(&index->zip_pad.pad, 0);
+#else /* HAVE_ATOMIC_BUILTINS */
+ os_fast_mutex_lock(&index->zip_pad.mutex);
+ pad = index->zip_pad.pad;
+ os_fast_mutex_unlock(&index->zip_pad.mutex);
+#endif /* HAVE_ATOMIC_BUILTINS */
+
+ ut_ad(pad < UNIV_PAGE_SIZE);
+ sz = UNIV_PAGE_SIZE - pad;
+
+ /* Min size allowed by user. */
+ ut_ad(zip_pad_max < 100);
+ min_sz = (UNIV_PAGE_SIZE * (100 - zip_pad_max)) / 100;
+
+ return(ut_max(sz, min_sz));
+}
+
+/*************************************************************//**
+Convert table flag to row format string.
+@return row format name. */
+UNIV_INTERN
+const char*
+dict_tf_to_row_format_string(
+/*=========================*/
+ ulint table_flag) /*!< in: row format setting */
+{
+ switch (dict_tf_get_rec_format(table_flag)) {
+ case REC_FORMAT_REDUNDANT:
+ return("ROW_TYPE_REDUNDANT");
+ case REC_FORMAT_COMPACT:
+ return("ROW_TYPE_COMPACT");
+ case REC_FORMAT_COMPRESSED:
+ return("ROW_TYPE_COMPRESSED");
+ case REC_FORMAT_DYNAMIC:
+ return("ROW_TYPE_DYNAMIC");
+ }
+
+ ut_error;
+ return(0);
+}
#endif /* !UNIV_HOTBACKUP */
diff --git a/storage/innobase/dict/dict0load.cc b/storage/innobase/dict/dict0load.cc
index ff93be3e76a..46d72786ac6 100644
--- a/storage/innobase/dict/dict0load.cc
+++ b/storage/innobase/dict/dict0load.cc
@@ -1,6 +1,6 @@
/*****************************************************************************
-Copyright (c) 1996, 2011, Oracle and/or its affiliates. All Rights Reserved.
+Copyright (c) 1996, 2012, Oracle and/or its affiliates. All Rights Reserved.
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
@@ -41,18 +41,22 @@ Created 4/24/1996 Heikki Tuuri
#include "rem0cmp.h"
#include "srv0start.h"
#include "srv0srv.h"
+#include "dict0crea.h"
#include "dict0priv.h"
#include "ha_prototypes.h" /* innobase_casedn_str() */
#include "fts0priv.h"
-/** Following are six InnoDB system tables */
+/** Following are the InnoDB system tables. The positions in
+this array are referenced by enum dict_system_table_id. */
static const char* SYSTEM_TABLE_NAME[] = {
"SYS_TABLES",
"SYS_INDEXES",
"SYS_COLUMNS",
"SYS_FIELDS",
"SYS_FOREIGN",
- "SYS_FOREIGN_COLS"
+ "SYS_FOREIGN_COLS",
+ "SYS_TABLESPACES",
+ "SYS_DATAFILES"
};
/* If this flag is TRUE, then we will load the cluster index's (and tables')
@@ -183,7 +187,8 @@ dict_print(void)
os_increment_counter_by_amount(
server_mutex,
- srv_fatal_semaphore_wait_threshold, 7200/*2 hours*/);
+ srv_fatal_semaphore_wait_threshold,
+ SRV_SEMAPHORE_WAIT_EXTENSION);
heap = mem_heap_create(1000);
mutex_enter(&(dict_sys->mutex));
@@ -196,13 +201,11 @@ dict_print(void)
err_msg = static_cast<const char*>(
dict_process_sys_tables_rec_and_mtr_commit(
- heap, rec, &table,
- static_cast<dict_table_info_t>(
- DICT_TABLE_LOAD_FROM_CACHE
- | DICT_TABLE_UPDATE_STATS), &mtr));
+ heap, rec, &table, DICT_TABLE_LOAD_FROM_CACHE,
+ &mtr));
if (!err_msg) {
- dict_table_print_low(table);
+ dict_table_print(table);
} else {
ut_print_timestamp(stderr);
fprintf(stderr, " InnoDB: %s\n", err_msg);
@@ -221,7 +224,8 @@ dict_print(void)
/* Restore the fatal semaphore wait timeout */
os_decrement_counter_by_amount(
server_mutex,
- srv_fatal_semaphore_wait_threshold, 7200/*2 hours*/);
+ srv_fatal_semaphore_wait_threshold,
+ SRV_SEMAPHORE_WAIT_EXTENSION);
}
/********************************************************************//**
@@ -278,8 +282,8 @@ dict_startscan_system(
clust_index = UT_LIST_GET_FIRST(system_table->indexes);
- btr_pcur_open_at_index_side(TRUE, clust_index, BTR_SEARCH_LEAF, pcur,
- TRUE, mtr);
+ btr_pcur_open_at_index_side(true, clust_index, BTR_SEARCH_LEAF, pcur,
+ true, 0, mtr);
rec = dict_getnext_system_low(pcur, mtr);
@@ -307,6 +311,7 @@ dict_getnext_system(
return(rec);
}
+
/********************************************************************//**
This function processes one SYS_TABLES record and populate the dict_table_t
struct for the table. Extracted out of dict_print() to be used by
@@ -362,15 +367,6 @@ dict_process_sys_tables_rec_and_mtr_commit(
return(err_msg);
}
- if ((status & DICT_TABLE_UPDATE_STATS)
- && dict_table_get_first_index(*table)) {
-
- /* Update statistics member fields in *table if
- DICT_TABLE_UPDATE_STATS is set */
- ut_ad(mutex_own(&dict_sys->mutex));
- dict_stats_update(*table, DICT_STATS_FETCH, TRUE);
- }
-
return(NULL);
}
@@ -401,6 +397,7 @@ dict_process_sys_indexes_rec(
return(err_msg);
}
+
/********************************************************************//**
This function parses a SYS_COLUMNS record and populate a dict_column_t
structure with the information from the record.
@@ -423,6 +420,7 @@ dict_process_sys_columns_rec(
return(err_msg);
}
+
/********************************************************************//**
This function parses a SYS_FIELDS record and populates a dict_field_t
structure with the information from the record.
@@ -475,7 +473,7 @@ dict_process_sys_foreign_rec(
const byte* field;
ulint n_fields_and_type;
- if (UNIV_UNLIKELY(rec_get_deleted_flag(rec, 0))) {
+ if (rec_get_deleted_flag(rec, 0)) {
return("delete-marked record in SYS_FOREIGN");
}
@@ -485,7 +483,7 @@ dict_process_sys_foreign_rec(
field = rec_get_nth_field_old(
rec, DICT_FLD__SYS_FOREIGN__ID, &len);
- if (UNIV_UNLIKELY(len < 1 || len == UNIV_SQL_NULL)) {
+ if (len == 0 || len == UNIV_SQL_NULL) {
err_len:
return("incorrect column length in SYS_FOREIGN");
}
@@ -512,7 +510,7 @@ err_len:
field = rec_get_nth_field_old(
rec, DICT_FLD__SYS_FOREIGN__FOR_NAME, &len);
- if (len < 1 || len == UNIV_SQL_NULL) {
+ if (len == 0 || len == UNIV_SQL_NULL) {
goto err_len;
}
foreign->foreign_table_name = mem_heap_strdupl(
@@ -520,7 +518,7 @@ err_len:
field = rec_get_nth_field_old(
rec, DICT_FLD__SYS_FOREIGN__REF_NAME, &len);
- if (len < 1 || len == UNIV_SQL_NULL) {
+ if (len == 0 || len == UNIV_SQL_NULL) {
goto err_len;
}
foreign->referenced_table_name = mem_heap_strdupl(
@@ -568,7 +566,7 @@ dict_process_sys_foreign_col_rec(
field = rec_get_nth_field_old(
rec, DICT_FLD__SYS_FOREIGN_COLS__ID, &len);
- if (len < 1 || len == UNIV_SQL_NULL) {
+ if (len == 0 || len == UNIV_SQL_NULL) {
err_len:
return("incorrect column length in SYS_FOREIGN_COLS");
}
@@ -594,14 +592,14 @@ err_len:
field = rec_get_nth_field_old(
rec, DICT_FLD__SYS_FOREIGN_COLS__FOR_COL_NAME, &len);
- if (len < 1 || len == UNIV_SQL_NULL) {
+ if (len == 0 || len == UNIV_SQL_NULL) {
goto err_len;
}
*for_col_name = mem_heap_strdupl(heap, (char*) field, len);
field = rec_get_nth_field_old(
rec, DICT_FLD__SYS_FOREIGN_COLS__REF_COL_NAME, &len);
- if (len < 1 || len == UNIV_SQL_NULL) {
+ if (len == 0 || len == UNIV_SQL_NULL) {
goto err_len;
}
*ref_col_name = mem_heap_strdupl(heap, (char*) field, len);
@@ -610,6 +608,127 @@ err_len:
}
/********************************************************************//**
+This function parses a SYS_TABLESPACES record, extracts necessary
+information from the record and returns to caller.
+@return error message, or NULL on success */
+UNIV_INTERN
+const char*
+dict_process_sys_tablespaces(
+/*=========================*/
+ mem_heap_t* heap, /*!< in/out: heap memory */
+ const rec_t* rec, /*!< in: current SYS_TABLESPACES rec */
+ ulint* space, /*!< out: space id */
+ const char** name, /*!< out: tablespace name */
+ ulint* flags) /*!< out: tablespace flags */
+{
+ ulint len;
+ const byte* field;
+
+ /* Initialize the output values */
+ *space = ULINT_UNDEFINED;
+ *name = NULL;
+ *flags = ULINT_UNDEFINED;
+
+ if (rec_get_deleted_flag(rec, 0)) {
+ return("delete-marked record in SYS_TABLESPACES");
+ }
+
+ if (rec_get_n_fields_old(rec) != DICT_NUM_FIELDS__SYS_TABLESPACES) {
+ return("wrong number of columns in SYS_TABLESPACES record");
+ }
+
+ field = rec_get_nth_field_old(
+ rec, DICT_FLD__SYS_TABLESPACES__SPACE, &len);
+ if (len != DICT_FLD_LEN_SPACE) {
+err_len:
+ return("incorrect column length in SYS_TABLESPACES");
+ }
+ *space = mach_read_from_4(field);
+
+ rec_get_nth_field_offs_old(
+ rec, DICT_FLD__SYS_TABLESPACES__DB_TRX_ID, &len);
+ if (len != DATA_TRX_ID_LEN && len != UNIV_SQL_NULL) {
+ goto err_len;
+ }
+
+ rec_get_nth_field_offs_old(
+ rec, DICT_FLD__SYS_TABLESPACES__DB_ROLL_PTR, &len);
+ if (len != DATA_ROLL_PTR_LEN && len != UNIV_SQL_NULL) {
+ goto err_len;
+ }
+
+ field = rec_get_nth_field_old(
+ rec, DICT_FLD__SYS_TABLESPACES__NAME, &len);
+ if (len == 0 || len == UNIV_SQL_NULL) {
+ goto err_len;
+ }
+ *name = mem_heap_strdupl(heap, (char*) field, len);
+
+ field = rec_get_nth_field_old(
+ rec, DICT_FLD__SYS_TABLESPACES__FLAGS, &len);
+ if (len != DICT_FLD_LEN_FLAGS) {
+ goto err_len;
+ }
+ *flags = mach_read_from_4(field);
+
+ return(NULL);
+}
+
+/********************************************************************//**
+This function parses a SYS_DATAFILES record, extracts necessary
+information from the record and returns it to the caller.
+@return error message, or NULL on success */
+UNIV_INTERN
+const char*
+dict_process_sys_datafiles(
+/*=======================*/
+ mem_heap_t* heap, /*!< in/out: heap memory */
+ const rec_t* rec, /*!< in: current SYS_DATAFILES rec */
+ ulint* space, /*!< out: space id */
+ const char** path) /*!< out: datafile paths */
+{
+ ulint len;
+ const byte* field;
+
+ if (rec_get_deleted_flag(rec, 0)) {
+ return("delete-marked record in SYS_DATAFILES");
+ }
+
+ if (rec_get_n_fields_old(rec) != DICT_NUM_FIELDS__SYS_DATAFILES) {
+ return("wrong number of columns in SYS_DATAFILES record");
+ }
+
+ field = rec_get_nth_field_old(
+ rec, DICT_FLD__SYS_DATAFILES__SPACE, &len);
+ if (len != DICT_FLD_LEN_SPACE) {
+err_len:
+ return("incorrect column length in SYS_DATAFILES");
+ }
+ *space = mach_read_from_4(field);
+
+ rec_get_nth_field_offs_old(
+ rec, DICT_FLD__SYS_DATAFILES__DB_TRX_ID, &len);
+ if (len != DATA_TRX_ID_LEN && len != UNIV_SQL_NULL) {
+ goto err_len;
+ }
+
+ rec_get_nth_field_offs_old(
+ rec, DICT_FLD__SYS_DATAFILES__DB_ROLL_PTR, &len);
+ if (len != DATA_ROLL_PTR_LEN && len != UNIV_SQL_NULL) {
+ goto err_len;
+ }
+
+ field = rec_get_nth_field_old(
+ rec, DICT_FLD__SYS_DATAFILES__PATH, &len);
+ if (len == 0 || len == UNIV_SQL_NULL) {
+ goto err_len;
+ }
+ *path = mem_heap_strdupl(heap, (char*) field, len);
+
+ return(NULL);
+}
+
+/********************************************************************//**
Determine the flags of a table as stored in SYS_TABLES.TYPE and N_COLS.
@return ULINT_UNDEFINED if error, else a valid dict_table_t::flags. */
static
@@ -629,11 +748,9 @@ dict_sys_tables_get_flags(
ut_a(len == 4);
type = mach_read_from_4(field);
- /* The low order bit of SYS_TABLES.TYPE is always set to 1. If no
- other bits are used, that is defined as SYS_TABLE_TYPE_ANTELOPE.
- But in dict_table_t::flags the low order bit is used to determine
- if the row format is Redundant or Compact when the format is
- Antelope.
+ /* The low order bit of SYS_TABLES.TYPE is always set to 1. But in
+ dict_table_t::flags the low order bit is used to determine if the
+ row format is Redundant or Compact when the format is Antelope.
Read the 4 byte N_COLS field and look at the high order bit. It
should be set for COMPACT and later. It should not be set for
REDUNDANT. */
@@ -645,10 +762,193 @@ dict_sys_tables_get_flags(
/* This validation function also combines the DICT_N_COLS_COMPACT
flag in n_cols into the type field to effectively make it a
dict_table_t::flags. */
- return(dict_sys_tables_type_validate(type, n_cols));
+
+ if (ULINT_UNDEFINED == dict_sys_tables_type_validate(type, n_cols)) {
+ return(ULINT_UNDEFINED);
+ }
+
+ return(dict_sys_tables_type_to_tf(type, n_cols));
}
/********************************************************************//**
+Gets the filepath for a spaceid from SYS_DATAFILES and checks it against
+the contents of a link file. This function is called when there is no
+fil_node_t entry for this space ID so both durable locations on disk
+must be checked and compared.
+We use a temporary heap here for the table lookup, but not for the path
+returned which the caller must free.
+This function can return NULL if the space ID is not found in SYS_DATAFILES,
+then the caller will assume that the ibd file is in the normal datadir.
+@return own: A copy of the first datafile found in SYS_DATAFILES.PATH for
+the given space ID. NULL if space ID is zero or not found. */
+UNIV_INTERN
+char*
+dict_get_first_path(
+/*================*/
+ ulint space, /*!< in: space id */
+ const char* name) /*!< in: tablespace name */
+{
+ mtr_t mtr;
+ dict_table_t* sys_datafiles;
+ dict_index_t* sys_index;
+ dtuple_t* tuple;
+ dfield_t* dfield;
+ byte* buf;
+ btr_pcur_t pcur;
+ const rec_t* rec;
+ const byte* field;
+ ulint len;
+ char* dict_filepath = NULL;
+ mem_heap_t* heap = mem_heap_create(1024);
+
+ ut_ad(mutex_own(&(dict_sys->mutex)));
+
+ mtr_start(&mtr);
+
+ sys_datafiles = dict_table_get_low("SYS_DATAFILES");
+ sys_index = UT_LIST_GET_FIRST(sys_datafiles->indexes);
+ ut_ad(!dict_table_is_comp(sys_datafiles));
+ ut_ad(name_of_col_is(sys_datafiles, sys_index,
+ DICT_FLD__SYS_DATAFILES__SPACE, "SPACE"));
+ ut_ad(name_of_col_is(sys_datafiles, sys_index,
+ DICT_FLD__SYS_DATAFILES__PATH, "PATH"));
+
+ tuple = dtuple_create(heap, 1);
+ dfield = dtuple_get_nth_field(tuple, DICT_FLD__SYS_DATAFILES__SPACE);
+
+ buf = static_cast<byte*>(mem_heap_alloc(heap, 4));
+ mach_write_to_4(buf, space);
+
+ dfield_set_data(dfield, buf, 4);
+ dict_index_copy_types(tuple, sys_index, 1);
+
+ btr_pcur_open_on_user_rec(sys_index, tuple, PAGE_CUR_GE,
+ BTR_SEARCH_LEAF, &pcur, &mtr);
+
+ rec = btr_pcur_get_rec(&pcur);
+
+ /* If the file-per-table tablespace was created with
+ an earlier version of InnoDB, then this record is not
+ in SYS_DATAFILES. But a link file still might exist. */
+
+ if (btr_pcur_is_on_user_rec(&pcur)) {
+ /* A record for this space ID was found. */
+ field = rec_get_nth_field_old(
+ rec, DICT_FLD__SYS_DATAFILES__PATH, &len);
+ ut_a(len > 0 || len == UNIV_SQL_NULL);
+ ut_a(len < OS_FILE_MAX_PATH);
+ dict_filepath = mem_strdupl((char*) field, len);
+ ut_a(dict_filepath);
+ }
+
+ btr_pcur_close(&pcur);
+ mtr_commit(&mtr);
+ mem_heap_free(heap);
+
+ return(dict_filepath);
+}
+
+/********************************************************************//**
+Update the record for space_id in SYS_TABLESPACES to this filepath.
+@return DB_SUCCESS if OK, dberr_t if the insert failed */
+UNIV_INTERN
+dberr_t
+dict_update_filepath(
+/*=================*/
+ ulint space_id, /*!< in: space id */
+ const char* filepath) /*!< in: filepath */
+{
+ dberr_t err = DB_SUCCESS;
+ trx_t* trx;
+
+#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)));
+
+ trx = trx_allocate_for_background();
+ trx->op_info = "update filepath";
+ trx->dict_operation_lock_mode = RW_X_LATCH;
+ trx_start_for_ddl(trx, TRX_DICT_OP_INDEX);
+
+ pars_info_t* info = pars_info_create();
+
+ pars_info_add_int4_literal(info, "space", space_id);
+ pars_info_add_str_literal(info, "path", filepath);
+
+ err = que_eval_sql(info,
+ "PROCEDURE UPDATE_FILEPATH () IS\n"
+ "BEGIN\n"
+ "UPDATE SYS_DATAFILES"
+ " SET PATH = :path\n"
+ " WHERE SPACE = :space;\n"
+ "END;\n", FALSE, trx);
+
+ trx_commit_for_mysql(trx);
+ trx->dict_operation_lock_mode = 0;
+ trx_free_for_background(trx);
+
+ if (err == DB_SUCCESS) {
+ /* We just updated SYS_DATAFILES due to the contents in
+ a link file. Make a note that we did this. */
+ ib_logf(IB_LOG_LEVEL_INFO,
+ "The InnoDB data dictionary table SYS_DATAFILES "
+ "for tablespace ID %lu was updated to use file %s.",
+ (ulong) space_id, filepath);
+ } else {
+ ib_logf(IB_LOG_LEVEL_WARN,
+ "Problem updating InnoDB data dictionary table "
+ "SYS_DATAFILES for tablespace ID %lu to file %s.",
+ (ulong) space_id, filepath);
+ }
+
+ return(err);
+}
+
+/********************************************************************//**
+Insert records into SYS_TABLESPACES and SYS_DATAFILES.
+@return DB_SUCCESS if OK, dberr_t if the insert failed */
+UNIV_INTERN
+dberr_t
+dict_insert_tablespace_and_filepath(
+/*================================*/
+ ulint space, /*!< in: space id */
+ const char* name, /*!< in: talespace name */
+ const char* filepath, /*!< in: filepath */
+ ulint fsp_flags) /*!< in: tablespace flags */
+{
+ dberr_t err = DB_SUCCESS;
+ trx_t* trx;
+
+#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)));
+ ut_ad(filepath);
+
+ trx = trx_allocate_for_background();
+ trx->op_info = "insert tablespace and filepath";
+ trx->dict_operation_lock_mode = RW_X_LATCH;
+ trx_start_for_ddl(trx, TRX_DICT_OP_INDEX);
+
+ /* A record for this space ID was not found in
+ SYS_DATAFILES. Assume the record is also missing in
+ SYS_TABLESPACES. Insert records onto them both. */
+ err = dict_create_add_tablespace_to_dictionary(
+ space, name, fsp_flags, filepath, trx, false);
+
+ trx_commit_for_mysql(trx);
+ trx->dict_operation_lock_mode = 0;
+ trx_free_for_background(trx);
+
+ return(err);
+}
+
+/********************************************************************//**
+This function looks at each table defined in SYS_TABLES. It checks the
+tablespace for any table with a space_id > 0. It looks up the tablespace
+in SYS_DATAFILES to ensure the correct path.
+
In a crash recovery we already have all the tablespace objects created.
This function compares the space id information in the InnoDB data dictionary
to what we already read with fil_load_single_table_tablespaces().
@@ -669,6 +969,7 @@ dict_check_tablespaces_and_store_max_id(
ulint max_space_id;
mtr_t mtr;
+ rw_lock_x_lock(&dict_operation_lock);
mutex_enter(&(dict_sys->mutex));
mtr_start(&mtr);
@@ -682,8 +983,8 @@ dict_check_tablespaces_and_store_max_id(
MLOG_4BYTES, &mtr);
fil_set_max_space_id_if_bigger(max_space_id);
- btr_pcur_open_at_index_side(TRUE, sys_index, BTR_SEARCH_LEAF, &pcur,
- TRUE, &mtr);
+ btr_pcur_open_at_index_side(true, sys_index, BTR_SEARCH_LEAF, &pcur,
+ true, 0, &mtr);
loop:
btr_pcur_move_to_next_user_rec(&pcur, &mtr);
@@ -703,6 +1004,7 @@ loop:
fil_set_max_space_id_if_bigger(max_space_id);
mutex_exit(&(dict_sys->mutex));
+ rw_lock_x_unlock(&dict_operation_lock);
return;
}
@@ -718,8 +1020,14 @@ loop:
field = rec_get_nth_field_old(
rec, DICT_FLD__SYS_TABLES__NAME, &len);
+
name = mem_strdupl((char*) field, len);
+ char table_name[MAX_FULL_NAME_LEN + 1];
+
+ innobase_format_name(
+ table_name, sizeof(table_name), name, FALSE);
+
flags = dict_sys_tables_get_flags(rec);
if (UNIV_UNLIKELY(flags == ULINT_UNDEFINED)) {
/* Read again the 4 bytes from rec. */
@@ -728,13 +1036,9 @@ loop:
ut_ad(len == 4); /* this was checked earlier */
flags = mach_read_from_4(field);
- ut_print_timestamp(stderr);
- fputs(" InnoDB: Error: table ", stderr);
- ut_print_filename(stderr, name);
- fprintf(stderr, "\n"
- "InnoDB: in InnoDB data dictionary"
- " has unknown type %lx.\n",
- (ulong) flags);
+ ib_logf(IB_LOG_LEVEL_ERROR,
+ "Table '%s' in InnoDB data dictionary"
+ " has unknown type %lx", table_name, flags);
goto loop;
}
@@ -749,43 +1053,84 @@ loop:
mtr_commit(&mtr);
+ /* For tables created with old versions of InnoDB,
+ SYS_TABLES.MIX_LEN may contain garbage. Such tables
+ would always be in ROW_FORMAT=REDUNDANT. Pretend that
+ all such tables are non-temporary. That is, do not
+ suppress error printouts about temporary or discarded
+ tablespaces not being found. */
+
+ field = rec_get_nth_field_old(
+ rec, DICT_FLD__SYS_TABLES__MIX_LEN, &len);
+
+ bool is_temp = false;
+ bool discarded = false;
+ ib_uint32_t flags2 = mach_read_from_4(field);
+
+ /* Check that the tablespace (the .ibd file) really
+ exists; print a warning to the .err log if not.
+ Do not print warnings for temporary tables or for
+ tablespaces that have been discarded. */
+
+ field = rec_get_nth_field_old(
+ rec, DICT_FLD__SYS_TABLES__N_COLS, &len);
+
+ /* MIX_LEN valid only for ROW_FORMAT > REDUNDANT. */
+ if (mach_read_from_4(field) & DICT_N_COLS_COMPACT) {
+
+ is_temp = !!(flags2 & DICT_TF2_TEMPORARY);
+ discarded = !!(flags2 & DICT_TF2_DISCARDED);
+ }
+
if (space_id == 0) {
/* The system tablespace always exists. */
+ ut_ad(!discarded);
} else if (in_crash_recovery) {
- /* Check that the tablespace (the .ibd file) really
- exists; print a warning to the .err log if not.
- Do not print warnings for temporary tables. */
- ibool is_temp;
+ /* All tablespaces should have been found in
+ fil_load_single_table_tablespaces(). */
- field = rec_get_nth_field_old(
- rec, DICT_FLD__SYS_TABLES__N_COLS, &len);
- if (mach_read_from_4(field) & DICT_N_COLS_COMPACT) {
- /* ROW_FORMAT=COMPACT: read the is_temp
- flag from SYS_TABLES.MIX_LEN. */
- field = rec_get_nth_field_old(
- rec, 7/*MIX_LEN*/, &len);
- is_temp = !!(mach_read_from_4(field)
- & DICT_TF2_TEMPORARY);
- } else {
- /* For tables created with old versions
- of InnoDB, SYS_TABLES.MIX_LEN may contain
- garbage. Such tables would always be
- in ROW_FORMAT=REDUNDANT. Pretend that
- all such tables are non-temporary. That is,
- do not suppress error printouts about
- temporary tables not being found. */
- is_temp = FALSE;
+ fil_space_for_table_exists_in_mem(
+ space_id, name, TRUE, !(is_temp || discarded),
+ false, NULL, 0);
+
+ } else if (!discarded) {
+
+ /* It is a normal database startup: create the
+ space object and check that the .ibd file exists.
+ If the table uses a remote tablespace, look for the
+ space_id in SYS_DATAFILES to find the filepath */
+
+ /* Use the remote filepath if known. */
+ char* filepath = NULL;
+ if (DICT_TF_HAS_DATA_DIR(flags)) {
+ filepath = dict_get_first_path(
+ space_id, name);
}
- fil_space_for_table_exists_in_mem(
- space_id, name, TRUE, !is_temp);
- } else {
- /* It is a normal database startup: create the space
- object and check that the .ibd file exists. */
+ /* We set the 2nd param (fix_dict = true)
+ here because we already have an x-lock on
+ dict_operation_lock and dict_sys->mutex. Besides,
+ this is at startup and we are now single threaded.
+ If the filepath is not known, it will need to
+ be discovered. */
+ dberr_t err = fil_open_single_table_tablespace(
+ false, srv_read_only_mode ? false : true,
+ space_id, dict_tf_to_fsp_flags(flags),
+ name, filepath);
+
+ if (err != DB_SUCCESS) {
+ ib_logf(IB_LOG_LEVEL_ERROR,
+ "Tablespace open failed for '%s', "
+ "ignored.", table_name);
+ }
- fil_open_single_table_tablespace(
- FALSE, space_id,
- dict_tf_to_fsp_flags(flags), name);
+ if (filepath) {
+ mem_free(filepath);
+ }
+ } else {
+ ib_logf(IB_LOG_LEVEL_INFO,
+ "DISCARD flag set for table '%s', ignored.",
+ table_name);
}
mem_free(name);
@@ -879,7 +1224,7 @@ err_len:
field = rec_get_nth_field_old(
rec, DICT_FLD__SYS_COLUMNS__NAME, &len);
- if (len < 1 || len == UNIV_SQL_NULL) {
+ if (len == 0 || len == UNIV_SQL_NULL) {
goto err_len;
}
@@ -1003,6 +1348,11 @@ dict_load_columns(
err_msg = dict_load_column_low(table, heap, NULL, NULL,
&name, rec);
+ if (err_msg) {
+ fprintf(stderr, "InnoDB: %s\n", err_msg);
+ ut_error;
+ }
+
/* Note: Currently we have one DOC_ID column that is
shared by all FTS indexes on a table. */
if (innobase_strcasecmp(name,
@@ -1037,11 +1387,6 @@ dict_load_columns(
table->fts->doc_col = i;
}
- if (err_msg) {
- fprintf(stderr, "InnoDB: %s\n", err_msg);
- ut_error;
- }
-
btr_pcur_move_to_next_user_rec(&pcur, &mtr);
}
@@ -1154,7 +1499,7 @@ err_len:
field = rec_get_nth_field_old(
rec, DICT_FLD__SYS_FIELDS__COL_NAME, &len);
- if (len < 1 || len == UNIV_SQL_NULL) {
+ if (len == 0 || len == UNIV_SQL_NULL) {
goto err_len;
}
@@ -1194,7 +1539,7 @@ dict_load_fields(
byte* buf;
ulint i;
mtr_t mtr;
- ulint error;
+ dberr_t error;
ut_ad(mutex_own(&(dict_sys->mutex)));
@@ -1394,8 +1739,8 @@ Loads definitions for table indexes. Adds them to the data dictionary
cache.
@return DB_SUCCESS if ok, DB_CORRUPTION if corruption of dictionary
table or DB_UNSUPPORTED if table has unknown index type */
-static
-ulint
+static __attribute__((nonnull))
+dberr_t
dict_load_indexes(
/*==============*/
dict_table_t* table, /*!< in/out: table */
@@ -1412,7 +1757,7 @@ dict_load_indexes(
const rec_t* rec;
byte* buf;
mtr_t mtr;
- ulint error = DB_SUCCESS;
+ dberr_t error = DB_SUCCESS;
ut_ad(mutex_own(&(dict_sys->mutex)));
@@ -1443,6 +1788,21 @@ dict_load_indexes(
if (!btr_pcur_is_on_user_rec(&pcur)) {
+ /* We should allow the table to open even
+ without index when DICT_ERR_IGNORE_CORRUPT is set.
+ DICT_ERR_IGNORE_CORRUPT is currently only set
+ for drop table */
+ if (dict_table_get_first_index(table) == NULL
+ && !(ignore_err & DICT_ERR_IGNORE_CORRUPT)) {
+ ib_logf(IB_LOG_LEVEL_WARN,
+ "Cannot load table %s "
+ "because it has no indexes in "
+ "InnoDB internal data dictionary.",
+ table->name);
+ error = DB_CORRUPTION;
+ goto func_exit;
+ }
+
break;
}
@@ -1456,6 +1816,20 @@ dict_load_indexes(
if (err_msg == dict_load_index_id_err) {
/* TABLE_ID mismatch means that we have
run out of index definitions for the table. */
+
+ if (dict_table_get_first_index(table) == NULL
+ && !(ignore_err & DICT_ERR_IGNORE_CORRUPT)) {
+ ib_logf(IB_LOG_LEVEL_WARN,
+ "Failed to load the "
+ "clustered index for table %s "
+ "because of the following error: %s. "
+ "Refusing to load the rest of the "
+ "indexes (if any) and the whole table "
+ "altogether.", table->name, err_msg);
+ error = DB_CORRUPTION;
+ goto func_exit;
+ }
+
break;
} else if (err_msg == dict_load_index_del) {
/* Skip delete-marked records. */
@@ -1510,15 +1884,15 @@ dict_load_indexes(
subsequent checks are relevant for the supported types. */
if (index->type & ~(DICT_CLUSTERED | DICT_UNIQUE
| DICT_CORRUPT | DICT_FTS)) {
- fprintf(stderr,
- "InnoDB: Error: unknown type %lu"
- " of index %s of table %s\n",
+ ib_logf(IB_LOG_LEVEL_ERROR,
+ "Unknown type %lu of index %s of table %s",
(ulong) index->type, index->name, table->name);
error = DB_UNSUPPORTED;
dict_mem_index_free(index);
goto func_exit;
} else if (index->page == FIL_NULL
+ && !table->ibd_file_missing
&& (!(index->type & DICT_FTS))) {
fprintf(stderr,
@@ -1560,7 +1934,7 @@ corrupted:
" is not clustered!\n", stderr);
goto corrupted;
- } else if (table->id < DICT_HDR_FIRST_ID
+ } else if (dict_is_sys_table(table->id)
&& (dict_index_is_clust(index)
|| ((table == dict_sys->sys_tables)
&& !strcmp("ID_IND", index->name)))) {
@@ -1570,8 +1944,10 @@ corrupted:
dict_mem_index_free(index);
} else {
dict_load_fields(index, heap);
- error = dict_index_add_to_cache(table, index,
- index->page, FALSE);
+
+ error = dict_index_add_to_cache(
+ table, index, index->page, FALSE);
+
/* The data dictionary tables should never contain
invalid index definitions. If we ignored this error
and simply did not load this index definition, the
@@ -1629,7 +2005,7 @@ dict_load_table_low(
rec_get_nth_field_offs_old(
rec, DICT_FLD__SYS_TABLES__NAME, &len);
- if (len < 1 || len == UNIV_SQL_NULL) {
+ if (len == 0 || len == UNIV_SQL_NULL) {
err_len:
return("incorrect column length in SYS_TABLES");
}
@@ -1751,6 +2127,77 @@ err_len:
}
/********************************************************************//**
+Using the table->heap, copy the null-terminated filepath into
+table->data_dir_path and replace the 'databasename/tablename.ibd'
+portion with 'tablename'.
+This allows SHOW CREATE TABLE to return the correct DATA DIRECTORY path.
+Make this data directory path only if it has not yet been saved. */
+UNIV_INTERN
+void
+dict_save_data_dir_path(
+/*====================*/
+ dict_table_t* table, /*!< in/out: table */
+ char* filepath) /*!< in: filepath of tablespace */
+{
+ ut_ad(mutex_own(&(dict_sys->mutex)));
+ ut_a(DICT_TF_HAS_DATA_DIR(table->flags));
+
+ ut_a(!table->data_dir_path);
+ ut_a(filepath);
+
+ /* Be sure this filepath is not the default filepath. */
+ char* default_filepath = fil_make_ibd_name(table->name, false);
+ if (strcmp(filepath, default_filepath)) {
+ ulint pathlen = strlen(filepath);
+ ut_a(pathlen < OS_FILE_MAX_PATH);
+ ut_a(0 == strcmp(filepath + pathlen - 4, ".ibd"));
+
+ table->data_dir_path = mem_heap_strdup(table->heap, filepath);
+ os_file_make_data_dir_path(table->data_dir_path);
+ } else {
+ /* This does not change SYS_DATAFILES or SYS_TABLES
+ or FSP_FLAGS on the header page of the tablespace,
+ but it makes dict_table_t consistent */
+ table->flags &= ~DICT_TF_MASK_DATA_DIR;
+ }
+ mem_free(default_filepath);
+}
+
+/*****************************************************************//**
+Make sure the data_file_name is saved in dict_table_t if needed. Try to
+read it from the file dictionary first, then from SYS_DATAFILES. */
+UNIV_INTERN
+void
+dict_get_and_save_data_dir_path(
+/*============================*/
+ dict_table_t* table, /*!< in/out: table */
+ bool dict_mutex_own) /*!< in: true if dict_sys->mutex
+ is owned already */
+{
+ if (DICT_TF_HAS_DATA_DIR(table->flags)
+ && (!table->data_dir_path)) {
+ char* path = fil_space_get_first_path(table->space);
+
+ if (!dict_mutex_own) {
+ dict_mutex_enter_for_mysql();
+ }
+ if (!path) {
+ path = dict_get_first_path(
+ table->space, table->name);
+ }
+
+ if (path) {
+ dict_save_data_dir_path(table, path);
+ mem_free(path);
+ }
+
+ if (!dict_mutex_own) {
+ dict_mutex_exit_for_mysql();
+ }
+ }
+}
+
+/********************************************************************//**
Loads a table definition and also all its index definitions, and also
the cluster definition if the table is a member in a cluster. Also loads
all foreign key constraints where the foreign key is in the table or where
@@ -1770,6 +2217,7 @@ dict_load_table(
/*!< in: error to be ignored when loading
table and its indexes' definition */
{
+ dberr_t err;
dict_table_t* table;
dict_table_t* sys_tables;
btr_pcur_t pcur;
@@ -1780,7 +2228,7 @@ dict_load_table(
const rec_t* rec;
const byte* field;
ulint len;
- ulint err;
+ char* filepath = NULL;
const char* err_msg;
mtr_t mtr;
@@ -1843,39 +2291,71 @@ err_exit:
goto err_exit;
}
+ char table_name[MAX_FULL_NAME_LEN + 1];
+
+ innobase_format_name(table_name, sizeof(table_name), name, FALSE);
+
+ btr_pcur_close(&pcur);
+ mtr_commit(&mtr);
+
if (table->space == 0) {
/* The system tablespace is always available. */
+ } else if (table->flags2 & DICT_TF2_DISCARDED) {
+
+ ib_logf(IB_LOG_LEVEL_WARN,
+ "Table '%s' tablespace is set as discarded.",
+ table_name);
+
+ table->ibd_file_missing = TRUE;
+
} else if (!fil_space_for_table_exists_in_mem(
- table->space, name, FALSE, FALSE)) {
+ table->space, name, FALSE, FALSE, true, heap,
+ table->id)) {
- if (table->flags2 & DICT_TF2_TEMPORARY) {
+ if (DICT_TF2_FLAG_IS_SET(table, DICT_TF2_TEMPORARY)) {
/* Do not bother to retry opening temporary tables. */
table->ibd_file_missing = TRUE;
+
} else {
- ut_print_timestamp(stderr);
- fprintf(stderr,
- " InnoDB: error: space object of table ");
- ut_print_filename(stderr, name);
- fprintf(stderr, ",\n"
- "InnoDB: space id %lu did not exist in memory."
- " Retrying an open.\n",
- (ulong) table->space);
- /* Try to open the tablespace */
- if (!fil_open_single_table_tablespace(
- TRUE, table->space,
+ ib_logf(IB_LOG_LEVEL_ERROR,
+ "Failed to find tablespace for table '%s' "
+ "in the cache. Attempting to load the "
+ "tablespace with space id %lu.",
+ table_name, (ulong) table->space);
+
+ /* Use the remote filepath if needed. */
+ if (DICT_TF_HAS_DATA_DIR(table->flags)) {
+ /* This needs to be added to the table
+ from SYS_DATAFILES */
+ dict_get_and_save_data_dir_path(table, true);
+
+ if (table->data_dir_path) {
+ filepath = os_file_make_remote_pathname(
+ table->data_dir_path,
+ table->name, "ibd");
+ }
+ }
+
+ /* Try to open the tablespace. We set the
+ 2nd param (fix_dict = false) here because we
+ do not have an x-lock on dict_operation_lock */
+ err = fil_open_single_table_tablespace(
+ true, false, table->space,
dict_tf_to_fsp_flags(table->flags),
- name)) {
+ name, filepath);
+
+ if (err != DB_SUCCESS) {
/* We failed to find a sensible
tablespace file */
table->ibd_file_missing = TRUE;
}
+ if (filepath) {
+ mem_free(filepath);
+ }
}
}
- btr_pcur_close(&pcur);
- mtr_commit(&mtr);
-
dict_load_columns(table, heap);
if (cached) {
@@ -1886,7 +2366,15 @@ err_exit:
mem_heap_empty(heap);
- err = dict_load_indexes(table, heap, ignore_err);
+ /* If there is no tablespace for the table then we only need to
+ load the index definitions. So that we can IMPORT the tablespace
+ later. */
+ if (table->ibd_file_missing) {
+ err = dict_load_indexes(
+ table, heap, DICT_ERR_IGNORE_ALL);
+ } else {
+ err = dict_load_indexes(table, heap, ignore_err);
+ }
if (err == DB_INDEX_CORRUPT) {
/* Refuse to load the table if the table has a corrupted
@@ -1920,7 +2408,8 @@ err_exit:
of the error condition, since the user may want to dump data from the
clustered index. However we load the foreign key information only if
all indexes were loaded. */
- if (!cached) {
+ if (!cached || table->ibd_file_missing) {
+ /* Don't attempt to load the indexes from disk. */
} else if (err == DB_SUCCESS) {
err = dict_load_foreigns(table->name, TRUE, TRUE);
@@ -1937,11 +2426,15 @@ err_exit:
Otherwise refuse to load the table */
index = dict_table_get_first_index(table);
- if (!srv_force_recovery || !index
+ if (!srv_force_recovery
+ || !index
|| !dict_index_is_clust(index)) {
+
dict_table_remove_from_cache(table);
table = NULL;
- } else if (dict_index_is_corrupted(index)) {
+
+ } else if (dict_index_is_corrupted(index)
+ && !table->ibd_file_missing) {
/* It is possible we force to load a corrupted
clustered index if srv_load_corrupted is set.
@@ -1949,36 +2442,28 @@ err_exit:
table->corrupted = TRUE;
}
}
-#if 0
- if (err != DB_SUCCESS && table != NULL) {
- mutex_enter(&dict_foreign_err_mutex);
-
- ut_print_timestamp(stderr);
-
- fprintf(stderr,
- " InnoDB: Error: could not make a foreign key"
- " definition to match\n"
- "InnoDB: the foreign key table"
- " or the referenced table!\n"
- "InnoDB: The data dictionary of InnoDB is corrupt."
- " You may need to drop\n"
- "InnoDB: and recreate the foreign key table"
- " or the referenced table.\n"
- "InnoDB: Submit a detailed bug report"
- " to http://bugs.mysql.com\n"
- "InnoDB: Latest foreign key error printout:\n%s\n",
- dict_foreign_err_buf);
-
- mutex_exit(&dict_foreign_err_mutex);
- }
-#endif /* 0 */
func_exit:
mem_heap_free(heap);
- ut_ad(!table || ignore_err != DICT_ERR_IGNORE_NONE
+ ut_ad(!table
+ || ignore_err != DICT_ERR_IGNORE_NONE
+ || table->ibd_file_missing
|| !table->corrupted);
+ if (table && table->fts) {
+ if (!(dict_table_has_fts_index(table)
+ || DICT_TF2_FLAG_IS_SET(table, DICT_TF2_FTS_HAS_DOC_ID)
+ || DICT_TF2_FLAG_IS_SET(table, DICT_TF2_FTS_ADD_DOC_ID))) {
+ /* the table->fts could be created in dict_load_column
+ when a user defined FTS_DOC_ID is present, but no
+ FTS */
+ fts_free(table);
+ } else {
+ fts_optimize_add_table(table);
+ }
+ }
+
return(table);
}
@@ -2019,6 +2504,7 @@ dict_load_table_on_id(
sys_table_ids = dict_table_get_next_index(
dict_table_get_first_index(sys_tables));
ut_ad(!dict_table_is_comp(sys_tables));
+ ut_ad(!dict_index_is_clust(sys_table_ids));
heap = mem_heap_create(256);
tuple = dtuple_create(heap, 1);
@@ -2099,15 +2585,20 @@ dict_load_sys_table(
}
/********************************************************************//**
-Loads foreign key constraint col names (also for the referenced table). */
+Loads foreign key constraint col names (also for the referenced table).
+Members that must be set (and valid) in foreign:
+foreign->heap
+foreign->n_fields
+foreign->id ('\0'-terminated)
+Members that will be created and set by this function:
+foreign->foreign_col_names[i]
+foreign->referenced_col_names[i]
+(for i=0..foreign->n_fields-1) */
static
void
dict_load_foreign_cols(
/*===================*/
- const char* id, /*!< in: foreign constraint id, not
- necessary '\0'-terminated */
- ulint id_len, /*!< in: id length */
- dict_foreign_t* foreign)/*!< in: foreign constraint object */
+ dict_foreign_t* foreign)/*!< in/out: foreign constraint object */
{
dict_table_t* sys_foreign_cols;
dict_index_t* sys_index;
@@ -2119,9 +2610,12 @@ dict_load_foreign_cols(
ulint len;
ulint i;
mtr_t mtr;
+ size_t id_len;
ut_ad(mutex_own(&(dict_sys->mutex)));
+ id_len = strlen(foreign->id);
+
foreign->foreign_col_names = static_cast<const char**>(
mem_heap_alloc(foreign->heap,
foreign->n_fields * sizeof(void*)));
@@ -2140,7 +2634,7 @@ dict_load_foreign_cols(
tuple = dtuple_create(foreign->heap, 1);
dfield = dtuple_get_nth_field(tuple, 0);
- dfield_set_data(dfield, id, id_len);
+ dfield_set_data(dfield, foreign->id, id_len);
dict_index_copy_types(tuple, sys_index, 1);
btr_pcur_open_on_user_rec(sys_index, tuple, PAGE_CUR_GE,
@@ -2154,8 +2648,42 @@ dict_load_foreign_cols(
field = rec_get_nth_field_old(
rec, DICT_FLD__SYS_FOREIGN_COLS__ID, &len);
- ut_a(len == id_len);
- ut_a(ut_memcmp(id, field, len) == 0);
+
+ if (len != id_len || ut_memcmp(foreign->id, field, len) != 0) {
+ const rec_t* pos;
+ ulint pos_len;
+ const rec_t* for_col_name;
+ ulint for_col_name_len;
+ const rec_t* ref_col_name;
+ ulint ref_col_name_len;
+
+ pos = rec_get_nth_field_old(
+ rec, DICT_FLD__SYS_FOREIGN_COLS__POS,
+ &pos_len);
+
+ for_col_name = rec_get_nth_field_old(
+ rec, DICT_FLD__SYS_FOREIGN_COLS__FOR_COL_NAME,
+ &for_col_name_len);
+
+ ref_col_name = rec_get_nth_field_old(
+ rec, DICT_FLD__SYS_FOREIGN_COLS__REF_COL_NAME,
+ &ref_col_name_len);
+
+ ib_logf(IB_LOG_LEVEL_ERROR,
+ "Unable to load columns names for foreign "
+ "key '%s' because it was not found in "
+ "InnoDB internal table SYS_FOREIGN_COLS. The "
+ "closest entry we found is: "
+ "(ID='%.*s', POS=%lu, FOR_COL_NAME='%.*s', "
+ "REF_COL_NAME='%.*s')",
+ foreign->id,
+ (int) len, field,
+ mach_read_from_4(pos),
+ (int) for_col_name_len, for_col_name,
+ (int) ref_col_name_len, ref_col_name);
+
+ ut_error;
+ }
field = rec_get_nth_field_old(
rec, DICT_FLD__SYS_FOREIGN_COLS__POS, &len);
@@ -2182,13 +2710,12 @@ dict_load_foreign_cols(
/***********************************************************************//**
Loads a foreign key constraint to the dictionary cache.
@return DB_SUCCESS or error code */
-static
-ulint
+static __attribute__((nonnull, warn_unused_result))
+dberr_t
dict_load_foreign(
/*==============*/
- const char* id, /*!< in: foreign constraint id, not
- necessary '\0'-terminated */
- ulint id_len, /*!< in: id length */
+ const char* id, /*!< in: foreign constraint id, must be
+ '\0'-terminated */
ibool check_charsets,
/*!< in: TRUE=check charset compatibility */
ibool check_recursive)
@@ -2210,9 +2737,12 @@ dict_load_foreign(
mtr_t mtr;
dict_table_t* for_table;
dict_table_t* ref_table;
+ size_t id_len;
ut_ad(mutex_own(&(dict_sys->mutex)));
+ id_len = strlen(id);
+
heap2 = mem_heap_create(1000);
mtr_start(&mtr);
@@ -2238,8 +2768,8 @@ dict_load_foreign(
fprintf(stderr,
"InnoDB: Error: cannot load foreign constraint "
- "%.*s: could not find the relevant record in "
- "SYS_FOREIGN\n", (int) id_len, id);
+ "%s: could not find the relevant record in "
+ "SYS_FOREIGN\n", id);
btr_pcur_close(&pcur);
mtr_commit(&mtr);
@@ -2255,8 +2785,8 @@ dict_load_foreign(
fprintf(stderr,
"InnoDB: Error: cannot load foreign constraint "
- "%.*s: found %.*s instead in SYS_FOREIGN\n",
- (int) id_len, id, (int) len, field);
+ "%s: found %.*s instead in SYS_FOREIGN\n",
+ id, (int) len, field);
btr_pcur_close(&pcur);
mtr_commit(&mtr);
@@ -2301,7 +2831,7 @@ dict_load_foreign(
btr_pcur_close(&pcur);
mtr_commit(&mtr);
- dict_load_foreign_cols(id, id_len, foreign);
+ dict_load_foreign_cols(foreign);
ref_table = dict_table_check_if_in_cache_low(
foreign->referenced_table_name_lookup);
@@ -2371,7 +2901,7 @@ cache already contains all constraints where the other relevant table is
already in the dictionary cache.
@return DB_SUCCESS or error code */
UNIV_INTERN
-ulint
+dberr_t
dict_load_foreigns(
/*===============*/
const char* table_name, /*!< in: table name */
@@ -2389,7 +2919,7 @@ dict_load_foreigns(
const rec_t* rec;
const byte* field;
ulint len;
- ulint err;
+ dberr_t err;
mtr_t mtr;
ut_ad(mutex_own(&(dict_sys->mutex)));
@@ -2414,6 +2944,7 @@ dict_load_foreigns(
sec_index = dict_table_get_next_index(
dict_table_get_first_index(sys_foreign));
+ ut_ad(!dict_index_is_clust(sec_index));
start_load:
tuple = dtuple_create_from_mem(tuple_buf, sizeof(tuple_buf), 1);
@@ -2436,7 +2967,6 @@ loop:
/* Now we have the record in the secondary index containing a table
name and a foreign constraint ID */
- rec = btr_pcur_get_rec(&pcur);
field = rec_get_nth_field_old(
rec, DICT_FLD__SYS_FOREIGN_FOR_NAME__NAME, &len);
@@ -2475,14 +3005,21 @@ loop:
field = rec_get_nth_field_old(
rec, DICT_FLD__SYS_FOREIGN_FOR_NAME__ID, &len);
+ /* Copy the string because the page may be modified or evicted
+ after mtr_commit() below. */
+ char fk_id[MAX_TABLE_NAME_LEN + 1];
+
+ ut_a(len <= MAX_TABLE_NAME_LEN);
+ memcpy(fk_id, field, len);
+ fk_id[len] = '\0';
+
btr_pcur_store_position(&pcur, &mtr);
mtr_commit(&mtr);
/* Load the foreign constraint definition to the dictionary cache */
- err = dict_load_foreign((char*) field, len, check_charsets,
- check_recursive);
+ err = dict_load_foreign(fk_id, check_charsets, check_recursive);
if (err != DB_SUCCESS) {
btr_pcur_close(&pcur);
diff --git a/storage/innobase/dict/dict0mem.cc b/storage/innobase/dict/dict0mem.cc
index 28b935d2e58..116a6a6d96a 100644
--- a/storage/innobase/dict/dict0mem.cc
+++ b/storage/innobase/dict/dict0mem.cc
@@ -1,6 +1,7 @@
/*****************************************************************************
-Copyright (c) 1996, 2011, Oracle and/or its affiliates. All Rights Reserved.
+Copyright (c) 1996, 2012, Oracle and/or its affiliates. All Rights Reserved.
+Copyright (c) 2012, Facebook Inc.
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
@@ -35,8 +36,9 @@ Created 1/8/1996 Heikki Tuuri
#include "dict0dict.h"
#include "fts0priv.h"
#ifndef UNIV_HOTBACKUP
-#include "ha_prototypes.h" /* innobase_casedn_str(),
+# include "ha_prototypes.h" /* innobase_casedn_str(),
innobase_get_lower_case_table_names */
+# include "mysql_com.h" /* NAME_LEN */
# include "lock0lock.h"
#endif /* !UNIV_HOTBACKUP */
#ifdef UNIV_BLOB_DEBUG
@@ -51,6 +53,10 @@ Created 1/8/1996 Heikki Tuuri
UNIV_INTERN mysql_pfs_key_t autoinc_mutex_key;
#endif /* UNIV_PFS_MUTEX */
+/** Prefix for tmp tables, adopted from sql/table.h */
+#define tmp_file_prefix "#sql"
+#define tmp_file_prefix_length 4
+
/**********************************************************************//**
Creates a table memory object.
@return own: table object */
@@ -60,9 +66,7 @@ dict_mem_table_create(
/*==================*/
const char* name, /*!< in: table name */
ulint space, /*!< in: space where the clustered index of
- the table is placed; this parameter is
- ignored if the table is made a member of
- a cluster */
+ the table is placed */
ulint n_cols, /*!< in: number of columns */
ulint flags, /*!< in: table flags */
ulint flags2) /*!< in: table flags2 */
@@ -71,7 +75,7 @@ dict_mem_table_create(
mem_heap_t* heap;
ut_ad(name);
- dict_tf_validate(flags);
+ ut_a(dict_tf_is_valid(flags));
ut_a(!(flags2 & ~DICT_TF2_BIT_MASK));
heap = mem_heap_create(DICT_HEAP_SIZE);
@@ -115,7 +119,6 @@ dict_mem_table_create(
|| DICT_TF2_FLAG_IS_SET(table, DICT_TF2_FTS_ADD_DOC_ID)) {
table->fts = fts_create(table);
table->fts->cache = fts_cache_create(table);
- fts_optimize_add_table(table);
} else {
table->fts = NULL;
}
@@ -243,6 +246,156 @@ dict_mem_table_add_col(
dict_mem_fill_column_struct(col, i, mtype, prtype, len);
}
+/**********************************************************************//**
+Renames a column of a table in the data dictionary cache. */
+static __attribute__((nonnull))
+void
+dict_mem_table_col_rename_low(
+/*==========================*/
+ dict_table_t* table, /*!< in/out: table */
+ unsigned i, /*!< in: column offset corresponding to s */
+ const char* to, /*!< in: new column name */
+ const char* s) /*!< in: pointer to table->col_names */
+{
+ size_t from_len = strlen(s), to_len = strlen(to);
+
+ ut_ad(i < table->n_def);
+ ut_ad(from_len <= NAME_LEN);
+ ut_ad(to_len <= NAME_LEN);
+
+ if (from_len == to_len) {
+ /* The easy case: simply replace the column name in
+ table->col_names. */
+ strcpy(const_cast<char*>(s), to);
+ } else {
+ /* We need to adjust all affected index->field
+ pointers, as in dict_index_add_col(). First, copy
+ table->col_names. */
+ ulint prefix_len = s - table->col_names;
+
+ for (; i < table->n_def; i++) {
+ s += strlen(s) + 1;
+ }
+
+ ulint full_len = s - table->col_names;
+ char* col_names;
+
+ if (to_len > from_len) {
+ col_names = static_cast<char*>(
+ mem_heap_alloc(
+ table->heap,
+ full_len + to_len - from_len));
+
+ memcpy(col_names, table->col_names, prefix_len);
+ } else {
+ col_names = const_cast<char*>(table->col_names);
+ }
+
+ memcpy(col_names + prefix_len, to, to_len);
+ memmove(col_names + prefix_len + to_len,
+ table->col_names + (prefix_len + from_len),
+ full_len - (prefix_len + from_len));
+
+ /* Replace the field names in every index. */
+ for (dict_index_t* index = dict_table_get_first_index(table);
+ index != NULL;
+ index = dict_table_get_next_index(index)) {
+ ulint n_fields = dict_index_get_n_fields(index);
+
+ for (ulint i = 0; i < n_fields; i++) {
+ dict_field_t* field
+ = dict_index_get_nth_field(
+ index, i);
+ ulint name_ofs
+ = field->name - table->col_names;
+ if (name_ofs <= prefix_len) {
+ field->name = col_names + name_ofs;
+ } else {
+ ut_a(name_ofs < full_len);
+ field->name = col_names
+ + name_ofs + to_len - from_len;
+ }
+ }
+ }
+
+ table->col_names = col_names;
+ }
+
+ /* Replace the field names in every foreign key constraint. */
+ for (dict_foreign_t* foreign = UT_LIST_GET_FIRST(table->foreign_list);
+ foreign != NULL;
+ foreign = UT_LIST_GET_NEXT(foreign_list, foreign)) {
+ for (unsigned f = 0; f < foreign->n_fields; f++) {
+ /* These can point straight to
+ table->col_names, because the foreign key
+ constraints will be freed at the same time
+ when the table object is freed. */
+ foreign->foreign_col_names[f]
+ = dict_index_get_nth_field(
+ foreign->foreign_index, f)->name;
+ }
+ }
+
+ for (dict_foreign_t* foreign = UT_LIST_GET_FIRST(
+ table->referenced_list);
+ foreign != NULL;
+ foreign = UT_LIST_GET_NEXT(referenced_list, foreign)) {
+ for (unsigned f = 0; f < foreign->n_fields; f++) {
+ /* foreign->referenced_col_names[] need to be
+ copies, because the constraint may become
+ orphan when foreign_key_checks=0 and the
+ parent table is dropped. */
+
+ const char* col_name = dict_index_get_nth_field(
+ foreign->referenced_index, f)->name;
+
+ if (strcmp(foreign->referenced_col_names[f],
+ col_name)) {
+ char** rc = const_cast<char**>(
+ foreign->referenced_col_names + f);
+ size_t col_name_len_1 = strlen(col_name) + 1;
+
+ if (col_name_len_1 <= strlen(*rc) + 1) {
+ memcpy(*rc, col_name, col_name_len_1);
+ } else {
+ *rc = static_cast<char*>(
+ mem_heap_dup(
+ foreign->heap,
+ col_name,
+ col_name_len_1));
+ }
+ }
+ }
+ }
+}
+
+/**********************************************************************//**
+Renames a column of a table in the data dictionary cache. */
+UNIV_INTERN
+void
+dict_mem_table_col_rename(
+/*======================*/
+ dict_table_t* table, /*!< in/out: table */
+ unsigned nth_col,/*!< in: column index */
+ const char* from, /*!< in: old column name */
+ const char* to) /*!< in: new column name */
+{
+ const char* s = table->col_names;
+
+ ut_ad(nth_col < table->n_def);
+
+ for (unsigned i = 0; i < nth_col; i++) {
+ size_t len = strlen(s);
+ ut_ad(len > 0);
+ s += len + 1;
+ }
+
+ /* This could fail if the data dictionaries are out of sync.
+ Proceed with the renaming anyway. */
+ ut_ad(!strcmp(from, s));
+
+ dict_mem_table_col_rename_low(table, nth_col, to, s);
+}
/**********************************************************************//**
This function populates a dict_col_t memory structure with
@@ -304,6 +457,8 @@ dict_mem_index_create(
dict_mem_fill_index_struct(index, heap, table_name, index_name,
space, type, n_fields);
+ os_fast_mutex_init(zip_pad_mutex_key, &index->zip_pad.mutex);
+
return(index);
}
@@ -436,5 +591,31 @@ dict_mem_index_free(
}
#endif /* UNIV_BLOB_DEBUG */
+ os_fast_mutex_free(&index->zip_pad.mutex);
+
mem_heap_free(index->heap);
}
+
+/*******************************************************************//**
+Create a temporary tablename.
+@return temporary tablename suitable for InnoDB use */
+UNIV_INTERN
+char*
+dict_mem_create_temporary_tablename(
+/*================================*/
+ mem_heap_t* heap, /*!< in: memory heap */
+ const char* dbtab, /*!< in: database/table name */
+ table_id_t id) /*!< in: InnoDB table id */
+{
+ const char* dbend = strchr(dbtab, '/');
+ ut_ad(dbend);
+ size_t dblen = dbend - dbtab + 1;
+ size_t size = tmp_file_prefix_length + 4 + 9 + 9 + dblen;
+
+ char* name = static_cast<char*>(mem_heap_alloc(heap, size));
+ memcpy(name, dbtab, dblen);
+ ut_snprintf(name + dblen, size - dblen,
+ tmp_file_prefix "-ib" UINT64PF, id);
+ return(name);
+}
+
diff --git a/storage/innobase/dict/dict0stats.cc b/storage/innobase/dict/dict0stats.cc
index eebf6b1ec26..ff7e1ce642c 100644
--- a/storage/innobase/dict/dict0stats.cc
+++ b/storage/innobase/dict/dict0stats.cc
@@ -1,6 +1,6 @@
/*****************************************************************************
-Copyright (c) 2009, 2011, Oracle and/or its affiliates. All Rights Reserved.
+Copyright (c) 2009, 2012, Oracle and/or its affiliates. All Rights Reserved.
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
@@ -29,27 +29,27 @@ Created Jan 06, 2010 Vasil Dimov
#include "btr0btr.h" /* btr_get_size() */
#include "btr0cur.h" /* btr_estimate_number_of_different_key_vals() */
-#include "dict0dict.h" /* dict_table_get_first_index() */
+#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" /* db_err */
+#include "db0err.h" /* dberr_t */
#include "dyn0dyn.h" /* dyn_array* */
+#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_struct */
+#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 "ha_prototypes.h" /* innobase_strcasecmp() */
+#include "ut0ut.h" /* ut_format_name(), ut_time() */
/* Sampling algorithm description @{
-The algorithm is controlled by one number - srv_stats_persistent_sample_pages,
+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).
@@ -124,126 +124,34 @@ where n=1..n_uniq.
#define DEBUG_PRINTF(fmt, ...) /* noop */
#endif /* UNIV_STATS_DEBUG */
-/* number of distinct records on a given level that are required to stop
-descending to lower levels and fetch
-srv_stats_persistent_sample_pages records from that level */
-#define N_DIFF_REQUIRED (srv_stats_persistent_sample_pages * 10)
+/* Gets the number of leaf pages to sample in persistent stats estimation */
+#define N_SAMPLE_PAGES(index) \
+ ((index)->table->stats_sample_pages != 0 ? \
+ (index)->table->stats_sample_pages : \
+ srv_stats_persistent_sample_pages)
-/** Open handles on the stats tables. Currently this is used to increase the
-reference count of the stats tables. */
-typedef struct dict_stats_struct {
- dict_table_t* table_stats; /*!< Handle to open TABLE_STATS_NAME */
- dict_table_t* index_stats; /*!< Handle to open INDEX_STATS_NAME */
-} dict_stats_t;
+/* 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)
/*********************************************************************//**
-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.
-dict_stats_update_transient() @{ */
-static
-void
-dict_stats_update_transient(
-/*========================*/
- dict_table_t* table) /*!< in/out: table */
+Checks whether an index should be ignored in stats manipulations:
+* stats fetch
+* stats recalc
+* stats save
+dict_stats_should_ignore_index() @{
+@return true if exists and all tables are ok */
+UNIV_INLINE
+bool
+dict_stats_should_ignore_index(
+/*===========================*/
+ const dict_index_t* index) /*!< in: index */
{
- 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 (index == NULL) {
- /* Table definition is corrupt */
- ut_print_timestamp(stderr);
- fprintf(stderr, " InnoDB: table %s has no indexes. "
- "Cannot calculate statistics.\n", table->name);
- return;
- }
-
- do {
-
- if (index->type & DICT_FTS) {
- index = dict_table_get_next_index(index);
- continue;
- }
-
- if (UNIV_LIKELY
- (srv_force_recovery < SRV_FORCE_NO_IBUF_MERGE
- || (srv_force_recovery < SRV_FORCE_NO_LOG_REDO
- && dict_index_is_clust(index)))) {
- 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:
- goto fake_statistics;
- case 0:
- /* The root node of the tree is a leaf */
- size = 1;
- }
-
- sum_of_index_sizes += index->stat_index_size;
-
- index->stat_n_leaf_pages = size;
-
- btr_estimate_number_of_different_key_vals(index);
- } else {
- /* 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. */
- ulint i;
-
-fake_statistics:
- sum_of_index_sizes++;
- index->stat_index_size = index->stat_n_leaf_pages = 1;
-
- for (i = dict_index_get_n_unique(index); i; ) {
- index->stat_n_diff_key_vals[i--] = 1;
- }
-
- memset(index->stat_n_non_null_key_vals, 0,
- (1 + dict_index_get_n_unique(index))
- * sizeof(*index->stat_n_non_null_key_vals));
- }
-
- index = dict_table_get_next_index(index);
- } while (index);
-
- index = dict_table_get_first_index(table);
-
- table->stat_n_rows = index->stat_n_diff_key_vals[
- dict_index_get_n_unique(index)];
-
- 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->stat_modified_counter = 0;
-
- table->stat_initialized = TRUE;
+ return((index->type & DICT_FTS)
+ || dict_index_is_corrupted(index)
+ || index->to_be_dropped
+ || *index->name == TEMP_INDEX_PREFIX);
}
/* @} */
@@ -251,24 +159,24 @@ fake_statistics:
Checks whether the persistent statistics storage exists and that all
tables have the proper structure.
dict_stats_persistent_storage_check() @{
-@return TRUE if exists and all tables are ok */
+@return true if exists and all tables are ok */
static
-ibool
+bool
dict_stats_persistent_storage_check(
/*================================*/
- ibool caller_has_dict_sys_mutex) /*!< in: TRUE if the caller
+ 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 /* NAME_LEN from mysql_com.h */},
+ DATA_NOT_NULL, 192},
{"table_name", DATA_VARMYSQL,
- DATA_NOT_NULL, 192 /* NAME_LEN from mysql_com.h */},
+ DATA_NOT_NULL, 192},
- {"last_update", DATA_INT,
- DATA_NOT_NULL | DATA_UNSIGNED, 4},
+ {"last_update", DATA_FIXBINARY,
+ DATA_NOT_NULL, 4},
{"n_rows", DATA_INT,
DATA_NOT_NULL | DATA_UNSIGNED, 8},
@@ -282,22 +190,24 @@ dict_stats_persistent_storage_check(
dict_table_schema_t table_stats_schema = {
TABLE_STATS_NAME,
UT_ARR_SIZE(table_stats_columns),
- 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 /* NAME_LEN from mysql_com.h */},
+ DATA_NOT_NULL, 192},
{"table_name", DATA_VARMYSQL,
- DATA_NOT_NULL, 192 /* NAME_LEN from mysql_com.h */},
+ DATA_NOT_NULL, 192},
{"index_name", DATA_VARMYSQL,
- DATA_NOT_NULL, 192 /* NAME_LEN from mysql_com.h */},
+ DATA_NOT_NULL, 192},
- {"last_update", DATA_INT,
- DATA_NOT_NULL | DATA_UNSIGNED, 4},
+ {"last_update", DATA_FIXBINARY,
+ DATA_NOT_NULL, 4},
{"stat_name", DATA_VARMYSQL,
DATA_NOT_NULL, 64*3},
@@ -314,11 +224,13 @@ dict_stats_persistent_storage_check(
dict_table_schema_t index_stats_schema = {
INDEX_STATS_NAME,
UT_ARR_SIZE(index_stats_columns),
- index_stats_columns
+ index_stats_columns,
+ 0 /* n_foreign */,
+ 0 /* n_referenced */
};
char errstr[512];
- enum db_err ret;
+ dberr_t ret;
if (!caller_has_dict_sys_mutex) {
mutex_enter(&(dict_sys->mutex));
@@ -339,24 +251,660 @@ dict_stats_persistent_storage_check(
mutex_exit(&(dict_sys->mutex));
}
- if (ret != DB_SUCCESS && ret != DB_TABLE_NOT_FOUND) {
+ if (ret != DB_SUCCESS) {
+ ut_print_timestamp(stderr);
+ fprintf(stderr, " InnoDB: Error: %s\n", errstr);
+ return(false);
+ }
+ /* else */
+
+ return(true);
+}
+/* @} */
+
+/*********************************************************************//**
+Executes a given SQL statement using the InnoDB internal SQL parser
+in its own transaction and commits it.
+This function will free the pinfo object.
+@return DB_SUCCESS or error code */
+static
+dberr_t
+dict_stats_exec_sql(
+/*================*/
+ pars_info_t* pinfo, /*!< in/out: pinfo to pass to que_eval_sql()
+ must already have any literals bound to it */
+ const char* sql) /*!< in: SQL string to execute */
+{
+ trx_t* trx;
+ dberr_t err;
+
+#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);
+ }
+
+ trx = trx_allocate_for_background();
+ trx_start_if_not_started(trx);
+
+ err = que_eval_sql(pinfo, sql, FALSE, trx); /* pinfo is freed here */
+
+ 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);
+ }
+
+ 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;
+
+ 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);
+ }
+
+ 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().
+dict_stats_table_clone_free() @{ */
+static
+void
+dict_stats_table_clone_free(
+/*========================*/
+ dict_table_t* t) /*!< in: dummy table object to free */
+{
+ 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))
+dict_stats_empty_index() @{ */
+static
+void
+dict_stats_empty_index(
+/*===================*/
+ dict_index_t* index) /*!< in/out: index */
+{
+ 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;
+}
+/* @} */
+
+/*********************************************************************//**
+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.
+dict_stats_empty_table() @{ */
+static
+void
+dict_stats_empty_table(
+/*===================*/
+ dict_table_t* table) /*!< in/out: table */
+{
+ /* 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);
+ }
+
+ 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 */
+{
+ 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)) {
+ 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);
+ 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);
+ } 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->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
+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(
+/*=======================*/
+ const 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);
+
+ 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().
+dict_stats_snapshot_free() @{ */
+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.
+dict_stats_update_transient_for_index() @{ */
+static
+void
+dict_stats_update_transient_for_index(
+/*==================================*/
+ dict_index_t* index) /*!< in/out: index */
+{
+ if (UNIV_LIKELY
+ (srv_force_recovery < SRV_FORCE_NO_IBUF_MERGE
+ || (srv_force_recovery < SRV_FORCE_NO_LOG_REDO
+ && dict_index_is_clust(index)))) {
+ 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);
+ return;
+ case 0:
+ /* The root node of the tree is a leaf */
+ size = 1;
+ }
+
+ index->stat_n_leaf_pages = size;
+
+ btr_estimate_number_of_different_key_vals(index);
+ } else {
+ /* 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);
+ }
+}
+/* @} */
+
+/*********************************************************************//**
+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.
+dict_stats_update_transient() @{ */
+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);
+ return;
+ } else if (index == NULL) {
+ /* Table definition is corrupt */
+
+ char buf[MAX_FULL_NAME_LEN];
ut_print_timestamp(stderr);
- fprintf(stderr, " InnoDB: %s\n", errstr);
+ 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);
+ 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);
+
+ if (dict_stats_should_ignore_index(index)) {
+ continue;
+ }
+
+ dict_stats_update_transient_for_index(index);
+
+ sum_of_index_sizes += index->stat_index_size;
}
- /* We return silently if some of the tables are not present because
- this code is executed during open table. By design we check if the
- persistent statistics storage is present and whether there are stats
- for the table being opened and if so, then we use them, otherwise we
- silently switch back to using the transient stats. */
- return(ret == DB_SUCCESS);
+ 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 = srv_stats_persistent_sample_pages
+let N = N_SAMPLE_PAGES(index)
dict_stats_analyze_index()
for each n_prefix
@@ -375,14 +923,11 @@ dict_stats_analyze_index()
/*********************************************************************//**
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[]. Notice that n_diff[] must be able to store
-n_uniq+1 numbers because the results are saved in
-n_diff[1] .. n_diff[n_uniq]. The total number of records on the level is
-saved in total_recs.
+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[1..n_uniq], records indexing starts from the leftmost
-record on the level and continues cross pages boundaries, counting from 0.
-dict_stats_analyze_index_level() @{ */
+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(
@@ -393,78 +938,87 @@ dict_stats_analyze_index_level(
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 */
- dyn_array_t* n_diff_boundaries)/*!< out: boundaries of the groups
+ dyn_array_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;
- dtuple_t* dtuple;
btr_pcur_t pcur;
- mtr_t mtr;
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 1..n_uniq (inclusive) */
- memset(n_diff, 0x0, (n_uniq + 1) * sizeof(*n_diff));
+ /* 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(256);
+ 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[1..n_uniq];
- n_diff_boundaries[0] is ignored to follow the same convention
- as n_diff[] */
+ /* reset the dynamic arrays n_diff_boundaries[0..n_uniq-1] */
if (n_diff_boundaries != NULL) {
- for (i = 1; i <= n_uniq; i++) {
+ for (i = 0; i < n_uniq; i++) {
dyn_array_free(&n_diff_boundaries[i]);
dyn_array_create(&n_diff_boundaries[i]);
}
}
- /* craft a record that is always smaller than the others,
- this way we are sure that the cursor pcur will be positioned
- on the leftmost record on the leftmost page on the desired level */
- dtuple = dtuple_create(heap, dict_index_get_n_unique(index));
- dict_table_copy_types(dtuple, index->table);
- dtuple_set_info_bits(dtuple, REC_INFO_MIN_REC_FLAG);
-
- mtr_start(&mtr);
+ /* Position pcur on the leftmost record on the leftmost page
+ on the desired level. */
- btr_pcur_open_low(index, level, dtuple, PAGE_CUR_LE, BTR_SEARCH_LEAF,
- &pcur, __FILE__, __LINE__, &mtr);
+ 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);
+ 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);
+ 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(
- page_rec_get_next_const(page_get_infimum_rec(page)),
- page_is_comp(page))));
-
- if (btr_pcur_is_before_first_on_page(&pcur)) {
- btr_pcur_move_to_next_on_page(&pcur);
- }
-
- if (btr_pcur_is_after_last_on_page(&pcur)) {
- btr_pcur_move_to_prev_on_page(&pcur);
- }
+ 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;
@@ -476,56 +1030,83 @@ dict_stats_analyze_index_level(
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)) {
+ btr_pcur_move_to_next_user_rec(&pcur, mtr)) {
ulint matched_fields = 0;
ulint matched_bytes = 0;
- ulint offsets_rec_onstack[REC_OFFS_NORMAL_SIZE];
- ulint* offsets_rec;
-
- rec_offs_init(offsets_rec_onstack);
+ 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 (page_rec_is_supremum(page_rec_get_next_const(rec))) {
+ if (rec_is_last_on_page) {
(*total_pages)++;
}
- /* skip delete-marked records */
- if (rec_get_deleted_flag(rec, page_is_comp(
- btr_pcur_get_page(&pcur)))) {
+ /* 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 &&
+ 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;
}
- offsets_rec = rec_get_offsets(rec, index, offsets_rec_onstack,
- n_uniq, &heap);
+ rec_offsets = rec_get_offsets(
+ rec, index, rec_offsets, n_uniq, &heap);
(*total_recs)++;
if (prev_rec != NULL) {
-
- ulint offsets_prev_rec_onstack[REC_OFFS_NORMAL_SIZE];
- ulint* offsets_prev_rec;
-
- rec_offs_init(offsets_prev_rec_onstack);
-
- offsets_prev_rec = rec_get_offsets(
- prev_rec, index, offsets_prev_rec_onstack,
+ prev_rec_offsets = rec_get_offsets(
+ prev_rec, index, prev_rec_offsets,
n_uniq, &heap);
cmp_rec_rec_with_match(rec,
prev_rec,
- offsets_rec,
- offsets_prev_rec,
+ rec_offsets,
+ prev_rec_offsets,
index,
FALSE,
&matched_fields,
&matched_bytes);
- for (i = matched_fields + 1; i <= n_uniq; i++) {
+ for (i = matched_fields; i < n_uniq; i++) {
if (n_diff_boundaries != NULL) {
/* push the index of the previous
@@ -553,17 +1134,18 @@ dict_stats_analyze_index_level(
}
/* increment the number of different keys
- for n_prefix=i */
+ 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 = 1; i <= n_uniq; i++) {
+ for (i = 0; i < n_uniq; i++) {
n_diff[i] = 1;
}
}
- if (page_rec_is_supremum(page_rec_get_next_const(rec))) {
+ if (rec_is_last_on_page) {
/* end of a page has been reached */
/* we need to copy the record instead of assigning
@@ -574,8 +1156,9 @@ dict_stats_analyze_index_level(
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(offsets_rec),
+ 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
@@ -584,12 +1167,14 @@ dict_stats_analyze_index_level(
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 */
+ 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);
@@ -605,7 +1190,7 @@ dict_stats_analyze_index_level(
/* 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 = 1; i <= n_uniq; i++) {
+ for (i = 0; i < n_uniq; i++) {
void* p;
ib_uint64_t idx;
@@ -619,10 +1204,10 @@ dict_stats_analyze_index_level(
}
/* now in n_diff_boundaries[i] there are exactly n_diff[i] integers,
- for i=1..n_uniq */
+ for i=0..n_uniq-1 */
#ifdef UNIV_STATS_DEBUG
- for (i = 1; i <= n_uniq; i++) {
+ for (i = 0; i < n_uniq; i++) {
DEBUG_PRINTF(" %s(): total recs: " UINT64PF
", total pages: " UINT64PF
@@ -654,9 +1239,11 @@ dict_stats_analyze_index_level(
}
#endif /* UNIV_STATS_DEBUG */
- btr_pcur_close(&pcur);
+ /* 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);
- mtr_commit(&mtr);
+ btr_pcur_close(&pcur);
if (prev_rec_buf != NULL) {
@@ -665,15 +1252,16 @@ dict_stats_analyze_index_level(
mem_heap_free(heap);
}
-/* @} */
/* aux enum for controlling the behavior of dict_stats_scan_page() @{ */
-typedef enum page_scan_method_enum {
- COUNT_ALL_NON_BORING, /* scan all records on the given page
- and count the number of distinct ones */
+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 */
-} page_scan_method_t;
+};
/* @} */
/*********************************************************************//**
@@ -715,11 +1303,18 @@ dict_stats_scan_page(
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*);
- rec = page_rec_get_next_const(page_get_infimum_rec(page));
+ 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;
+ }
+
+ rec = get_next(page_get_infimum_rec(page));
if (page_rec_is_supremum(rec)) {
- /* the page is empty */
+ /* the page is empty or contains only delete-marked records */
*n_diff = 0;
*out_rec = NULL;
return(NULL);
@@ -728,7 +1323,7 @@ dict_stats_scan_page(
offsets_rec = rec_get_offsets(rec, index, offsets_rec,
ULINT_UNDEFINED, &heap);
- next_rec = page_rec_get_next_const(rec);
+ next_rec = get_next(rec);
*n_diff = 1;
@@ -777,7 +1372,8 @@ dict_stats_scan_page(
offsets_rec = offsets_next_rec;
offsets_next_rec = offsets_tmp;
}
- next_rec = page_rec_get_next_const(next_rec);
+
+ next_rec = get_next(next_rec);
}
func_exit:
@@ -814,7 +1410,6 @@ dict_stats_analyze_index_below_cur(
ulint* offsets1;
ulint* offsets2;
ulint* offsets_rec;
- ulint root_height;
ib_uint64_t n_diff; /* the result */
ulint size;
@@ -841,8 +1436,6 @@ dict_stats_analyze_index_below_cur(
rec_offs_set_n_alloc(offsets1, size);
rec_offs_set_n_alloc(offsets2, size);
- root_height = btr_page_get_level(btr_root_get(index, mtr), mtr);
-
space = dict_index_get_space(index);
zip_size = dict_table_zip_size(index->table);
@@ -907,14 +1500,7 @@ dict_stats_analyze_index_below_cur(
offsets_rec = dict_stats_scan_page(
&rec, offsets1, offsets2, index, page, n_prefix,
- COUNT_ALL_NON_BORING, &n_diff);
-
- if (root_height > 0) {
-
- /* empty pages are allowed only if the whole B-tree is empty
- and contains a single empty page */
- ut_a(offsets_rec != NULL);
- }
+ COUNT_ALL_NON_BORING_AND_SKIP_DEL_MARKED, &n_diff);
#if 0
DEBUG_PRINTF(" %s(): n_diff below page_no=%lu: " UINT64PF "\n",
@@ -928,42 +1514,40 @@ dict_stats_analyze_index_below_cur(
/* @} */
/*********************************************************************//**
-For a given level in an index select srv_stats_persistent_sample_pages
+For a given level in an index select N_SAMPLE_PAGES(index)
(or less) records from that level and dive below them to the corresponding
leaf pages, then scan those leaf pages and save the sampling results in
-index->stat_n_diff_key_vals[n_prefix] and the number of pages scanned in
-index->stat_n_sample_sizes[n_prefix].
-dict_stats_analyze_index_for_n_prefix() @{ */
+index->stat_n_diff_key_vals[n_prefix - 1] and the number of pages scanned in
+index->stat_n_sample_sizes[n_prefix - 1]. */
static
void
dict_stats_analyze_index_for_n_prefix(
/*==================================*/
- dict_index_t* index, /*!< in/out: index */
- ulint level, /*!< in: level,
- must be >= 1 */
- ib_uint64_t total_recs_on_level, /*!< in: total number of
- records on the given level */
- ulint n_prefix, /*!< in: look at first
- n_prefix columns when
- comparing records */
- ib_uint64_t n_diff_for_this_prefix, /*!< in: number of distinct
- records on the given level,
- when looking at the first
- n_prefix columns */
- dyn_array_t* boundaries) /*!< in: array that contains
- n_diff_for_this_prefix
- integers each of which
- represents the index (on the
- level, counting from
- left/smallest to right/biggest
- from 0) of the last record
- from each group of distinct
- keys */
+ dict_index_t* index, /*!< in/out: index */
+ ulint level, /*!< in: level, must be >= 1 */
+ ib_uint64_t total_recs_on_level,
+ /*!< in: total number of
+ records on the given level */
+ ulint n_prefix, /*!< in: look at first
+ n_prefix columns when
+ comparing records */
+ ib_uint64_t n_diff_for_this_prefix,
+ /*!< in: number of distinct
+ records on the given level,
+ when looking at the first
+ n_prefix columns */
+ dyn_array_t* boundaries, /*!< in: array that contains
+ n_diff_for_this_prefix
+ integers each of which
+ represents the index (on the
+ level, counting from
+ left/smallest to right/biggest
+ from 0) of the last record
+ from each group of distinct
+ keys */
+ mtr_t* mtr) /*!< in/out: mini-transaction */
{
- mem_heap_t* heap;
- dtuple_t* dtuple;
btr_pcur_t pcur;
- mtr_t mtr;
const page_t* page;
ib_uint64_t rec_idx;
ib_uint64_t last_idx_on_level;
@@ -978,51 +1562,45 @@ dict_stats_analyze_index_for_n_prefix(
n_prefix, n_diff_for_this_prefix);
#endif
+ ut_ad(mtr_memo_contains(mtr, dict_index_get_lock(index),
+ MTR_MEMO_S_LOCK));
+
/* if some of those is 0 then this means that 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_on_level > 0);
ut_ad(n_diff_for_this_prefix > 0);
- /* this is configured to be min 1, someone has changed the code */
- ut_ad(srv_stats_persistent_sample_pages > 0);
+ /* this must be at least 1 */
+ ut_ad(N_SAMPLE_PAGES(index) > 0);
- heap = mem_heap_create(256);
+ /* Position pcur on the leftmost record on the leftmost page
+ on the desired level. */
- /* craft a record that is always smaller than the others,
- this way we are sure that the cursor pcur will be positioned
- on the leftmost record on the leftmost page on the desired level */
- dtuple = dtuple_create(heap, dict_index_get_n_unique(index));
- dict_table_copy_types(dtuple, index->table);
- dtuple_set_info_bits(dtuple, REC_INFO_MIN_REC_FLAG);
-
- mtr_start(&mtr);
-
- btr_pcur_open_low(index, level, dtuple, PAGE_CUR_LE, BTR_SEARCH_LEAF,
- &pcur, __FILE__, __LINE__, &mtr);
+ 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);
+ 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);
+ 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(page_rec_get_next_const(
- page_get_infimum_rec(page)),
- page_is_comp(page)));
-
- if (btr_pcur_is_before_first_on_page(&pcur)) {
- btr_pcur_move_to_next_on_page(&pcur);
- }
-
- if (btr_pcur_is_after_last_on_page(&pcur)) {
- btr_pcur_move_to_prev_on_page(&pcur);
- }
+ ut_a((level == 0)
+ == !(REC_INFO_MIN_REC_FLAG & rec_get_info_bits(
+ btr_pcur_get_rec(&pcur), page_is_comp(page))));
last_idx_on_level = *(ib_uint64_t*) dyn_array_get_element(boundaries,
(ulint) ((n_diff_for_this_prefix - 1) * sizeof(ib_uint64_t)));
@@ -1031,7 +1609,7 @@ dict_stats_analyze_index_for_n_prefix(
n_diff_sum_of_all_analyzed_pages = 0;
- n_recs_to_dive_below = ut_min(srv_stats_persistent_sample_pages,
+ n_recs_to_dive_below = ut_min(N_SAMPLE_PAGES(index),
n_diff_for_this_prefix);
for (i = 0; i < n_recs_to_dive_below; i++) {
@@ -1093,7 +1671,7 @@ dict_stats_analyze_index_for_n_prefix(
while (rec_idx < dive_below_idx
&& btr_pcur_is_on_user_rec(&pcur)) {
- btr_pcur_move_to_next_user_rec(&pcur, &mtr);
+ btr_pcur_move_to_next_user_rec(&pcur, mtr);
rec_idx++;
}
@@ -1107,12 +1685,20 @@ dict_stats_analyze_index_for_n_prefix(
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;
n_diff_on_leaf_page = dict_stats_analyze_index_below_cur(
- btr_pcur_get_btr_cur(&pcur), n_prefix, &mtr);
+ btr_pcur_get_btr_cur(&pcur), n_prefix, mtr);
/* We adjust n_diff_on_leaf_page here to avoid counting
one record twice - once as the last on some page and once
@@ -1135,12 +1721,13 @@ dict_stats_analyze_index_for_n_prefix(
n_diff_sum_of_all_analyzed_pages += n_diff_on_leaf_page;
}
- if (n_diff_sum_of_all_analyzed_pages == 0) {
- n_diff_sum_of_all_analyzed_pages = 1;
- }
+ /* n_diff_sum_of_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. */
/* See REF01 for an explanation of the algorithm */
- index->stat_n_diff_key_vals[n_prefix]
+ index->stat_n_diff_key_vals[n_prefix - 1]
= index->stat_n_leaf_pages
* n_diff_for_this_prefix
@@ -1149,31 +1736,25 @@ dict_stats_analyze_index_for_n_prefix(
* n_diff_sum_of_all_analyzed_pages
/ n_recs_to_dive_below;
- index->stat_n_sample_sizes[n_prefix] = n_recs_to_dive_below;
+ index->stat_n_sample_sizes[n_prefix - 1] = n_recs_to_dive_below;
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],
+ __func__, index->stat_n_diff_key_vals[n_prefix - 1],
n_prefix,
index->stat_n_leaf_pages,
n_diff_for_this_prefix, total_recs_on_level,
n_diff_sum_of_all_analyzed_pages, n_recs_to_dive_below);
btr_pcur_close(&pcur);
-
- mtr_commit(&mtr);
-
- mem_heap_free(heap);
}
-/* @} */
/*********************************************************************//**
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.
-dict_stats_analyze_index() @{ */
+stat_n_leaf_pages. This function could be slow. */
static
void
dict_stats_analyze_index(
@@ -1182,7 +1763,7 @@ dict_stats_analyze_index(
{
ulint root_level;
ulint level;
- ibool level_is_analyzed;
+ bool level_is_analyzed;
ulint n_uniq;
ulint n_prefix;
ib_uint64_t* n_diff_on_level;
@@ -1191,10 +1772,11 @@ dict_stats_analyze_index(
dyn_array_t* n_diff_boundaries;
mtr_t mtr;
ulint size;
- ulint i;
DEBUG_PRINTF(" %s(index=%s)\n", __func__, index->name);
+ dict_stats_empty_index(index);
+
mtr_start(&mtr);
mtr_s_lock(dict_index_get_lock(index), &mtr);
@@ -1206,19 +1788,12 @@ dict_stats_analyze_index(
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:
- mtr_commit(&mtr);
- /* Fake some statistics. */
- index->stat_index_size = index->stat_n_leaf_pages = 1;
-
- for (i = dict_index_get_n_unique(index); i; ) {
- index->stat_n_diff_key_vals[i--] = 1;
- }
-
- memset(index->stat_n_non_null_key_vals, 0,
- (1 + dict_index_get_n_unique(index))
- * sizeof(*index->stat_n_non_null_key_vals));
+ dict_stats_assert_initialized_index(index);
return;
case 0:
/* The root node of the tree is a leaf */
@@ -1227,23 +1802,25 @@ dict_stats_analyze_index(
index->stat_n_leaf_pages = size;
- root_level = btr_page_get_level(btr_root_get(index, &mtr), &mtr);
+ mtr_start(&mtr);
+
+ mtr_s_lock(dict_index_get_lock(index), &mtr);
- mtr_commit(&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 */
+ /* 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
- /* for each n-column prefix (for n=1..n_uniq)
- srv_stats_persistent_sample_pages will be sampled, so in total
- srv_stats_persistent_sample_pages * 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. */
- || srv_stats_persistent_sample_pages * n_uniq
- > index->stat_n_leaf_pages) {
+ || N_SAMPLE_PAGES(index) * n_uniq > index->stat_n_leaf_pages) {
if (root_level == 0) {
DEBUG_PRINTF(" %s(): just one page, "
@@ -1261,27 +1838,28 @@ dict_stats_analyze_index(
index->stat_n_diff_key_vals,
&total_recs,
&total_pages,
- NULL /*boundaries not needed*/);
+ NULL /* boundaries not needed */,
+ &mtr);
- for (i = 1; i <= n_uniq; i++) {
+ 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);
return;
}
- /* else */
/* set to zero */
- n_diff_on_level = (ib_uint64_t*) mem_zalloc((n_uniq + 1)
- * sizeof(ib_uint64_t));
+ n_diff_on_level = reinterpret_cast<ib_uint64_t*>
+ (mem_zalloc(n_uniq * sizeof(ib_uint64_t)));
- n_diff_boundaries = (dyn_array_t*) mem_alloc((n_uniq + 1)
- * sizeof(dyn_array_t));
+ n_diff_boundaries = reinterpret_cast<dyn_array_t*>
+ (mem_alloc(n_uniq * sizeof(dyn_array_t)));
- for (i = 1; i <= n_uniq; i++) {
- /* initialize the dynamic arrays, the first one
- (index=0) is ignored to follow the same indexing
- scheme as n_diff_on_level[] */
+ for (ulint i = 0; i < n_uniq; i++) {
+ /* initialize the dynamic arrays */
dyn_array_create(&n_diff_boundaries[i]);
}
@@ -1299,25 +1877,42 @@ dict_stats_analyze_index(
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 = (long) root_level;
- level_is_analyzed = FALSE;
+ 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, n_prefix);
+ __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] >= N_DIFF_REQUIRED
+ && (n_diff_on_level[n_prefix - 1] >= N_DIFF_REQUIRED(index)
|| level == 1)) {
goto found_level;
}
- /* else */
/* search for a level that contains enough distinct records */
@@ -1325,12 +1920,14 @@ dict_stats_analyze_index(
/* if this does not hold we should be on
"found_level" instead of here */
- ut_ad(n_diff_on_level[n_prefix] < N_DIFF_REQUIRED);
+ ut_ad(n_diff_on_level[n_prefix - 1]
+ < N_DIFF_REQUIRED(index));
level--;
- level_is_analyzed = FALSE;
+ level_is_analyzed = false;
}
+ /* descend into the tree, searching for "good enough" level */
for (;;) {
/* make sure we do not scan the leaf level
@@ -1349,18 +1946,19 @@ dict_stats_analyze_index(
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 > srv_stats_persistent_sample_pages) {
+ 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 and cannot
- be > srv_stats_persistent_sample_pages */
+ /* 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;
+ level_is_analyzed = true;
break;
}
@@ -1370,27 +1968,28 @@ dict_stats_analyze_index(
n_diff_on_level,
&total_recs,
&total_pages,
- n_diff_boundaries);
+ n_diff_boundaries,
+ &mtr);
- level_is_analyzed = TRUE;
+ level_is_analyzed = true;
- if (n_diff_on_level[n_prefix] >= N_DIFF_REQUIRED
+ if (n_diff_on_level[n_prefix - 1]
+ >= N_DIFF_REQUIRED(index)
|| level == 1) {
/* we found a good level with many distinct
records or we have reached the last level we
could scan */
break;
}
- /* else */
level--;
- level_is_analyzed = FALSE;
+ 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],
+ __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
@@ -1406,28 +2005,30 @@ found_level:
dict_stats_analyze_index_for_n_prefix(
index, level, total_recs, n_prefix,
- n_diff_on_level[n_prefix],
- &n_diff_boundaries[n_prefix]);
+ n_diff_on_level[n_prefix - 1],
+ &n_diff_boundaries[n_prefix - 1], &mtr);
}
- for (i = 1; i <= n_uniq; i++) {
+ mtr_commit(&mtr);
+
+ for (ulint i = 0; i < n_uniq; i++) {
dyn_array_free(&n_diff_boundaries[i]);
}
mem_free(n_diff_boundaries);
mem_free(n_diff_on_level);
+
+ dict_stats_assert_initialized_index(index);
}
-/* @} */
/*********************************************************************//**
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.
-dict_stats_update_persistent() @{
@return DB_SUCCESS or error code */
static
-enum db_err
+dberr_t
dict_stats_update_persistent(
/*=========================*/
dict_table_t* table) /*!< in/out: table */
@@ -1436,21 +2037,30 @@ dict_stats_update_persistent(
DEBUG_PRINTF("%s(table=%s)\n", __func__, table->name);
- /* XXX quit if interrupted, e.g. SIGTERM */
+ dict_table_stats_lock(table, RW_X_LATCH);
/* analyze the clustered index first */
index = dict_table_get_first_index(table);
- if (index == NULL) {
+ 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);
+
return(DB_CORRUPTION);
}
+ ut_ad(!dict_index_is_univ(index));
+
dict_stats_analyze_index(index);
- table->stat_n_rows
- = index->stat_n_diff_key_vals[dict_index_get_n_unique(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;
@@ -1462,31 +2072,47 @@ dict_stats_update_persistent(
index != NULL;
index = dict_table_get_next_index(index)) {
+ ut_ad(!dict_index_is_univ(index));
+
if (index->type & DICT_FTS) {
continue;
}
- dict_stats_analyze_index(index);
+ dict_stats_empty_index(index);
+
+ 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.
dict_stats_save_index_stat() @{
@return DB_SUCCESS or error code */
static
-enum db_err
+dberr_t
dict_stats_save_index_stat(
/*=======================*/
dict_index_t* index, /*!< in: index */
@@ -1494,95 +2120,114 @@ dict_stats_save_index_stat(
const char* stat_name, /*!< in: name of the stat */
ib_uint64_t stat_value, /*!< in: value of the stat */
ib_uint64_t* sample_size, /*!< in: n pages sampled or NULL */
- const char* stat_description,/*!< in: description of the stat */
- trx_t* trx, /*!< in/out: transaction to use */
- ibool caller_has_dict_sys_mutex)/*!< in: TRUE if the caller
- owns dict_sys->mutex */
+ const char* stat_description)/*!< in: description of the stat */
{
pars_info_t* pinfo;
- enum db_err ret;
+ dberr_t ret;
+ char db_utf8[MAX_DB_UTF8_LEN];
+ char table_utf8[MAX_TABLE_UTF8_LEN];
- pinfo = pars_info_create();
-
- pars_info_add_literal(pinfo, "database_name", index->table->name,
- dict_get_db_name_len(index->table->name),
- DATA_VARCHAR, 0);
+#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));
- pars_info_add_str_literal(pinfo, "table_name",
- dict_remove_db_name(index->table->name));
+ 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 = que_eval_sql(pinfo,
- "PROCEDURE INDEX_STATS_SAVE () IS\n"
- "dummy CHAR;\n"
- "BEGIN\n"
-
- "SELECT database_name INTO dummy\n"
- "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"
- "FOR UPDATE;\n"
-
- "IF (SQL % NOTFOUND) THEN\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"
- "ELSE\n"
- " UPDATE \"" INDEX_STATS_NAME "\" SET\n"
- " last_update = :last_update,\n"
- " stat_value = :stat_value,\n"
- " sample_size = :sample_size,\n"
- " stat_description = :stat_description\n"
- " WHERE\n"
- " database_name = :database_name AND\n"
- " table_name = :table_name AND\n"
- " index_name = :index_name AND\n"
- " stat_name = :stat_name;\n"
- "END IF;\n"
- "END;",
- !caller_has_dict_sys_mutex, trx);
-
- /* pinfo is freed by que_eval_sql() */
+ ret = dict_stats_exec_sql(
+ pinfo,
+ "PROCEDURE INDEX_STATS_SAVE_INSERT () IS\n"
+ "BEGIN\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;");
+
+ if (ret == DB_DUPLICATE_KEY) {
+
+ pinfo = pars_info_create();
+ pars_info_add_str_literal(pinfo, "database_name", db_utf8);
+ pars_info_add_str_literal(pinfo, "table_name", table_utf8);
+ UNIV_MEM_ASSERT_RW_ABORT(index->name, strlen(index->name));
+ pars_info_add_str_literal(pinfo, "index_name", index->name);
+ UNIV_MEM_ASSERT_RW_ABORT(&last_update, 4);
+ pars_info_add_int4_literal(pinfo, "last_update", last_update);
+ UNIV_MEM_ASSERT_RW_ABORT(stat_name, strlen(stat_name));
+ pars_info_add_str_literal(pinfo, "stat_name", stat_name);
+ UNIV_MEM_ASSERT_RW_ABORT(&stat_value, 8);
+ pars_info_add_ull_literal(pinfo, "stat_value", stat_value);
+ if (sample_size != NULL) {
+ UNIV_MEM_ASSERT_RW_ABORT(sample_size, 8);
+ pars_info_add_ull_literal(pinfo, "sample_size", *sample_size);
+ } else {
+ pars_info_add_literal(pinfo, "sample_size", NULL,
+ UNIV_SQL_NULL, DATA_FIXBINARY, 0);
+ }
+ UNIV_MEM_ASSERT_RW_ABORT(stat_description, strlen(stat_description));
+ pars_info_add_str_literal(pinfo, "stat_description",
+ stat_description);
+
+ ret = dict_stats_exec_sql(
+ pinfo,
+ "PROCEDURE INDEX_STATS_SAVE_UPDATE () IS\n"
+ "BEGIN\n"
+ "UPDATE \"" INDEX_STATS_NAME "\" SET\n"
+ "last_update = :last_update,\n"
+ "stat_value = :stat_value,\n"
+ "sample_size = :sample_size,\n"
+ "stat_description = :stat_description\n"
+ "WHERE\n"
+ "database_name = :database_name AND\n"
+ "table_name = :table_name AND\n"
+ "index_name = :index_name AND\n"
+ "stat_name = :stat_name;\n"
+ "END;");
+ }
if (ret != DB_SUCCESS) {
+ char buf_table[MAX_FULL_NAME_LEN];
+ char buf_index[MAX_FULL_NAME_LEN];
ut_print_timestamp(stderr);
fprintf(stderr,
- " InnoDB: Error while trying to save index "
- "statistics for table %s, index %s, "
- "stat name %s: %s\n",
- index->table->name, index->name,
+ " 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));
-
- trx->error_state = DB_SUCCESS;
}
return(ret);
@@ -1594,196 +2239,165 @@ Save the table's statistics into the persistent statistics storage.
dict_stats_save() @{
@return DB_SUCCESS or error code */
static
-enum db_err
+dberr_t
dict_stats_save(
/*============*/
- dict_table_t* table, /*!< in: table */
- ibool caller_has_dict_sys_mutex)/*!< in: TRUE if the caller
- owns dict_sys->mutex */
+ dict_table_t* table_orig) /*!< in: table */
{
- trx_t* trx;
pars_info_t* pinfo;
- dict_index_t* index;
lint now;
- enum db_err ret;
+ dberr_t ret;
+ dict_table_t* table;
+ char db_utf8[MAX_DB_UTF8_LEN];
+ char table_utf8[MAX_TABLE_UTF8_LEN];
+
+ 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();
- 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);
+#define PREPARE_PINFO_FOR_TABLE_SAVE(p, t, n) \
+ do { \
+ pars_info_add_str_literal((p), "database_name", db_utf8); \
+ pars_info_add_str_literal((p), "table_name", table_utf8); \
+ pars_info_add_int4_literal((p), "last_update", (n)); \
+ pars_info_add_ull_literal((p), "n_rows", (t)->stat_n_rows); \
+ pars_info_add_ull_literal((p), "clustered_index_size", \
+ (t)->stat_clustered_index_size); \
+ pars_info_add_ull_literal((p), "sum_of_other_index_sizes", \
+ (t)->stat_sum_of_other_index_sizes); \
+ } while(false);
pinfo = pars_info_create();
- pars_info_add_literal(pinfo, "database_name", table->name,
- dict_get_db_name_len(table->name),
- DATA_VARCHAR, 0);
-
- pars_info_add_str_literal(pinfo, "table_name",
- dict_remove_db_name(table->name));
-
- 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 = que_eval_sql(pinfo,
- "PROCEDURE TABLE_STATS_SAVE () IS\n"
- "dummy CHAR;\n"
- "BEGIN\n"
-
- "SELECT database_name INTO dummy\n"
- "FROM \"" TABLE_STATS_NAME "\"\n"
- "WHERE\n"
- "database_name = :database_name AND\n"
- "table_name = :table_name\n"
- "FOR UPDATE;\n"
-
- "IF (SQL % NOTFOUND) THEN\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"
- "ELSE\n"
- " UPDATE \"" TABLE_STATS_NAME "\" SET\n"
- " last_update = :last_update,\n"
- " n_rows = :n_rows,\n"
- " clustered_index_size = :clustered_index_size,\n"
- " sum_of_other_index_sizes = "
- " :sum_of_other_index_sizes\n"
- " WHERE\n"
- " database_name = :database_name AND\n"
- " table_name = :table_name;\n"
- "END IF;\n"
- "END;",
- !caller_has_dict_sys_mutex, trx);
-
- /* pinfo is freed by que_eval_sql() */
+ PREPARE_PINFO_FOR_TABLE_SAVE(pinfo, table, now);
+
+ ret = dict_stats_exec_sql(
+ pinfo,
+ "PROCEDURE TABLE_STATS_SAVE_INSERT () IS\n"
+ "BEGIN\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;");
+
+ if (ret == DB_DUPLICATE_KEY) {
+ pinfo = pars_info_create();
+
+ PREPARE_PINFO_FOR_TABLE_SAVE(pinfo, table, now);
+
+ ret = dict_stats_exec_sql(
+ pinfo,
+ "PROCEDURE TABLE_STATS_SAVE_UPDATE () IS\n"
+ "BEGIN\n"
+ "UPDATE \"" TABLE_STATS_NAME "\" SET\n"
+ "last_update = :last_update,\n"
+ "n_rows = :n_rows,\n"
+ "clustered_index_size = :clustered_index_size,\n"
+ "sum_of_other_index_sizes = "
+ " :sum_of_other_index_sizes\n"
+ "WHERE\n"
+ "database_name = :database_name AND\n"
+ "table_name = :table_name;\n"
+ "END;");
+ }
if (ret != DB_SUCCESS) {
-
+ char buf[MAX_FULL_NAME_LEN];
ut_print_timestamp(stderr);
fprintf(stderr,
- " InnoDB: Error while trying to save table "
- "statistics for table %s: %s\n",
- table->name, ut_strerr(ret));
-
- goto end_rollback;
+ " InnoDB: Cannot save table statistics for table "
+ "%s: %s\n",
+ ut_format_name(table->name, TRUE, buf, sizeof(buf)),
+ ut_strerr(ret));
+ goto end;
}
+ dict_index_t* index;
+
for (index = dict_table_get_first_index(table);
index != NULL;
index = dict_table_get_next_index(index)) {
- ib_uint64_t stat_n_diff_key_vals[REC_MAX_N_FIELDS];
- ib_uint64_t stat_n_sample_sizes[REC_MAX_N_FIELDS];
- ulint n_uniq;
- ulint i;
+ if (dict_stats_should_ignore_index(index)) {
+ continue;
+ }
+
+ ut_ad(!dict_index_is_univ(index));
ret = dict_stats_save_index_stat(index, now, "size",
index->stat_index_size,
NULL,
"Number of pages "
- "in the index",
- trx,
- caller_has_dict_sys_mutex);
+ "in the index");
if (ret != DB_SUCCESS) {
- goto end_rollback;
+ 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,
- caller_has_dict_sys_mutex);
+ "in the index");
if (ret != DB_SUCCESS) {
- goto end_rollback;
+ goto end;
}
- n_uniq = dict_index_get_n_unique(index);
-
- ut_ad(n_uniq + 1 <= UT_ARR_SIZE(stat_n_diff_key_vals));
-
- memcpy(stat_n_diff_key_vals, index->stat_n_diff_key_vals,
- (n_uniq + 1) * sizeof(index->stat_n_diff_key_vals[0]));
-
- ut_ad(n_uniq + 1 <= UT_ARR_SIZE(stat_n_sample_sizes));
-
- memcpy(stat_n_sample_sizes, index->stat_n_sample_sizes,
- (n_uniq + 1) * sizeof(index->stat_n_sample_sizes[0]));
-
- for (i = 1; i <= n_uniq; i++) {
+ 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);
+ "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 = 2; j <= i; j++) {
+ 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 - 1].name);
+ ",%s", index->fields[j].name);
}
ret = dict_stats_save_index_stat(
index, now, stat_name,
- stat_n_diff_key_vals[i],
- &stat_n_sample_sizes[i],
- stat_description, trx,
- caller_has_dict_sys_mutex);
+ index->stat_n_diff_key_vals[i],
+ &index->stat_n_sample_sizes[i],
+ stat_description);
if (ret != DB_SUCCESS) {
- goto end_rollback;
+ goto end;
}
}
}
- trx_commit_for_mysql(trx);
- ret = DB_SUCCESS;
- goto end_free;
-
-end_rollback:
-
- trx->op_info = "rollback of internal transaction on stats tables";
- trx_rollback_to_savepoint(trx, NULL);
- trx->op_info = "";
- ut_a(trx->error_state == DB_SUCCESS);
+end:
+ mutex_exit(&dict_sys->mutex);
+ rw_lock_x_unlock(&dict_operation_lock);
-end_free:
-
- trx_free_for_background(trx);
+ dict_stats_snapshot_free(table);
return(ret);
}
@@ -1875,11 +2489,11 @@ dict_stats_fetch_table_stats_step(
/** Aux struct used to pass a table and a boolean to
dict_stats_fetch_index_stats_step(). */
-typedef struct index_fetch_struct {
+struct index_fetch_t {
dict_table_t* table; /*!< table whose indexes are to be modified */
- ibool stats_were_modified; /*!< will be set to TRUE if at
+ bool stats_were_modified; /*!< will be set to true if at
least one index stats were modified */
-} index_fetch_t;
+};
/*********************************************************************//**
Called for the rows that are selected by
@@ -2036,12 +2650,12 @@ dict_stats_fetch_index_stats_step(
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;
+ 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;
+ 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) {
@@ -2057,19 +2671,24 @@ dict_stats_fetch_index_stats_step(
|| 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 "
+ "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,
- (int) dict_get_db_name_len(table->name),
- table->name,
- dict_remove_db_name(table->name),
+ db_utf8,
+ table_utf8,
index->name,
(int) stat_name_len,
stat_name);
@@ -2081,41 +2700,50 @@ dict_stats_fetch_index_stats_step(
note that stat_name does not have a terminating '\0' */
n_pfx = (num_ptr[0] - '0') * 10 + (num_ptr[1] - '0');
- if (n_pfx == 0 || n_pfx > dict_index_get_n_unique(index)) {
+ 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 "
+ "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,
- (int) dict_get_db_name_len(table->name),
- table->name,
- dict_remove_db_name(table->name),
+ db_utf8,
+ table_utf8,
index->name,
(int) stat_name_len,
stat_name,
- dict_index_get_n_unique(index));
+ n_uniq);
return(TRUE);
}
/* else */
- index->stat_n_diff_key_vals[n_pfx] = stat_value;
+ index->stat_n_diff_key_vals[n_pfx - 1] = stat_value;
if (sample_size != UINT64_UNDEFINED) {
- index->stat_n_sample_sizes[n_pfx] = sample_size;
+ 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] = 0;
+ index->stat_n_sample_sizes[n_pfx - 1] = 0;
}
- arg->stats_were_modified = TRUE;
+ 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 */
@@ -2131,19 +2759,25 @@ Read table's statistics from the persistent statistics storage.
dict_stats_fetch_from_ps() @{
@return DB_SUCCESS or error code */
static
-enum db_err
+dberr_t
dict_stats_fetch_from_ps(
/*=====================*/
- dict_table_t* table, /*!< in/out: table */
- ibool caller_has_dict_sys_mutex)/*!< in: TRUE if the caller
- owns dict_sys->mutex */
+ dict_table_t* table) /*!< in/out: table */
{
index_fetch_t index_fetch_arg;
trx_t* trx;
pars_info_t* pinfo;
- enum db_err ret;
+ dberr_t ret;
+ char db_utf8[MAX_DB_UTF8_LEN];
+ char table_utf8[MAX_TABLE_UTF8_LEN];
+
+ ut_ad(!mutex_own(&dict_sys->mutex));
- ut_ad(mutex_own(&dict_sys->mutex) == caller_has_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);
trx = trx_allocate_for_background();
@@ -2155,14 +2789,14 @@ dict_stats_fetch_from_ps(
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_literal(pinfo, "database_name", table->name,
- dict_get_db_name_len(table->name),
- DATA_VARCHAR, 0);
+ pars_info_add_str_literal(pinfo, "database_name", db_utf8);
- pars_info_add_str_literal(pinfo, "table_name",
- dict_remove_db_name(table->name));
+ pars_info_add_str_literal(pinfo, "table_name", table_utf8);
pars_info_bind_function(pinfo,
"fetch_table_stats_step",
@@ -2170,7 +2804,7 @@ dict_stats_fetch_from_ps(
table);
index_fetch_arg.table = table;
- index_fetch_arg.stats_were_modified = FALSE;
+ index_fetch_arg.stats_were_modified = false;
pars_info_bind_function(pinfo,
"fetch_index_stats_step",
dict_stats_fetch_index_stats_step,
@@ -2230,19 +2864,9 @@ dict_stats_fetch_from_ps(
"CLOSE index_stats_cur;\n"
"END;",
- !caller_has_dict_sys_mutex, trx);
-
+ TRUE, trx);
/* pinfo is freed by que_eval_sql() */
- /* XXX If mysql.innodb_index_stats contained less rows than the number
- of indexes in the table, then some of the indexes of the table
- were left uninitialized. Currently this is ignored and those
- indexes are left with uninitialized stats until ANALYZE TABLE is
- run. This condition happens when the user creates a new index
- on a table. We could return DB_STATS_DO_NOT_EXIST from here,
- forcing the usage of transient stats until mysql.innodb_index_stats
- is complete. */
-
trx_commit_for_mysql(trx);
trx_free_for_background(trx);
@@ -2256,32 +2880,67 @@ dict_stats_fetch_from_ps(
/* @} */
/*********************************************************************//**
+Fetches or calculates new estimates for index statistics.
+dict_stats_update_for_index() @{ */
+UNIV_INTERN
+void
+dict_stats_update_for_index(
+/*========================*/
+ dict_index_t* index) /*!< in/out: 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);
+ return;
+ }
+ /* else */
+
+ /* 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)));
+ }
+
+ 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);
+}
+/* @} */
+
+/*********************************************************************//**
Calculates new estimates for table and index statistics. The statistics
are used in query optimization.
-dict_stats_update() @{
-@return DB_* error code or DB_SUCCESS */
+@return DB_SUCCESS or error code */
UNIV_INTERN
-enum db_err
+dberr_t
dict_stats_update(
/*==============*/
dict_table_t* table, /*!< in/out: table */
- dict_stats_upd_option_t stats_upd_option,
+ dict_stats_upd_option_t stats_upd_option)
/*!< in: whether to (re) calc
the stats or to fetch them from
the persistent statistics
storage */
- ibool caller_has_dict_sys_mutex)
- /*!< in: TRUE if the caller
- owns dict_sys->mutex */
{
- enum db_err ret = DB_ERROR;
+ char buf[MAX_FULL_NAME_LEN];
- /* check whether caller_has_dict_sys_mutex is set correctly;
- note that mutex_own() is not implemented in non-debug code so
- we cannot avoid having this extra param to the current function */
- ut_ad(caller_has_dict_sys_mutex
- ? mutex_own(&dict_sys->mutex)
- : !mutex_own(&dict_sys->mutex));
+ ut_ad(!mutex_own(&dict_sys->mutex));
if (table->ibd_file_missing) {
ut_print_timestamp(stderr);
@@ -2289,83 +2948,61 @@ dict_stats_update(
" InnoDB: cannot calculate statistics for table %s "
"because the .ibd file is missing. For help, please "
"refer to " REFMAN "innodb-troubleshooting.html\n",
- table->name);
-
+ ut_format_name(table->name, TRUE, buf, sizeof(buf)));
+ dict_stats_empty_table(table);
return(DB_TABLESPACE_DELETED);
- }
-
- /* 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. */
-
- if (srv_force_recovery >= SRV_FORCE_NO_IBUF_MERGE) {
-
+ } 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);
return(DB_SUCCESS);
}
switch (stats_upd_option) {
case DICT_STATS_RECALC_PERSISTENT:
- case DICT_STATS_RECALC_PERSISTENT_SILENT:
+
+ ut_ad(!srv_read_only_mode);
+
/* Persistent recalculation requested, called from
- ANALYZE TABLE or from TRUNCATE TABLE */
-
- /* FTS auxiliary tables do not need persistent stats */
- if ((ut_strcount(table->name, "FTS") > 0
- && (ut_strcount(table->name, "CONFIG") > 0
- || ut_strcount(table->name, "INDEX") > 0
- || ut_strcount(table->name, "DELETED") > 0
- || ut_strcount(table->name, "DOC_ID") > 0
- || ut_strcount(table->name, "ADDED") > 0))) {
- goto transient;
- }
+ 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(
- caller_has_dict_sys_mutex)) {
-
- dict_table_stats_lock(table, RW_X_LATCH);
+ if (dict_stats_persistent_storage_check(false)) {
- ret = dict_stats_update_persistent(table);
+ dberr_t err;
- /* XXX Currently dict_stats_save() would read the
- stats from the table without dict_table_stats_lock()
- which means it could save inconsistent data on the
- disk. This is because we must call
- dict_table_stats_lock() after locking dict_sys->mutex.
- A solution is to copy here the stats to a temporary
- buffer while holding the _stats_lock(), release it,
- and pass that buffer to dict_stats_save(). */
+ err = dict_stats_update_persistent(table);
- dict_table_stats_unlock(table, RW_X_LATCH);
-
- if (ret == DB_SUCCESS) {
- ret = dict_stats_save(
- table,
- caller_has_dict_sys_mutex);
+ if (err != DB_SUCCESS) {
+ return(err);
}
- return(ret);
+ err = dict_stats_save(table);
+
+ return(err);
}
- /* else */
/* Fall back to transient stats since the persistent
storage is not present or is corrupted */
- if (stats_upd_option == DICT_STATS_RECALC_PERSISTENT) {
-
- ut_print_timestamp(stderr);
- /* XXX add link to the doc about storage
- creation */
- fprintf(stderr,
- " InnoDB: Recalculation of persistent "
- "statistics requested but the required "
- "persistent statistics storage is not "
- "present or is corrupted. "
- "Using quick transient stats "
- "instead.\n");
- }
+ 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)));
goto transient;
@@ -2373,265 +3010,317 @@ dict_stats_update(
goto transient;
- case DICT_STATS_FETCH:
- case DICT_STATS_FETCH_ONLY_IF_NOT_IN_MEMORY:
- /* fetch requested, either fetch from persistent statistics
- storage or use the old method */
+ case DICT_STATS_EMPTY_TABLE:
- dict_table_stats_lock(table, RW_X_LATCH);
+ dict_stats_empty_table(table);
- if (stats_upd_option == DICT_STATS_FETCH_ONLY_IF_NOT_IN_MEMORY
- && table->stat_initialized) {
+ /* If table is using persistent stats,
+ then save the stats on disk */
- dict_table_stats_unlock(table, RW_X_LATCH);
- return(DB_SUCCESS);
+ if (dict_stats_is_persistent_enabled(table)) {
+
+ if (dict_stats_persistent_storage_check(false)) {
+
+ return(dict_stats_save(table));
+ }
+
+ return(DB_STATS_DO_NOT_EXIST);
}
- /* else */
- /* Must unlock because otherwise there is a lock order
- violation with dict_sys->mutex below. Declare stats to be
- initialized before unlocking. */
- table->stat_initialized = TRUE;
- dict_table_stats_unlock(table, RW_X_LATCH);
+ return(DB_SUCCESS);
+
+ case DICT_STATS_FETCH_ONLY_IF_NOT_IN_MEMORY:
- if (strchr(table->name, '/') == NULL
- || strcmp(table->name, INDEX_STATS_NAME) == 0
- || strcmp(table->name, TABLE_STATS_NAME) == 0
- || (ut_strcount(table->name, "FTS") > 0
- && (ut_strcount(table->name, "CONFIG") > 0
- || ut_strcount(table->name, "INDEX") > 0
- || ut_strcount(table->name, "DELETED") > 0
- || ut_strcount(table->name, "DOC_ID") > 0
- || ut_strcount(table->name, "ADDED") > 0))) {
- /* Use the quick transient stats method for
- InnoDB internal tables, because we know the
- persistent stats storage does not contain data
- for them */
+ /* fetch requested, either fetch from persistent statistics
+ storage or use the old method */
- goto transient;
+ if (table->stat_initialized) {
+ return(DB_SUCCESS);
}
- /* else */
- if (dict_stats_persistent_storage_check(
- caller_has_dict_sys_mutex)) {
+ /* InnoDB internal tables (e.g. SYS_TABLES) cannot have
+ persistent stats enabled */
+ ut_a(strchr(table->name, '/') != NULL);
- ret = dict_stats_fetch_from_ps(table,
- caller_has_dict_sys_mutex);
+ if (!dict_stats_persistent_storage_check(false)) {
+ /* persistent statistics storage does not exist
+ or is corrupted, calculate the transient stats */
- if (ret == DB_STATS_DO_NOT_EXIST
- || (ret != DB_SUCCESS && stats_upd_option
- == DICT_STATS_FETCH_ONLY_IF_NOT_IN_MEMORY)) {
- /* Stats for this particular table do not
- exist or we have been called from open table
- which needs to initialize the stats,
- calculate the quick transient statistics */
- goto transient;
- }
- /* else */
+ 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);
- return(ret);
- } else {
- /* persistent statistics storage does not exist,
- calculate the transient stats */
goto transient;
}
- break;
+ dict_table_t* t;
- /* no "default:" in order to produce a compilation warning
- about unhandled enumeration value */
- }
+ ut_ad(!srv_read_only_mode);
-transient:
+ /* 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);
- dict_table_stats_lock(table, RW_X_LATCH);
+ dberr_t err = dict_stats_fetch_from_ps(t);
- dict_stats_update_transient(table);
+ t->stats_last_recalc = table->stats_last_recalc;
+ t->stat_modified_counter = 0;
- dict_table_stats_unlock(table, RW_X_LATCH);
+ switch (err) {
+ case DB_SUCCESS:
- return(DB_SUCCESS);
-}
-/* @} */
+ dict_table_stats_lock(table, RW_X_LATCH);
-/*********************************************************************//**
-Close the stats tables. Should always be called after successful
-dict_stats_open(). It will free the dict_stats handle.
-dict_stats_close() @{ */
-UNIV_INLINE
-void
-dict_stats_close(
-/*=============*/
- dict_stats_t* dict_stats) /*!< in/own: Handle to open
- statistics tables */
-{
- if (dict_stats->table_stats != NULL) {
- dict_table_close(dict_stats->table_stats, FALSE);
- dict_stats->table_stats = NULL;
- }
+ /* Initialize all stats to dummy values before
+ copying because dict_stats_table_clone_create() does
+ skip corrupted indexes so our dummy object 't' may
+ have less indexes than the real object 'table'. */
+ dict_stats_empty_table(table);
- if (dict_stats->index_stats != NULL) {
- dict_table_close(dict_stats->index_stats, FALSE);
- dict_stats->index_stats = NULL;
- }
+ dict_stats_copy(table, t);
- mem_free(dict_stats);
-}
-/* @} */
+ dict_stats_assert_initialized(table);
-/*********************************************************************//**
-Open stats tables to prevent these tables from being DROPped.
-Also check whether they have the correct structure. The caller
-must call dict_stats_close() when he has finished DMLing the tables.
-dict_stats_open() @{
-@return pointer to open tables or NULL on failure */
-UNIV_INLINE
-dict_stats_t*
-dict_stats_open(void)
-/*=================*/
-{
- dict_stats_t* dict_stats;
+ 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);
- dict_stats = static_cast<dict_stats_t*>(
- mem_zalloc(sizeof(*dict_stats)));
+ if (dict_stats_auto_recalc_is_enabled(table)) {
+ return(dict_stats_update(
+ table,
+ DICT_STATS_RECALC_PERSISTENT));
+ }
- dict_stats->table_stats = dict_table_open_on_name_no_stats(
- TABLE_STATS_NAME, FALSE, DICT_ERR_IGNORE_NONE);
+ 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);
- dict_stats->index_stats = dict_table_open_on_name_no_stats(
- INDEX_STATS_NAME, FALSE, DICT_ERR_IGNORE_NONE);
+ goto transient;
+ default:
- /* Check if the tables have the correct structure, if yes then
- after this function we can safely DELETE from them without worrying
- that they may get DROPped or DDLed because the open will have
- increased the reference count. */
+ dict_stats_table_clone_free(t);
- if (dict_stats->table_stats == NULL
- || dict_stats->index_stats == NULL
- || !dict_stats_persistent_storage_check(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));
- /* There was an error, close the tables and free the handle. */
- dict_stats_close(dict_stats);
- dict_stats = NULL;
+ goto transient;
+ }
+ /* no "default:" in order to produce a compilation warning
+ about unhandled enumeration value */
}
- return(dict_stats);
+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.
-The transaction is not committed, it must not be committed in this
-function because this is the user trx that is running DROP INDEX.
-The transaction will be committed at the very end when dropping an
-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.
-dict_stats_delete_index_stats() @{
+dict_stats_drop_index() @{
@return DB_SUCCESS or error code */
UNIV_INTERN
-enum db_err
-dict_stats_delete_index_stats(
-/*==========================*/
- dict_index_t* index, /*!< in: index */
- trx_t* trx, /*!< in: transaction to use */
+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 database_name[MAX_DATABASE_NAME_LEN + 1];
- const char* table_name;
+ char db_utf8[MAX_DB_UTF8_LEN];
+ char table_utf8[MAX_TABLE_UTF8_LEN];
pars_info_t* pinfo;
- enum db_err ret;
- dict_stats_t* dict_stats;
- void* mysql_thd = trx->mysql_thd;
+ 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(index->table_name, '/') == NULL) {
-
- return(DB_SUCCESS);
- }
+ if (strchr(db_and_table, '/') == NULL) {
- /* Increment table reference count to prevent the tables from
- being DROPped just before que_eval_sql(). */
- dict_stats = dict_stats_open();
-
- if (dict_stats == NULL) {
- /* stats tables do not exist or have unexpected structure */
return(DB_SUCCESS);
}
- /* the stats tables cannot be DROPped now */
-
- ut_snprintf(database_name, sizeof(database_name), "%.*s",
- (int) dict_get_db_name_len(index->table_name),
- index->table_name);
-
- table_name = dict_remove_db_name(index->table_name);
+ 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", database_name);
+ pars_info_add_str_literal(pinfo, "database_name", db_utf8);
- pars_info_add_str_literal(pinfo, "table_name", table_name);
+ pars_info_add_str_literal(pinfo, "table_name", table_utf8);
- pars_info_add_str_literal(pinfo, "index_name", index->name);
+ pars_info_add_str_literal(pinfo, "index_name", iname);
- /* Force lock wait timeout to be instantaneous because the incoming
- transaction was created via MySQL. */
+ rw_lock_x_lock(&dict_operation_lock);
+ mutex_enter(&dict_sys->mutex);
- mysql_thd = trx->mysql_thd;
- trx->mysql_thd = NULL;
+ 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");
- ret = que_eval_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",
- TRUE,
- trx);
-
- trx->mysql_thd = mysql_thd;
-
- /* pinfo is freed by que_eval_sql() */
+ mutex_exit(&dict_sys->mutex);
+ rw_lock_x_unlock(&dict_operation_lock);
- /* do not to commit here, see the function's comment */
+ 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. They can be deleted later using "
+ "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';",
- index->name,
+ iname,
INDEX_STATS_NAME_PRINT,
(ret == DB_LOCK_WAIT_TIMEOUT
? " because the rows are locked"
: ""),
+ ut_strerr(ret),
INDEX_STATS_NAME_PRINT,
- database_name,
- table_name,
- index->name);
+ db_utf8,
+ table_utf8,
+ iname);
ut_print_timestamp(stderr);
fprintf(stderr, " InnoDB: %s\n", errstr);
-
- trx->error_state = DB_SUCCESS;
}
- dict_stats_close(dict_stats);
+ return(ret);
+}
+/* @} */
+
+/*********************************************************************//**
+Executes
+DELETE FROM mysql.innodb_table_stats
+WHERE database_name = '...' AND table_name = '...';
+Creates its own transaction and commits it.
+dict_stats_delete_from_table_stats() @{
+@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_STAT */
+ 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");
+
+ return(ret);
+}
+/* @} */
+
+/*********************************************************************//**
+Executes
+DELETE FROM mysql.innodb_index_stats
+WHERE database_name = '...' AND table_name = '...';
+Creates its own transaction and commits it.
+dict_stats_delete_from_index_stats() @{
+@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_STAT */
+ 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");
return(ret);
}
@@ -2640,130 +3329,332 @@ dict_stats_delete_index_stats(
/*********************************************************************//**
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.
-dict_stats_delete_table_stats() @{
+the table. This function creates its own transaction and commits it.
+dict_stats_drop_table() @{
@return DB_SUCCESS or error code */
UNIV_INTERN
-enum db_err
-dict_stats_delete_table_stats(
-/*==========================*/
- const char* table_name, /*!< in: table name */
+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 database_name[MAX_DATABASE_NAME_LEN + 1];
- const char* table_name_strip; /* without leading db name */
- trx_t* trx;
- pars_info_t* pinfo;
- enum db_err ret = DB_ERROR;
- dict_stats_t* dict_stats;
+ 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_STAT */
+ 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(table_name, '/') == NULL) {
+ if (strchr(db_and_table, '/') == NULL) {
return(DB_SUCCESS);
}
/* skip innodb_table_stats and innodb_index_stats themselves */
- if (strcmp(table_name, TABLE_STATS_NAME) == 0
- || strcmp(table_name, INDEX_STATS_NAME) == 0) {
+ if (strcmp(db_and_table, TABLE_STATS_NAME) == 0
+ || strcmp(db_and_table, INDEX_STATS_NAME) == 0) {
return(DB_SUCCESS);
}
- /* Create a new private trx */
+ dict_fs2utf8(db_and_table, db_utf8, sizeof(db_utf8),
+ table_utf8, sizeof(table_utf8));
- trx = trx_allocate_for_background();
+ ret = dict_stats_delete_from_table_stats(db_utf8, table_utf8);
- /* Use 'read-uncommitted' so that the SELECTs we execute
- do not get blocked in case some user has locked the rows we
- are SELECTing */
+ if (ret == DB_SUCCESS) {
+ ret = dict_stats_delete_from_index_stats(db_utf8, table_utf8);
+ }
- trx->isolation_level = TRX_ISO_READ_UNCOMMITTED;
+ if (ret == DB_STATS_DO_NOT_EXIST) {
+ ret = DB_SUCCESS;
+ }
- trx_start_if_not_started(trx);
+ if (ret != DB_SUCCESS) {
- /* Increment table reference count to prevent the tables from
- being DROPped just before que_eval_sql(). */
- dict_stats = dict_stats_open();
+ ut_snprintf(errstr, errstr_sz,
+ "Unable to delete statistics for table %s.%s: %s. "
+ "They can be deleted later using "
- if (dict_stats == NULL) {
- /* stats tables do not exist or have unexpected structure */
- ret = DB_SUCCESS;
- goto commit_and_return;
+ "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);
}
- ut_snprintf(database_name, sizeof(database_name), "%.*s",
- (int) dict_get_db_name_len(table_name),
- table_name);
+ 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.
+dict_stats_rename_in_table_stats() @{
+@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;
- table_name_strip = dict_remove_db_name(table_name);
+#ifdef UNIV_SYNC_DEBUG
+ ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
+#endif /* UNIV_STAT */
+ 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, "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");
- pars_info_add_str_literal(pinfo, "table_name", table_name_strip);
+ return(ret);
+}
+/* @} */
- ret = que_eval_sql(pinfo,
- "PROCEDURE DROP_TABLE_STATS () IS\n"
- "BEGIN\n"
+/*********************************************************************//**
+Executes
+UPDATE mysql.innodb_index_stats SET
+database_name = '...', table_name = '...'
+WHERE database_name = '...' AND table_name = '...';
+Creates its own transaction and commits it.
+dict_stats_rename_in_index_stats() @{
+@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;
- "DELETE FROM \"" INDEX_STATS_NAME "\" WHERE\n"
- "database_name = :database_name AND\n"
- "table_name = :table_name;\n"
+#ifdef UNIV_SYNC_DEBUG
+ ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
+#endif /* UNIV_STAT */
+ ut_ad(mutex_own(&dict_sys->mutex));
- "DELETE FROM \"" TABLE_STATS_NAME "\" WHERE\n"
- "database_name = :database_name AND\n"
- "table_name = :table_name;\n"
+ pinfo = pars_info_create();
- "END;\n",
- TRUE,
- trx);
+ 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");
- /* pinfo is freed by que_eval_sql() */
+ return(ret);
+}
+/* @} */
- if (ret != DB_SUCCESS) {
+/*********************************************************************//**
+Renames a table in InnoDB persistent stats storage.
+This function creates its own transaction and commits it.
+dict_stats_rename_table() @{
+@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;
- ut_snprintf(errstr, errstr_sz,
- "Unable to delete statistics for table %s.%s "
- "from %s or %s%s. "
- "They can be deleted later using "
+#ifdef UNIV_SYNC_DEBUG
+ ut_ad(!rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
+#endif /* UNIV_STAT */
+ ut_ad(!mutex_own(&dict_sys->mutex));
- "DELETE FROM %s WHERE "
- "database_name = '%s' AND "
- "table_name = '%s'; "
+ /* 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) {
- "DELETE FROM %s WHERE "
- "database_name = '%s' AND "
- "table_name = '%s';",
+ return(DB_SUCCESS);
+ }
- database_name, table_name_strip,
- TABLE_STATS_NAME_PRINT, INDEX_STATS_NAME_PRINT,
+ dict_fs2utf8(old_name, old_db_utf8, sizeof(old_db_utf8),
+ old_table_utf8, sizeof(old_table_utf8));
- (ret == DB_LOCK_WAIT_TIMEOUT
- ? " because the rows are locked"
- : ""),
+ dict_fs2utf8(new_name, new_db_utf8, sizeof(new_db_utf8),
+ new_table_utf8, sizeof(new_table_utf8));
- INDEX_STATS_NAME_PRINT,
- database_name, table_name_strip,
+ 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,
- database_name, table_name_strip);
+ ut_strerr(ret),
- ut_print_timestamp(stderr);
- fprintf(stderr, " InnoDB: %s\n", errstr);
+ 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 */
- dict_stats_close(dict_stats);
+ n_attempts = 0;
+ do {
+ n_attempts++;
-commit_and_return:
+ ret = dict_stats_rename_in_index_stats(
+ old_db_utf8, old_table_utf8,
+ new_db_utf8, new_table_utf8);
- trx_commit_for_mysql(trx);
+ if (ret == DB_DUPLICATE_KEY) {
+ dict_stats_delete_from_index_stats(
+ new_db_utf8, new_table_utf8);
+ }
- trx_free_for_background(trx);
+ 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);
}
@@ -2933,13 +3824,13 @@ 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[2];
- ib_uint64_t index1_stat_n_sample_sizes[2];
+ 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[5];
- ib_uint64_t index2_stat_n_sample_sizes[5];
- enum db_err ret;
+ 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);
@@ -2949,16 +3840,11 @@ test_dict_stats_save()
UT_LIST_INIT(table.indexes);
UT_LIST_ADD_LAST(indexes, table.indexes, &index1);
UT_LIST_ADD_LAST(indexes, table.indexes, &index2);
-#ifdef UNIV_DEBUG
- table.magic_n = DICT_TABLE_MAGIC_N;
-#endif /* UNIV_DEBUG */
+ 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;
-#ifdef UNIV_DEBUG
- index1.magic_n = DICT_INDEX_MAGIC_N;
-#endif /* UNIV_DEBUG */
- index1.to_be_dropped = 0;
index1.cached = 1;
index1.n_uniq = 1;
index1.fields = index1_fields;
@@ -2967,17 +3853,12 @@ test_dict_stats_save()
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] = 1; /* dummy */
- index1_stat_n_diff_key_vals[1] = TEST_IDX1_N_DIFF1;
- index1_stat_n_sample_sizes[0] = 0; /* dummy */
- index1_stat_n_sample_sizes[1] = TEST_IDX1_N_DIFF1_SAMPLE_SIZE;
+ 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;
-#ifdef UNIV_DEBUG
- index2.magic_n = DICT_INDEX_MAGIC_N;
-#endif /* UNIV_DEBUG */
- index2.to_be_dropped = 0;
index2.cached = 1;
index2.n_uniq = 4;
index2.fields = index2_fields;
@@ -2989,18 +3870,16 @@ test_dict_stats_save()
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] = 1; /* dummy */
- index2_stat_n_diff_key_vals[1] = TEST_IDX2_N_DIFF1;
- index2_stat_n_diff_key_vals[2] = TEST_IDX2_N_DIFF2;
- index2_stat_n_diff_key_vals[3] = TEST_IDX2_N_DIFF3;
- index2_stat_n_diff_key_vals[4] = TEST_IDX2_N_DIFF4;
- index2_stat_n_sample_sizes[0] = 0; /* dummy */
- index2_stat_n_sample_sizes[1] = TEST_IDX2_N_DIFF1_SAMPLE_SIZE;
- index2_stat_n_sample_sizes[2] = TEST_IDX2_N_DIFF2_SAMPLE_SIZE;
- index2_stat_n_sample_sizes[3] = TEST_IDX2_N_DIFF3_SAMPLE_SIZE;
- index2_stat_n_sample_sizes[4] = TEST_IDX2_N_DIFF4_SAMPLE_SIZE;
-
- ret = dict_stats_save(&table, FALSE);
+ 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);
ut_a(ret == DB_SUCCESS);
@@ -3098,41 +3977,35 @@ test_dict_stats_fetch_from_ps()
{
dict_table_t table;
dict_index_t index1;
- ib_uint64_t index1_stat_n_diff_key_vals[2];
- ib_uint64_t index1_stat_n_sample_sizes[2];
+ 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[5];
- ib_uint64_t index2_stat_n_sample_sizes[5];
- enum db_err ret;
+ 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);
-#ifdef UNIV_DEBUG
- table.magic_n = DICT_TABLE_MAGIC_N;
-#endif /* UNIV_DEBUG */
+ ut_d(table.magic_n = DICT_TABLE_MAGIC_N);
index1.name = TEST_IDX1_NAME;
-#ifdef UNIV_DEBUG
- index1.magic_n = DICT_INDEX_MAGIC_N;
-#endif /* UNIV_DEBUG */
+ 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;
-#ifdef UNIV_DEBUG
- index2.magic_n = DICT_INDEX_MAGIC_N;
-#endif /* UNIV_DEBUG */
+ 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, FALSE);
+ ret = dict_stats_fetch_from_ps(&table);
ut_a(ret == DB_SUCCESS);
@@ -3143,19 +4016,19 @@ test_dict_stats_fetch_from_ps()
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[1] == TEST_IDX1_N_DIFF1);
- ut_a(index1_stat_n_sample_sizes[1] == TEST_IDX1_N_DIFF1_SAMPLE_SIZE);
+ 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[1] == TEST_IDX2_N_DIFF1);
- ut_a(index2_stat_n_sample_sizes[1] == TEST_IDX2_N_DIFF1_SAMPLE_SIZE);
- ut_a(index2_stat_n_diff_key_vals[2] == TEST_IDX2_N_DIFF2);
- ut_a(index2_stat_n_sample_sizes[2] == TEST_IDX2_N_DIFF2_SAMPLE_SIZE);
- ut_a(index2_stat_n_diff_key_vals[3] == TEST_IDX2_N_DIFF3);
- ut_a(index2_stat_n_sample_sizes[3] == TEST_IDX2_N_DIFF3_SAMPLE_SIZE);
- ut_a(index2_stat_n_diff_key_vals[4] == TEST_IDX2_N_DIFF4);
- ut_a(index2_stat_n_sample_sizes[4] == TEST_IDX2_N_DIFF4_SAMPLE_SIZE);
+ 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");
}
diff --git a/storage/innobase/dict/dict0stats_bg.cc b/storage/innobase/dict/dict0stats_bg.cc
new file mode 100644
index 00000000000..7a30b748e7f
--- /dev/null
+++ b/storage/innobase/dict/dict0stats_bg.cc
@@ -0,0 +1,392 @@
+/*****************************************************************************
+
+Copyright (c) 2012, Oracle and/or its affiliates. All Rights Reserved.
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+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_bg.cc
+Code used for background table and index stats gathering.
+
+Created Apr 25, 2012 Vasil Dimov
+*******************************************************/
+
+#include "row0mysql.h"
+#include "srv0start.h"
+#include "dict0stats.h"
+#include "dict0stats_bg.h"
+
+#include <vector>
+
+/** Minimum time interval between stats recalc for a given table */
+#define MIN_RECALC_INTERVAL 10 /* seconds */
+
+#define SHUTTING_DOWN() (srv_shutdown_state != SRV_SHUTDOWN_NONE)
+
+/** Event to wake up the stats thread */
+UNIV_INTERN os_event_t dict_stats_event = NULL;
+
+/** This mutex protects the "recalc_pool" variable. */
+static ib_mutex_t recalc_pool_mutex;
+#ifdef HAVE_PSI_INTERFACE
+static mysql_pfs_key_t recalc_pool_mutex_key;
+#endif /* HAVE_PSI_INTERFACE */
+
+/** The number of tables that can be added to "recalc_pool" before
+it is enlarged */
+static const ulint RECALC_POOL_INITIAL_SLOTS = 128;
+
+/** The multitude of tables whose stats are to be automatically
+recalculated - an STL vector */
+typedef std::vector<table_id_t> recalc_pool_t;
+static recalc_pool_t recalc_pool;
+
+typedef recalc_pool_t::iterator recalc_pool_iterator_t;
+
+/*****************************************************************//**
+Initialize the recalc pool, called once during thread initialization. */
+static
+void
+dict_stats_recalc_pool_init()
+/*=========================*/
+{
+ ut_ad(!srv_read_only_mode);
+
+ recalc_pool.reserve(RECALC_POOL_INITIAL_SLOTS);
+}
+
+/*****************************************************************//**
+Free the resources occupied by the recalc pool, called once during
+thread de-initialization. */
+static
+void
+dict_stats_recalc_pool_deinit()
+/*===========================*/
+{
+ ut_ad(!srv_read_only_mode);
+
+ recalc_pool.clear();
+}
+
+/*****************************************************************//**
+Add a table to the recalc pool, which is processed by the
+background stats gathering thread. Only the table id is added to the
+list, so the table can be closed after being enqueued and it will be
+opened when needed. If the table does not exist later (has been DROPped),
+then it will be removed from the pool and skipped.
+dict_stats_recalc_pool_add() @{ */
+UNIV_INTERN
+void
+dict_stats_recalc_pool_add(
+/*=======================*/
+ const dict_table_t* table) /*!< in: table to add */
+{
+ ut_ad(!srv_read_only_mode);
+
+ mutex_enter(&recalc_pool_mutex);
+
+ /* quit if already in the list */
+ for (recalc_pool_iterator_t iter = recalc_pool.begin();
+ iter != recalc_pool.end();
+ ++iter) {
+
+ if (*iter == table->id) {
+ mutex_exit(&recalc_pool_mutex);
+ return;
+ }
+ }
+
+ recalc_pool.push_back(table->id);
+
+ mutex_exit(&recalc_pool_mutex);
+
+ os_event_set(dict_stats_event);
+}
+/* @} */
+
+/*****************************************************************//**
+Get a table from the auto recalc pool. The returned table id is removed
+from the pool.
+dict_stats_recalc_pool_get() @{
+@return true if the pool was non-empty and "id" was set, false otherwise */
+static
+bool
+dict_stats_recalc_pool_get(
+/*=======================*/
+ table_id_t* id) /*!< out: table id, or unmodified if list is
+ empty */
+{
+ ut_ad(!srv_read_only_mode);
+
+ mutex_enter(&recalc_pool_mutex);
+
+ if (recalc_pool.empty()) {
+ mutex_exit(&recalc_pool_mutex);
+ return(false);
+ }
+
+ *id = recalc_pool[0];
+
+ recalc_pool.erase(recalc_pool.begin());
+
+ mutex_exit(&recalc_pool_mutex);
+
+ return(true);
+}
+/* @} */
+
+/*****************************************************************//**
+Delete a given table from the auto recalc pool.
+dict_stats_recalc_pool_del() */
+UNIV_INTERN
+void
+dict_stats_recalc_pool_del(
+/*=======================*/
+ const dict_table_t* table) /*!< in: table to remove */
+{
+ ut_ad(!srv_read_only_mode);
+ ut_ad(mutex_own(&dict_sys->mutex));
+
+ mutex_enter(&recalc_pool_mutex);
+
+ ut_ad(table->id > 0);
+
+ for (recalc_pool_iterator_t iter = recalc_pool.begin();
+ iter != recalc_pool.end();
+ ++iter) {
+
+ if (*iter == table->id) {
+ /* erase() invalidates the iterator */
+ recalc_pool.erase(iter);
+ break;
+ }
+ }
+
+ mutex_exit(&recalc_pool_mutex);
+}
+
+/*****************************************************************//**
+Wait until background stats thread has stopped using the specified table(s).
+The caller must have locked the data dictionary using
+row_mysql_lock_data_dictionary() and this function may unlock it temporarily
+and restore the lock before it exits.
+The background stats thead is guaranteed not to start using the specified
+tables after this function returns and before the caller unlocks the data
+dictionary because it sets the BG_STAT_IN_PROGRESS bit in table->stats_bg_flag
+under dict_sys->mutex.
+dict_stats_wait_bg_to_stop_using_table() @{ */
+UNIV_INTERN
+void
+dict_stats_wait_bg_to_stop_using_tables(
+/*====================================*/
+ dict_table_t* table1, /*!< in/out: table1 */
+ dict_table_t* table2, /*!< in/out: table2, could be NULL */
+ trx_t* trx) /*!< in/out: transaction to use for
+ unlocking/locking the data dict */
+{
+ ut_ad(!srv_read_only_mode);
+
+ while ((table1->stats_bg_flag & BG_STAT_IN_PROGRESS)
+ || (table2 != NULL
+ && (table2->stats_bg_flag & BG_STAT_IN_PROGRESS))) {
+
+ table1->stats_bg_flag |= BG_STAT_SHOULD_QUIT;
+ if (table2 != NULL) {
+ table2->stats_bg_flag |= BG_STAT_SHOULD_QUIT;
+ }
+
+ row_mysql_unlock_data_dictionary(trx);
+ os_thread_sleep(250000);
+ row_mysql_lock_data_dictionary(trx);
+ }
+}
+/* @} */
+
+/*****************************************************************//**
+Initialize global variables needed for the operation of dict_stats_thread()
+Must be called before dict_stats_thread() is started.
+dict_stats_thread_init() @{ */
+UNIV_INTERN
+void
+dict_stats_thread_init()
+/*====================*/
+{
+ ut_a(!srv_read_only_mode);
+
+ dict_stats_event = os_event_create();
+
+ /* The recalc_pool_mutex is acquired from:
+ 1) the background stats gathering thread before any other latch
+ and released without latching anything else in between (thus
+ any level would do here)
+ 2) from row_update_statistics_if_needed()
+ and released without latching anything else in between. We know
+ that dict_sys->mutex (SYNC_DICT) is not acquired when
+ row_update_statistics_if_needed() is called and it may be acquired
+ inside that function (thus a level <=SYNC_DICT would do).
+ 3) from row_drop_table_for_mysql() after dict_sys->mutex (SYNC_DICT)
+ and dict_operation_lock (SYNC_DICT_OPERATION) have been locked
+ (thus a level <SYNC_DICT && <SYNC_DICT_OPERATION would do)
+ So we choose SYNC_STATS_AUTO_RECALC to be about below SYNC_DICT. */
+ mutex_create(recalc_pool_mutex_key, &recalc_pool_mutex,
+ SYNC_STATS_AUTO_RECALC);
+
+ dict_stats_recalc_pool_init();
+}
+/* @} */
+
+/*****************************************************************//**
+Free resources allocated by dict_stats_thread_init(), must be called
+after dict_stats_thread() has exited.
+dict_stats_thread_deinit() @{ */
+UNIV_INTERN
+void
+dict_stats_thread_deinit()
+/*======================*/
+{
+ ut_a(!srv_read_only_mode);
+ ut_ad(!srv_dict_stats_thread_active);
+
+ dict_stats_recalc_pool_deinit();
+
+ mutex_free(&recalc_pool_mutex);
+ memset(&recalc_pool_mutex, 0x0, sizeof(recalc_pool_mutex));
+
+ os_event_free(dict_stats_event);
+ dict_stats_event = NULL;
+}
+/* @} */
+
+/*****************************************************************//**
+Get the first table that has been added for auto recalc and eventually
+update its stats.
+dict_stats_process_entry_from_recalc_pool() @{ */
+static
+void
+dict_stats_process_entry_from_recalc_pool()
+/*=======================================*/
+{
+ table_id_t table_id;
+
+ ut_ad(!srv_read_only_mode);
+
+ /* pop the first table from the auto recalc pool */
+ if (!dict_stats_recalc_pool_get(&table_id)) {
+ /* no tables for auto recalc */
+ return;
+ }
+
+ dict_table_t* table;
+
+ mutex_enter(&dict_sys->mutex);
+
+ table = dict_table_open_on_id(table_id, TRUE, FALSE);
+
+ if (table == NULL) {
+ /* table does not exist, must have been DROPped
+ after its id was enqueued */
+ mutex_exit(&dict_sys->mutex);
+ return;
+ }
+
+ /* Check whether table is corrupted */
+ if (table->corrupted) {
+ dict_table_close(table, TRUE, FALSE);
+ mutex_exit(&dict_sys->mutex);
+ return;
+ }
+
+ table->stats_bg_flag = BG_STAT_IN_PROGRESS;
+
+ mutex_exit(&dict_sys->mutex);
+
+ /* ut_time() could be expensive, the current function
+ is called once every time a table has been changed more than 10% and
+ on a system with lots of small tables, this could become hot. If we
+ find out that this is a problem, then the check below could eventually
+ be replaced with something else, though a time interval is the natural
+ approach. */
+
+ if (ut_difftime(ut_time(), table->stats_last_recalc)
+ < MIN_RECALC_INTERVAL) {
+
+ /* Stats were (re)calculated not long ago. To avoid
+ too frequent stats updates we put back the table on
+ the auto recalc list and do nothing. */
+
+ dict_stats_recalc_pool_add(table);
+
+ } else {
+
+ dict_stats_update(table, DICT_STATS_RECALC_PERSISTENT);
+ }
+
+ mutex_enter(&dict_sys->mutex);
+
+ table->stats_bg_flag = BG_STAT_NONE;
+
+ dict_table_close(table, TRUE, FALSE);
+
+ mutex_exit(&dict_sys->mutex);
+}
+/* @} */
+
+/*****************************************************************//**
+This is the thread for background stats gathering. It pops tables, from
+the auto recalc list and proceeds them, eventually recalculating their
+statistics.
+dict_stats_thread() @{
+@return this function does not return, it calls os_thread_exit() */
+extern "C" UNIV_INTERN
+os_thread_ret_t
+DECLARE_THREAD(dict_stats_thread)(
+/*==============================*/
+ void* arg __attribute__((unused))) /*!< in: a dummy parameter
+ required by os_thread_create */
+{
+ ut_a(!srv_read_only_mode);
+
+ srv_dict_stats_thread_active = TRUE;
+
+ while (!SHUTTING_DOWN()) {
+
+ /* Wake up periodically even if not signaled. This is
+ because we may lose an event - if the below call to
+ dict_stats_process_entry_from_recalc_pool() puts the entry back
+ in the list, the os_event_set() will be lost by the subsequent
+ os_event_reset(). */
+ os_event_wait_time(
+ dict_stats_event, MIN_RECALC_INTERVAL * 1000000);
+
+ if (SHUTTING_DOWN()) {
+ break;
+ }
+
+ dict_stats_process_entry_from_recalc_pool();
+
+ os_event_reset(dict_stats_event);
+ }
+
+ srv_dict_stats_thread_active = FALSE;
+
+ /* We count the number of threads in os_thread_exit(). A created
+ thread should always use that to exit instead of return(). */
+ os_thread_exit(NULL);
+
+ OS_THREAD_DUMMY_RETURN;
+}
+/* @} */
+
+/* vim: set foldmethod=marker foldmarker=@{,@}: */