Commit 576afcea authored by Marko Mäkelä's avatar Marko Mäkelä

MDEV-26966: Remove innodb_force_load_corrupted

MySQL 5.5 in commit 177d8b0c
introduced a configuration parameter innodb_force_load_corrupted
whose purpose was to allow a corrupted table to be dropped.

Given that MDEV-11412 in MariaDB 10.5.4 aims to allow any metadata
for a missing or corrupted table to be dropped, and given that
MDEV-17567 and MDEV-25506 and related tasks made DDL operations
crash-safe, the parameter no longer serves any purpose.

Because this obscure parameter was read-only (not settable by a client),
it seems that we can simply declare it with MARIADB_REMOVED_OPTION
(commit 1bc9cce7) without breaking
any upgrades.

DICT_ERR_IGNORE_INDEX: Replaces DICT_ERR_IGNORE_INDEX_ROOT and
DICT_ERR_IGNORE_CORRUPT, which were always set equally.

dict_load_indexes(): Report "No indexes found for table" in
a uniform way, and only when the DICT_ERR_IGNORE_INDEX flag is
not set.

If the clustered index is marked corrupted, and the operation
is DICT_ERR_IGNORE_DROP (we are about to drop the table), we will
load the metadata; else, we will return DB_INDEX_CORRUPT.

If SYS_INDEXES.PAGE is FIL_NULL, report an error or warning
unless we are about to drop the table.

dict_load_table_one(): Simplify the logic.
parent 993b8edf
'#---------------------BS_STVARS_031_01----------------------#'
SELECT COUNT(@@GLOBAL.innodb_force_load_corrupted);
COUNT(@@GLOBAL.innodb_force_load_corrupted)
1
1 Expected
'#---------------------BS_STVARS_031_02----------------------#'
SET @@GLOBAL.innodb_force_load_corrupted=1;
ERROR HY000: Variable 'innodb_force_load_corrupted' is a read only variable
Expected error 'Read only variable'
SELECT COUNT(@@GLOBAL.innodb_force_load_corrupted);
COUNT(@@GLOBAL.innodb_force_load_corrupted)
1
1 Expected
'#---------------------BS_STVARS_031_03----------------------#'
SELECT IF(@@GLOBAL.innodb_force_load_corrupted, "ON", "OFF") = VARIABLE_VALUE
FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES
WHERE VARIABLE_NAME='innodb_force_load_corrupted';
IF(@@GLOBAL.innodb_force_load_corrupted, "ON", "OFF") = VARIABLE_VALUE
1
1 Expected
SELECT COUNT(@@GLOBAL.innodb_force_load_corrupted);
COUNT(@@GLOBAL.innodb_force_load_corrupted)
1
1 Expected
SELECT COUNT(VARIABLE_VALUE)
FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES
WHERE VARIABLE_NAME='innodb_force_load_corrupted';
COUNT(VARIABLE_VALUE)
1
1 Expected
'#---------------------BS_STVARS_031_04----------------------#'
SELECT @@innodb_force_load_corrupted = @@GLOBAL.innodb_force_load_corrupted;
@@innodb_force_load_corrupted = @@GLOBAL.innodb_force_load_corrupted
1
1 Expected
'#---------------------BS_STVARS_031_05----------------------#'
SELECT COUNT(@@innodb_force_load_corrupted);
COUNT(@@innodb_force_load_corrupted)
1
1 Expected
SELECT COUNT(@@local.innodb_force_load_corrupted);
ERROR HY000: Variable 'innodb_force_load_corrupted' is a GLOBAL variable
Expected error 'Variable is a GLOBAL variable'
SELECT COUNT(@@SESSION.innodb_force_load_corrupted);
ERROR HY000: Variable 'innodb_force_load_corrupted' is a GLOBAL variable
Expected error 'Variable is a GLOBAL variable'
SELECT COUNT(@@GLOBAL.innodb_force_load_corrupted);
COUNT(@@GLOBAL.innodb_force_load_corrupted)
1
1 Expected
SELECT innodb_force_load_corrupted = @@SESSION.innodb_force_load_corrupted;
ERROR 42S22: Unknown column 'innodb_force_load_corrupted' in 'field list'
Expected error 'Readonly variable'
......@@ -765,18 +765,6 @@ NUMERIC_BLOCK_SIZE NULL
ENUM_VALUE_LIST OFF,ON
READ_ONLY NO
COMMAND_LINE_ARGUMENT NONE
VARIABLE_NAME INNODB_FORCE_LOAD_CORRUPTED
SESSION_VALUE NULL
DEFAULT_VALUE OFF
VARIABLE_SCOPE GLOBAL
VARIABLE_TYPE BOOLEAN
VARIABLE_COMMENT Force InnoDB to load metadata of corrupted table.
NUMERIC_MIN_VALUE NULL
NUMERIC_MAX_VALUE NULL
NUMERIC_BLOCK_SIZE NULL
ENUM_VALUE_LIST OFF,ON
READ_ONLY YES
COMMAND_LINE_ARGUMENT NONE
VARIABLE_NAME INNODB_FORCE_PRIMARY_KEY
SESSION_VALUE NULL
DEFAULT_VALUE OFF
......
################## mysql-test\t\innodb_force_load_corrupted_basic.test #####
# #
# Variable Name: innodb_force_load_corrupted #
# Scope: Global #
# Access Type: Static #
# Data Type: boolean #
# #
# #
# Creation Date: 2008-02-07 #
# Author : Sharique Abdullah #
# #
# #
# Description:Test Cases of Dynamic System Variable innodb_force_load_corrupted#
# that checks the behavior of this variable in the following ways #
# * Value Check #
# * Scope Check #
# #
# Reference: http://dev.mysql.com/doc/refman/5.1/en/ #
# server-system-variables.html #
# #
###############################################################################
--source include/have_innodb.inc
--echo '#---------------------BS_STVARS_031_01----------------------#'
####################################################################
# Displaying default value #
####################################################################
SELECT COUNT(@@GLOBAL.innodb_force_load_corrupted);
--echo 1 Expected
--echo '#---------------------BS_STVARS_031_02----------------------#'
####################################################################
# Check if Value can set #
####################################################################
--error ER_INCORRECT_GLOBAL_LOCAL_VAR
SET @@GLOBAL.innodb_force_load_corrupted=1;
--echo Expected error 'Read only variable'
SELECT COUNT(@@GLOBAL.innodb_force_load_corrupted);
--echo 1 Expected
--echo '#---------------------BS_STVARS_031_03----------------------#'
#################################################################
# Check if the value in GLOBAL Table matches value in variable #
#################################################################
--disable_warnings
SELECT IF(@@GLOBAL.innodb_force_load_corrupted, "ON", "OFF") = VARIABLE_VALUE
FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES
WHERE VARIABLE_NAME='innodb_force_load_corrupted';
--enable_warnings
--echo 1 Expected
SELECT COUNT(@@GLOBAL.innodb_force_load_corrupted);
--echo 1 Expected
--disable_warnings
SELECT COUNT(VARIABLE_VALUE)
FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES
WHERE VARIABLE_NAME='innodb_force_load_corrupted';
--enable_warnings
--echo 1 Expected
--echo '#---------------------BS_STVARS_031_04----------------------#'
################################################################################
# Check if accessing variable with and without GLOBAL point to same variable #
################################################################################
SELECT @@innodb_force_load_corrupted = @@GLOBAL.innodb_force_load_corrupted;
--echo 1 Expected
--echo '#---------------------BS_STVARS_031_05----------------------#'
################################################################################
# Check if innodb_force_load_corrupted can be accessed with and without @@ sign #
################################################################################
SELECT COUNT(@@innodb_force_load_corrupted);
--echo 1 Expected
--Error ER_INCORRECT_GLOBAL_LOCAL_VAR
SELECT COUNT(@@local.innodb_force_load_corrupted);
--echo Expected error 'Variable is a GLOBAL variable'
--Error ER_INCORRECT_GLOBAL_LOCAL_VAR
SELECT COUNT(@@SESSION.innodb_force_load_corrupted);
--echo Expected error 'Variable is a GLOBAL variable'
SELECT COUNT(@@GLOBAL.innodb_force_load_corrupted);
--echo 1 Expected
--Error ER_BAD_FIELD_ERROR
SELECT innodb_force_load_corrupted = @@SESSION.innodb_force_load_corrupted;
--echo Expected error 'Readonly variable'
......@@ -5105,6 +5105,9 @@ static int init_server_components()
MYSQL_COMPATIBILITY_OPTION("new"),
MYSQL_COMPATIBILITY_OPTION("show_compatibility_56"),
/* The following options were removed in 10.6 */
MARIADB_REMOVED_OPTION("innodb-force-load-corrupted"),
/* The following options were removed in 10.5 */
#if defined(__linux__)
MARIADB_REMOVED_OPTION("super-large-pages"),
......
......@@ -80,6 +80,7 @@ static const char *removed_variables[] =
"innodb_file_format_check",
"innodb_file_format_max",
"innodb_flush_neighbor_pages",
"innodb_force_load_corrupted",
"innodb_foreground_preflush",
"innodb_ibuf_accel_rate",
"innodb_ibuf_active_contract",
......
......@@ -145,10 +145,6 @@ dict_load_field_low(
for temporary storage */
const rec_t* rec); /*!< in: SYS_FIELDS record */
/* If this flag is TRUE, then we will load the cluster index's (and tables')
metadata even if it is marked as "corrupted". */
my_bool srv_load_corrupted;
#ifdef UNIV_DEBUG
/****************************************************************//**
Compare the name of an index column.
......@@ -1833,31 +1829,11 @@ dict_load_indexes(
btr_pcur_open_on_user_rec(sys_index, tuple, PAGE_CUR_GE,
BTR_SEARCH_LEAF, &pcur, &mtr);
for (;;) {
while (btr_pcur_is_on_user_rec(&pcur)) {
dict_index_t* index = NULL;
const char* err_msg;
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::warn() << "Cannot load table "
<< table->name
<< " because it has no indexes in"
" InnoDB internal data dictionary.";
error = DB_CORRUPTION;
goto func_exit;
}
break;
}
rec = btr_pcur_get_rec(&pcur);
if ((ignore_err & DICT_ERR_IGNORE_RECOVER_LOCK)
&& (rec_get_n_fields_old(rec)
== DICT_NUM_FIELDS__SYS_INDEXES
......@@ -1882,27 +1858,11 @@ dict_load_indexes(
}
err_msg = dict_load_index_low(buf, heap, rec, TRUE, &index);
ut_ad((index == NULL && err_msg != NULL)
|| (index != NULL && err_msg == NULL));
ut_ad(!index == !!err_msg);
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::warn() << "Failed to load the"
" clustered index for table "
<< table->name
<< " because of TABLE_ID mismatch."
" Refusing to load the rest of the"
" indexes (if any) and the whole table"
" altogether.";
error = DB_CORRUPTION;
goto func_exit;
}
/* We have ran out of index definitions for
the table. */
break;
}
......@@ -1914,7 +1874,7 @@ dict_load_indexes(
goto next_rec;
} else if (err_msg) {
ib::error() << err_msg;
if (ignore_err & DICT_ERR_IGNORE_CORRUPT) {
if (ignore_err & DICT_ERR_IGNORE_INDEX) {
goto next_rec;
}
error = DB_CORRUPTION;
......@@ -1933,29 +1893,11 @@ dict_load_indexes(
ut_ad(!dict_index_is_online_ddl(index));
/* Check whether the index is corrupted */
if (index->is_corrupted()) {
ib::error() << "Index " << index->name
<< " of table " << table->name
<< " is corrupted";
if (!srv_load_corrupted
&& !(ignore_err & DICT_ERR_IGNORE_CORRUPT)
&& dict_index_is_clust(index)) {
dict_mem_index_free(index);
error = DB_INDEX_CORRUPT;
goto func_exit;
} else {
/* We will load the index if
1) srv_load_corrupted is TRUE
2) ignore_err is set with
DICT_ERR_IGNORE_CORRUPT
3) if the index corrupted is a secondary
index */
ib::info() << "Load corrupted index "
<< index->name
<< " of table " << table->name;
}
if (ignore_err != DICT_ERR_IGNORE_DROP
&& index->is_corrupted() && index->is_clust()) {
dict_mem_index_free(index);
error = DB_INDEX_CORRUPT;
goto func_exit;
}
if (index->type & DICT_FTS
......@@ -1981,31 +1923,30 @@ dict_load_indexes(
} else if (index->page == FIL_NULL
&& table->is_readable()
&& (!(index->type & DICT_FTS))) {
if (ignore_err != DICT_ERR_IGNORE_DROP) {
ib::error_or_warn(!(ignore_err
& DICT_ERR_IGNORE_INDEX))
<< "Index " << index->name
<< " for table " << table->name
<< " has been freed!";
}
ib::error() << "Trying to load index " << index->name
<< " for table " << table->name
<< ", but the index tree has been freed!";
if (ignore_err & DICT_ERR_IGNORE_INDEX_ROOT) {
/* If caller can tolerate this error,
we will continue to load the index and
let caller deal with this error. However
mark the index and table corrupted. We
only need to mark such in the index
dictionary cache for such metadata corruption,
since we would always be able to set it
when loading the dictionary cache */
index->table = table;
dict_set_corrupted_index_cache_only(index);
ib::info() << "Index is corrupt but forcing"
" load into data dictionary";
} else {
if (!(ignore_err & DICT_ERR_IGNORE_INDEX)) {
corrupted:
dict_mem_index_free(index);
error = DB_CORRUPTION;
goto func_exit;
}
/* If caller can tolerate this error,
we will continue to load the index and
let caller deal with this error. However
mark the index and table corrupted. We
only need to mark such in the index
dictionary cache for such metadata corruption,
since we would always be able to set it
when loading the dictionary cache */
index->table = table;
dict_set_corrupted_index_cache_only(index);
} else if (!dict_index_is_clust(index)
&& NULL == dict_table_get_first_index(table)) {
......@@ -2054,6 +1995,13 @@ dict_load_indexes(
btr_pcur_move_to_next_user_rec(&pcur, &mtr);
}
if (!dict_table_get_first_index(table)
&& !(ignore_err & DICT_ERR_IGNORE_INDEX)) {
ib::warn() << "No indexes found for table " << table->name;
error = DB_CORRUPTION;
goto func_exit;
}
ut_ad(table->fts_doc_id_index == NULL);
if (table->fts != NULL) {
......@@ -2388,26 +2336,20 @@ static dict_table_t *dict_load_table_one(const span<const char> &name,
if (err == DB_INDEX_CORRUPT) {
/* Refuse to load the table if the table has a corrupted
cluster index */
if (!srv_load_corrupted) {
ib::error() << "Load table " << table->name
<< " failed, the table has"
" corrupted clustered indexes. Turn on"
" 'innodb_force_load_corrupted' to drop it";
dict_sys.remove(table);
table = NULL;
goto func_exit;
} else {
if (table->indexes.start->is_corrupted()) {
table->corrupted = true;
}
}
ut_ad(index_load_err != DICT_ERR_IGNORE_DROP);
ib::error() << "Refusing to load corrupted table "
<< table->name;
evict:
dict_sys.remove(table);
table = NULL;
goto func_exit;
}
if (err == DB_SUCCESS && table->is_readable()) {
const auto root = dict_table_get_first_index(table)->page;
if (root >= table->space->get_size()) {
if (err != DB_SUCCESS || !table->is_readable()) {
} else if (dict_index_t* pk = dict_table_get_first_index(table)) {
ut_ad(pk->is_primary());
if (pk->is_corrupted()
|| pk->page >= table->space->get_size()) {
corrupted:
table->corrupted = true;
table->file_unreadable = true;
......@@ -2418,7 +2360,7 @@ static dict_table_t *dict_load_table_one(const span<const char> &name,
only to delete the .ibd files. */
goto corrupted;
} else {
const page_id_t page_id(table->space->id, root);
const page_id_t page_id{table->space->id, pk->page};
mtr.start();
buf_block_t* block = buf_page_get(
page_id, table->space->zip_size(),
......@@ -2443,6 +2385,12 @@ static dict_table_t *dict_load_table_one(const span<const char> &name,
err = btr_cur_instant_init(table);
}
}
} else {
ut_ad(ignore_err & DICT_ERR_IGNORE_INDEX);
if (ignore_err != DICT_ERR_IGNORE_DROP) {
err = DB_CORRUPTION;
goto evict;
}
}
/* Initialize table foreign_child value. Its value could be
......@@ -2465,33 +2413,11 @@ static dict_table_t *dict_load_table_one(const span<const char> &name,
<< " failed, the table has missing"
" foreign key indexes. Turn off"
" 'foreign_key_checks' and try again.";
evict:
dict_sys.remove(table);
table = NULL;
goto evict;
} else {
dict_mem_table_fill_foreign_vcol_set(table);
table->fk_max_recusive_level = 0;
}
} else {
dict_index_t* index;
/* Make sure that at least the clustered index was loaded.
Otherwise refuse to load the table */
index = dict_table_get_first_index(table);
if (!srv_force_recovery
|| !index
|| !index->is_primary()) {
ib::warn() << "Failed to load table " << table->name
<< ":" << err;
goto evict;
} else if (index->is_corrupted()
&& table->is_readable()) {
/* It is possible we force to load a corrupted
clustered index if srv_load_corrupted is set.
Mark the table as corrupted in this case */
table->corrupted = true;
}
}
func_exit:
......
......@@ -18880,11 +18880,6 @@ static MYSQL_SYSVAR_ENUM(flush_method, srv_file_flush_method,
NULL, NULL, IF_WIN(SRV_ALL_O_DIRECT_FSYNC, SRV_O_DIRECT),
&innodb_flush_method_typelib);
static MYSQL_SYSVAR_BOOL(force_load_corrupted, srv_load_corrupted,
PLUGIN_VAR_NOCMDARG | PLUGIN_VAR_READONLY,
"Force InnoDB to load metadata of corrupted table.",
NULL, NULL, FALSE);
static MYSQL_SYSVAR_STR(log_group_home_dir, srv_log_group_home_dir,
PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
"Path to InnoDB log files.", NULL, NULL, NULL);
......@@ -19781,7 +19776,6 @@ static struct st_mysql_sys_var* innobase_system_variables[]= {
MYSQL_SYSVAR(ft_min_token_size),
MYSQL_SYSVAR(ft_num_word_optimize),
MYSQL_SYSVAR(ft_sort_pll_degree),
MYSQL_SYSVAR(force_load_corrupted),
MYSQL_SYSVAR(lock_wait_timeout),
MYSQL_SYSVAR(deadlock_detect),
MYSQL_SYSVAR(deadlock_report),
......
......@@ -71,23 +71,21 @@ enum dict_err_ignore_t {
DICT_ERR_IGNORE_NONE = 0, /*!< no error to ignore */
DICT_ERR_IGNORE_FK_NOKEY = 1, /*!< ignore error if any foreign
key is missing */
DICT_ERR_IGNORE_INDEX_ROOT = 2, /*!< ignore error if index root
page is FIL_NULL or incorrect value */
DICT_ERR_IGNORE_CORRUPT = 4, /*!< skip corrupted indexes */
DICT_ERR_IGNORE_RECOVER_LOCK = 8,
DICT_ERR_IGNORE_INDEX = 2, /*!< ignore corrupted indexes */
DICT_ERR_IGNORE_RECOVER_LOCK = 4,
/*!< Used when recovering table locks
for resurrected transactions.
Silently load a missing
tablespace, and do not load
incomplete index definitions. */
/** ignore all errors above */
DICT_ERR_IGNORE_ALL = 15,
DICT_ERR_IGNORE_ALL = 7,
/** prepare some DDL operation;
do not attempt to load tablespace */
DICT_ERR_IGNORE_TABLESPACE = 31,
DICT_ERR_IGNORE_TABLESPACE = 15,
/** prepare to drop the table; do not attempt to load tablespace
or the metadata */
DICT_ERR_IGNORE_DROP = 63
DICT_ERR_IGNORE_DROP = 31
};
/** Quiescing states for flushing tables to disk. */
......
......@@ -307,11 +307,6 @@ void innodb_wait_allow_writes();
# define innodb_wait_allow_writes() do {} while (0)
#endif /* WITH_INNODB_DISALLOW_WRITES */
/* If this flag is TRUE, then we will load the indexes' (and tables') metadata
even if they are marked as "corrupted". Mostly it is for DBA to process
corrupted index and table */
extern my_bool srv_load_corrupted;
/** Requested size in bytes */
extern ulint srv_buf_pool_size;
/** Minimum pool size in bytes */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment