Browse Source

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().
bb-10.2-mariarocks
Marko Mäkelä 8 years ago
parent
commit
e44ca6cc9c
  1. 27
      mysql-test/suite/innodb/r/alter_partitioned_debug.result
  2. 34
      mysql-test/suite/innodb/t/alter_partitioned_debug.test
  3. 4
      storage/innobase/dict/dict0defrag_bg.cc
  4. 16
      storage/innobase/dict/dict0load.cc
  5. 7
      storage/innobase/dict/dict0stats.cc
  6. 2
      storage/innobase/fts/fts0fts.cc
  7. 22
      storage/innobase/handler/ha_innodb.cc
  8. 17
      storage/innobase/handler/handler0alter.cc
  9. 12
      storage/innobase/include/dict0dict.h
  10. 15
      storage/innobase/include/dict0dict.ic
  11. 10
      storage/innobase/include/dict0mem.h
  12. 3
      storage/innobase/row/row0ins.cc
  13. 18
      storage/innobase/row/row0log.cc
  14. 4
      storage/innobase/row/row0merge.cc
  15. 3
      storage/innobase/row/row0purge.cc
  16. 6
      storage/innobase/row/row0sel.cc

27
mysql-test/suite/innodb/r/alter_partitioned_debug.result

@ -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;

34
mysql-test/suite/innodb/t/alter_partitioned_debug.test

@ -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;

4
storage/innobase/dict/dict0defrag_bg.cc

@ -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);
}

16
storage/innobase/dict/dict0load.cc

@ -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 */

7
storage/innobase/dict/dict0stats.cc

@ -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 */

2
storage/innobase/fts/fts0fts.cc

@ -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();
}
}

22
storage/innobase/handler/ha_innodb.cc

@ -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;
});

17
storage/innobase/handler/handler0alter.cc

@ -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

12
storage/innobase/include/dict0dict.h

@ -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. */

15
storage/innobase/include/dict0dict.ic

@ -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. */

10
storage/innobase/include/dict0mem.h

@ -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

3
storage/innobase/row/row0ins.cc

@ -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);
}

18
storage/innobase/row/row0log.cc

@ -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;
}

4
storage/innobase/row/row0merge.cc

@ -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)

3
storage/innobase/row/row0purge.cc

@ -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. */

6
storage/innobase/row/row0sel.cc

@ -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);
}

Loading…
Cancel
Save