1
0
mirror of https://github.com/MariaDB/server.git synced 2025-08-07 00:04:31 +03:00

MDEV-14825 Assertion `col->ord_part' in row_build_index_entry_low upon ROLLBACK or DELETE with concurrent ALTER on partitioned table

If creating a secondary index fails (typically, ADD UNIQUE INDEX fails
due to duplicate key), it is possible that concurrently running UPDATE
or DELETE will access the index stub and hit the debug assertion.

It does not make any sense to keep updating an uncommitted index whose
creation has failed.

dict_index_t::is_corrupted(): Replaces dict_index_is_corrupted().
Also take online_status into account.

Replace some calls to dict_index_is_clust() with calls to
dict_index_t::is_primary().
This commit is contained in:
Marko Mäkelä
2018-05-07 15:30:57 +03:00
parent d257c425f2
commit e44ca6cc9c
16 changed files with 115 additions and 85 deletions

View File

@@ -0,0 +1,27 @@
CREATE TABLE t1 (a INT, b VARCHAR(10)) ENGINE=InnoDB
PARTITION BY RANGE(a)
(PARTITION pa VALUES LESS THAN (3),
PARTITION pb VALUES LESS THAN (5));
INSERT INTO t1 VALUES(2,'two'),(2,'two'),(4,'four');
connect ddl,localhost,root,,test;
SET DEBUG_SYNC = 'inplace_after_index_build SIGNAL go WAIT_FOR done';
ALTER TABLE t1 ADD UNIQUE KEY (a,b(3));
connection default;
SET DEBUG_SYNC = 'now WAIT_FOR go';
BEGIN;
SELECT * FROM t1 FOR UPDATE;
a b
2 two
2 two
4 four
SET DEBUG_SYNC = 'now SIGNAL done';
connection ddl;
ERROR 23000: Duplicate entry '2-two' for key 'a'
connection default;
DELETE FROM t1;
disconnect ddl;
SET DEBUG_SYNC = 'RESET';
CHECK TABLE t1;
Table Op Msg_type Msg_text
test.t1 check status OK
DROP TABLE t1;

View File

@@ -0,0 +1,34 @@
--source include/have_innodb.inc
--source include/have_partition.inc
--source include/have_debug.inc
--source include/have_debug_sync.inc
CREATE TABLE t1 (a INT, b VARCHAR(10)) ENGINE=InnoDB
PARTITION BY RANGE(a)
(PARTITION pa VALUES LESS THAN (3),
PARTITION pb VALUES LESS THAN (5));
INSERT INTO t1 VALUES(2,'two'),(2,'two'),(4,'four');
connect ddl,localhost,root,,test;
SET DEBUG_SYNC = 'inplace_after_index_build SIGNAL go WAIT_FOR done';
send ALTER TABLE t1 ADD UNIQUE KEY (a,b(3));
connection default;
SET DEBUG_SYNC = 'now WAIT_FOR go';
BEGIN;
SELECT * FROM t1 FOR UPDATE;
SET DEBUG_SYNC = 'now SIGNAL done';
connection ddl;
--error ER_DUP_ENTRY
reap;
connection default;
DELETE FROM t1;
disconnect ddl;
SET DEBUG_SYNC = 'RESET';
CHECK TABLE t1;
DROP TABLE t1;

View File

@@ -1,6 +1,6 @@
/*****************************************************************************
Copyright (c) 2016, MariaDB Corporation. All Rights Reserved.
Copyright (c) 2016, 2018, MariaDB Corporation.
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
@@ -232,7 +232,7 @@ dict_stats_process_entry_from_defrag_pool()
? dict_table_find_index_on_id(table, index_id)
: NULL;
if (!index || dict_index_is_corrupted(index)) {
if (!index || index->is_corrupted()) {
if (table) {
dict_table_close(table, TRUE, FALSE);
}

View File

@@ -2521,10 +2521,10 @@ dict_load_indexes(
}
ut_ad(index);
ut_ad(!dict_index_is_online_ddl(index));
/* Check whether the index is corrupted */
if (dict_index_is_corrupted(index)) {
if (index->is_corrupted()) {
ib::error() << "Index " << index->name
<< " of table " << table->name
<< " is corrupted";
@@ -3044,10 +3044,7 @@ err_exit:
table = NULL;
goto func_exit;
} else {
dict_index_t* clust_index;
clust_index = dict_table_get_first_index(table);
if (dict_index_is_corrupted(clust_index)) {
if (table->indexes.start->is_corrupted()) {
table->corrupted = true;
}
}
@@ -3095,14 +3092,11 @@ err_exit:
if (!srv_force_recovery
|| !index
|| !dict_index_is_clust(index)) {
|| !index->is_primary()) {
dict_table_remove_from_cache(table);
table = NULL;
} else if (dict_index_is_corrupted(index)
} 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 */

View File

@@ -158,9 +158,8 @@ dict_stats_should_ignore_index(
/*===========================*/
const dict_index_t* index) /*!< in: index */
{
return((index->type & DICT_FTS)
|| dict_index_is_corrupted(index)
|| dict_index_is_spatial(index)
return((index->type & (DICT_FTS | DICT_SPATIAL))
|| index->is_corrupted()
|| index->to_be_dropped
|| !index->is_committed());
}
@@ -2228,7 +2227,7 @@ dict_stats_update_persistent(
index = dict_table_get_first_index(table);
if (index == NULL
|| dict_index_is_corrupted(index)
|| index->is_corrupted()
|| (index->type | DICT_UNIQUE) != (DICT_CLUSTERED | DICT_UNIQUE)) {
/* Table definition is corrupt */

View File

@@ -6549,7 +6549,7 @@ fts_check_corrupt_index(
if (index->id == aux_table->index_id) {
ut_ad(index->type & DICT_FTS);
dict_table_close(table, true, false);
return(dict_index_is_corrupted(index));
return index->is_corrupted();
}
}

View File

@@ -9406,13 +9406,13 @@ ha_innobase::index_read(
dict_index_t* index = m_prebuilt->index;
if (index == NULL || dict_index_is_corrupted(index)) {
if (index == NULL || index->is_corrupted()) {
m_prebuilt->index_usable = FALSE;
DBUG_RETURN(HA_ERR_CRASHED);
}
if (!m_prebuilt->index_usable) {
DBUG_RETURN(dict_index_is_corrupted(index)
DBUG_RETURN(index->is_corrupted()
? HA_ERR_INDEX_CORRUPT
: HA_ERR_TABLE_DEF_CHANGED);
}
@@ -9671,14 +9671,14 @@ ha_innobase::change_active_index(
m_prebuilt->trx, m_prebuilt->index);
if (!m_prebuilt->index_usable) {
if (dict_index_is_corrupted(m_prebuilt->index)) {
if (m_prebuilt->index->is_corrupted()) {
char table_name[MAX_FULL_NAME_LEN + 1];
innobase_format_name(
table_name, sizeof table_name,
m_prebuilt->index->table->name.m_name);
if (dict_index_is_clust(m_prebuilt->index)) {
if (m_prebuilt->index->is_primary()) {
ut_ad(m_prebuilt->index->table->corrupted);
push_warning_printf(
m_user_thd, Sql_condition::WARN_LEVEL_WARN,
@@ -13662,7 +13662,7 @@ ha_innobase::records_in_range(
n_rows = HA_POS_ERROR;
goto func_exit;
}
if (dict_index_is_corrupted(index)) {
if (index->is_corrupted()) {
n_rows = HA_ERR_INDEX_CORRUPT;
goto func_exit;
}
@@ -14522,7 +14522,7 @@ ha_innobase::defragment_table(
for (index = dict_table_get_first_index(table); index;
index = dict_table_get_next_index(index)) {
if (dict_index_is_corrupted(index)) {
if (index->is_corrupted()) {
continue;
}
@@ -14721,7 +14721,7 @@ ha_innobase::check(
clustered index, we will do so here */
index = dict_table_get_first_index(m_prebuilt->table);
if (!dict_index_is_corrupted(index)) {
if (!index->is_corrupted()) {
dict_set_corrupted(
index, m_prebuilt->trx, "CHECK TABLE");
}
@@ -14759,7 +14759,7 @@ ha_innobase::check(
}
if (!(check_opt->flags & T_QUICK)
&& !dict_index_is_corrupted(index)) {
&& !index->is_corrupted()) {
/* Enlarge the fatal lock wait timeout during
CHECK TABLE. */
my_atomic_addlong(
@@ -14811,7 +14811,7 @@ ha_innobase::check(
DBUG_EXECUTE_IF(
"dict_set_index_corrupted",
if (!dict_index_is_clust(index)) {
if (!index->is_primary()) {
m_prebuilt->index_usable = FALSE;
// row_mysql_lock_data_dictionary(m_prebuilt->trx);
dict_set_corrupted(index, m_prebuilt->trx, "dict_set_index_corrupted");
@@ -14819,7 +14819,7 @@ ha_innobase::check(
});
if (UNIV_UNLIKELY(!m_prebuilt->index_usable)) {
if (dict_index_is_corrupted(m_prebuilt->index)) {
if (index->is_corrupted()) {
push_warning_printf(
m_user_thd,
Sql_condition::WARN_LEVEL_WARN,
@@ -14859,7 +14859,7 @@ ha_innobase::check(
DBUG_EXECUTE_IF(
"dict_set_index_corrupted",
if (!dict_index_is_clust(index)) {
if (!index->is_primary()) {
ret = DB_CORRUPTION;
});

View File

@@ -4784,8 +4784,7 @@ new_clustered_failed:
= dict_table_get_first_index(user_table);
index != NULL;
index = dict_table_get_next_index(index)) {
if (!index->to_be_dropped
&& dict_index_is_corrupted(index)) {
if (!index->to_be_dropped && index->is_corrupted()) {
my_error(ER_CHECK_NO_SUCH_TABLE, MYF(0));
goto error_handled;
}
@@ -4795,8 +4794,7 @@ new_clustered_failed:
= dict_table_get_first_index(user_table);
index != NULL;
index = dict_table_get_next_index(index)) {
if (!index->to_be_dropped
&& dict_index_is_corrupted(index)) {
if (!index->to_be_dropped && index->is_corrupted()) {
my_error(ER_CHECK_NO_SUCH_TABLE, MYF(0));
goto error_handled;
}
@@ -5597,8 +5595,7 @@ ha_innobase::prepare_inplace_alter_table(
if (indexed_table->corrupted
|| dict_table_get_first_index(indexed_table) == NULL
|| dict_index_is_corrupted(
dict_table_get_first_index(indexed_table))) {
|| dict_table_get_first_index(indexed_table)->is_corrupted()) {
/* The clustered index is corrupted. */
my_error(ER_CHECK_NO_SUCH_TABLE, MYF(0));
DBUG_RETURN(true);
@@ -5885,7 +5882,7 @@ found_fk:
" with name %s", key->name);
} else {
ut_ad(!index->to_be_dropped);
if (!dict_index_is_clust(index)) {
if (!index->is_primary()) {
drop_index[n_drop_index++] = index;
} else {
drop_primary = index;
@@ -5986,7 +5983,7 @@ check_if_can_drop_indexes:
for (dict_index_t* index = dict_table_get_first_index(indexed_table);
index != NULL; index = dict_table_get_next_index(index)) {
if (!index->to_be_dropped && dict_index_is_corrupted(index)) {
if (!index->to_be_dropped && index->is_corrupted()) {
my_error(ER_INDEX_CORRUPT, MYF(0), index->name());
goto err_exit;
}
@@ -7717,7 +7714,7 @@ commit_try_rebuild(
DBUG_ASSERT(dict_index_get_online_status(index)
== ONLINE_INDEX_COMPLETE);
DBUG_ASSERT(index->is_committed());
if (dict_index_is_corrupted(index)) {
if (index->is_corrupted()) {
my_error(ER_INDEX_CORRUPT, MYF(0), index->name());
DBUG_RETURN(true);
}
@@ -7966,7 +7963,7 @@ commit_try_norebuild(
DBUG_ASSERT(dict_index_get_online_status(index)
== ONLINE_INDEX_COMPLETE);
DBUG_ASSERT(!index->is_committed());
if (dict_index_is_corrupted(index)) {
if (index->is_corrupted()) {
/* Report a duplicate key
error for the index that was
flagged corrupted, most likely

View File

@@ -702,7 +702,7 @@ dict_table_get_next_index(
/* Skip corrupted index */
#define dict_table_skip_corrupt_index(index) \
while (index && dict_index_is_corrupted(index)) { \
while (index && index->is_corrupted()) { \
index = dict_table_get_next_index(index); \
}
@@ -1835,16 +1835,6 @@ dict_table_is_corrupted(
const dict_table_t* table) /*!< in: table */
MY_ATTRIBUTE((nonnull, warn_unused_result));
/**********************************************************************//**
Check whether the index is corrupted.
@return nonzero for corrupted index, zero for valid indexes */
UNIV_INLINE
ulint
dict_index_is_corrupted(
/*====================*/
const dict_index_t* index) /*!< in: index */
MY_ATTRIBUTE((nonnull, warn_unused_result));
/**********************************************************************//**
Flags an index and table corrupted both in the data dictionary cache
and in the system table SYS_INDEXES. */

View File

@@ -1486,21 +1486,6 @@ dict_table_is_corrupted(
return(table->corrupted);
}
/********************************************************************//**
Check whether the index is corrupted.
@return nonzero for corrupted index, zero for valid indexes */
UNIV_INLINE
ulint
dict_index_is_corrupted(
/*====================*/
const dict_index_t* index) /*!< in: index */
{
ut_ad(index->magic_n == DICT_INDEX_MAGIC_N);
return((index->type & DICT_CORRUPT)
|| (index->table && index->table->corrupted));
}
/********************************************************************//**
Check if the tablespace for the table has been discarded.
@return true if the tablespace has been discarded. */

View File

@@ -980,6 +980,9 @@ struct dict_index_t{
{
return DICT_CLUSTERED == (type & (DICT_CLUSTERED | DICT_IBUF));
}
/** @return whether the index is corrupted */
inline bool is_corrupted() const;
};
/** The status of online index creation */
@@ -1724,6 +1727,13 @@ inline bool dict_index_t::is_readable() const
return(UNIV_LIKELY(!table->file_unreadable));
}
inline bool dict_index_t::is_corrupted() const
{
return UNIV_UNLIKELY(online_status >= ONLINE_INDEX_ABORTED
|| (type & DICT_CORRUPT)
|| (table && table->corrupted));
}
/*******************************************************************//**
Initialise the table lock list. */
void

View File

@@ -3686,8 +3686,7 @@ row_ins(
node->index = NULL; node->entry = NULL; break;);
/* Skip corrupted secondary index and its entry */
while (node->index && dict_index_is_corrupted(node->index)) {
while (node->index && node->index->is_corrupted()) {
node->index = dict_table_get_next_index(node->index);
node->entry = UT_LIST_GET_NEXT(tuple_list, node->entry);
}

View File

@@ -293,7 +293,7 @@ row_log_online_op(
ut_ad(rw_lock_own(dict_index_get_lock(index), RW_LOCK_S)
|| rw_lock_own(dict_index_get_lock(index), RW_LOCK_X));
if (dict_index_is_corrupted(index)) {
if (index->is_corrupted()) {
return;
}
@@ -613,8 +613,8 @@ row_log_table_delete(
&index->lock,
RW_LOCK_FLAG_S | RW_LOCK_FLAG_X | RW_LOCK_FLAG_SX));
if (dict_index_is_corrupted(index)
|| !dict_index_is_online_ddl(index)
if (index->online_status != ONLINE_INDEX_CREATION
|| (index->type & DICT_CORRUPT) || index->table->corrupted
|| index->online_log->error != DB_SUCCESS) {
return;
}
@@ -922,8 +922,8 @@ row_log_table_low(
ut_ad(!old_pk || !insert);
ut_ad(!old_pk || old_pk->n_v_fields == 0);
if (dict_index_is_corrupted(index)
|| !dict_index_is_online_ddl(index)
if (index->online_status != ONLINE_INDEX_CREATION
|| (index->type & DICT_CORRUPT) || index->table->corrupted
|| index->online_log->error != DB_SUCCESS) {
return;
}
@@ -2672,7 +2672,7 @@ next_block:
goto interrupted;
}
if (dict_index_is_corrupted(index)) {
if (index->is_corrupted()) {
error = DB_INDEX_CORRUPT;
goto func_exit;
}
@@ -3167,7 +3167,7 @@ row_log_apply_op_low(
ut_ad(rw_lock_own(dict_index_get_lock(index), RW_LOCK_X)
== has_index_lock);
ut_ad(!dict_index_is_corrupted(index));
ut_ad(!index->is_corrupted());
ut_ad(trx_id != 0 || op == ROW_OP_DELETE);
DBUG_LOG("ib_create_index",
@@ -3411,7 +3411,7 @@ row_log_apply_op(
ut_ad(rw_lock_own(dict_index_get_lock(index), RW_LOCK_X)
== has_index_lock);
if (dict_index_is_corrupted(index)) {
if (index->is_corrupted()) {
*error = DB_INDEX_CORRUPT;
return(NULL);
}
@@ -3548,7 +3548,7 @@ next_block:
goto func_exit;
}
if (dict_index_is_corrupted(index)) {
if (index->is_corrupted()) {
error = DB_INDEX_CORRUPT;
goto func_exit;
}

View File

@@ -4443,13 +4443,13 @@ row_merge_is_index_usable(
const trx_t* trx, /*!< in: transaction */
const dict_index_t* index) /*!< in: index to check */
{
if (!dict_index_is_clust(index)
if (!index->is_primary()
&& dict_index_is_online_ddl(index)) {
/* Indexes that are being created are not useable. */
return(false);
}
return(!dict_index_is_corrupted(index)
return(!index->is_corrupted()
&& (dict_table_is_temporary(index->table)
|| index->trx_id == 0
|| !MVCC::is_view_active(trx->read_view)

View File

@@ -888,8 +888,7 @@ try_again:
clust_index = dict_table_get_first_index(node->table);
if (clust_index == NULL
|| dict_index_is_corrupted(clust_index)) {
if (!clust_index || clust_index->is_corrupted()) {
/* The table was corrupt in the data dictionary.
dict_set_corrupted() works on an index, and
we do not have an index to call it with. */

View File

@@ -4253,18 +4253,14 @@ row_search_mvcc(
ut_ad(!sync_check_iterate(sync_check()));
if (dict_table_is_discarded(prebuilt->table)) {
DBUG_RETURN(DB_TABLESPACE_DELETED);
} else if (!prebuilt->table->is_readable()) {
DBUG_RETURN(fil_space_get(prebuilt->table->space)
? DB_DECRYPTION_FAILED
: DB_TABLESPACE_NOT_FOUND);
} else if (!prebuilt->index_usable) {
DBUG_RETURN(DB_MISSING_HISTORY);
} else if (dict_index_is_corrupted(prebuilt->index)) {
} else if (prebuilt->index->is_corrupted()) {
DBUG_RETURN(DB_CORRUPTION);
}