Commit 43120009 authored by Marko Mäkelä's avatar Marko Mäkelä

MDEV-22867 Assertion instant.n_core_fields == n_core_fields failed

This is a race condition where a table on which a 10.3-style
instant ADD COLUMN is emptied during the execution of
ALTER TABLE ... DROP COLUMN ..., DROP INDEX ..., ALGORITHM=NOCOPY.

In commit 2c4844c9 the
function instant_metadata_lock() would prevent this race condition.
But, it would also hold a page latch on the leftmost leaf page of
clustered index for the duration of a possible DROP INDEX operation.

The race could be fixed by restoring the function
instant_metadata_lock() that was removed in
commit ea37b144
but it would be more future-proof to prevent the
dict_index_t::clear_instant_add() call from being issued at all.

We at some point support DROP COLUMN ..., ADD INDEX ..., ALGORITHM=NOCOPY
and that would spend a non-trivial amount of
execution time in ha_innobase::inplace_alter(),
making a server hang possible. Currently this is not supported
and our added test case will notice when the support is introduced.

dict_index_t::must_avoid_clear_instant_add(): Determine if
a call to clear_instant_add() must be avoided.

btr_discard_only_page_on_level(): Preserve the metadata record
if must_avoid_clear_instant_add() holds.

btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Do not remove the metadata record even if the table becomes empty
but must_avoid_clear_instant_add() holds.

btr_pcur_store_position(): Relax a debug assertion.

This is joint work with Thirunarayanan Balathandayuthapani.
parent d7d80689
...@@ -370,3 +370,36 @@ b ...@@ -370,3 +370,36 @@ b
SET DEBUG_SYNC='RESET'; SET DEBUG_SYNC='RESET';
disconnect con2; disconnect con2;
DROP TABLE t1; DROP TABLE t1;
#
# MDEV-22867 Assertion instant.n_core_fields == n_core_fields
# in dict_index_t::instant_add_field
#
CREATE TABLE t1 (a INT PRIMARY KEY, b INT UNIQUE) ENGINE=InnoDB;
INSERT INTO t1 SET a=1;
connect prevent_purge,localhost,root,,;
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection default;
ALTER TABLE t1 ADD COLUMN c INT;
DELETE FROM t1;
connect con2,localhost,root,,test;
ALTER TABLE t1 DROP b, ADD INDEX(c), ALGORITHM=NOCOPY;
ERROR 0A000: ALGORITHM=NOCOPY is not supported for this operation. Try ALGORITHM=INPLACE
SET DEBUG_SYNC='innodb_inplace_alter_table_enter SIGNAL ddl WAIT_FOR emptied';
ALTER TABLE t1 DROP b;
connection default;
SET DEBUG_SYNC='now WAIT_FOR ddl';
BEGIN;
INSERT INTO t1 SET a=1;
connection prevent_purge;
COMMIT;
disconnect prevent_purge;
connection default;
ROLLBACK;
SELECT * FROM t1;
a b c
SET DEBUG_SYNC='now SIGNAL emptied';
connection con2;
disconnect con2;
connection default;
DROP TABLE t1;
SET DEBUG_SYNC=RESET;
...@@ -414,3 +414,46 @@ SELECT * FROM t1; ...@@ -414,3 +414,46 @@ SELECT * FROM t1;
SET DEBUG_SYNC='RESET'; SET DEBUG_SYNC='RESET';
--disconnect con2 --disconnect con2
DROP TABLE t1; DROP TABLE t1;
--echo #
--echo # MDEV-22867 Assertion instant.n_core_fields == n_core_fields
--echo # in dict_index_t::instant_add_field
--echo #
CREATE TABLE t1 (a INT PRIMARY KEY, b INT UNIQUE) ENGINE=InnoDB;
INSERT INTO t1 SET a=1;
connect (prevent_purge,localhost,root,,);
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection default;
ALTER TABLE t1 ADD COLUMN c INT;
DELETE FROM t1;
connect (con2,localhost,root,,test);
# FIXME: If this is implemented then also i->online_log must be checked in
# dict_index_t::must_avoid_clear_instant_add(). See the comment there.
--error ER_ALTER_OPERATION_NOT_SUPPORTED
ALTER TABLE t1 DROP b, ADD INDEX(c), ALGORITHM=NOCOPY;
SET DEBUG_SYNC='innodb_inplace_alter_table_enter SIGNAL ddl WAIT_FOR emptied';
send ALTER TABLE t1 DROP b;
connection default;
SET DEBUG_SYNC='now WAIT_FOR ddl';
BEGIN;
INSERT INTO t1 SET a=1;
connection prevent_purge;
COMMIT;
disconnect prevent_purge;
connection default;
ROLLBACK;
SELECT * FROM t1;
SET DEBUG_SYNC='now SIGNAL emptied';
connection con2;
reap;
disconnect con2;
connection default;
DROP TABLE t1;
SET DEBUG_SYNC=RESET;
...@@ -3944,11 +3944,14 @@ btr_discard_only_page_on_level( ...@@ -3944,11 +3944,14 @@ btr_discard_only_page_on_level(
mem_heap_t* heap = NULL; mem_heap_t* heap = NULL;
const rec_t* rec = NULL; const rec_t* rec = NULL;
rec_offs* offsets = NULL; rec_offs* offsets = NULL;
if (index->table->instant) {
if (index->table->instant || index->must_avoid_clear_instant_add()) {
const rec_t* r = page_rec_get_next(page_get_infimum_rec( const rec_t* r = page_rec_get_next(page_get_infimum_rec(
block->frame)); block->frame));
ut_ad(rec_is_metadata(r, *index) == index->is_instant()); ut_ad(rec_is_metadata(r, *index) == index->is_instant());
if (rec_is_alter_metadata(r, *index)) { if (!rec_is_metadata(r, *index)) {
} else if (!index->table->instant
|| rec_is_alter_metadata(r, *index)) {
heap = mem_heap_create(srv_page_size); heap = mem_heap_create(srv_page_size);
offsets = rec_get_offsets(r, index, NULL, true, offsets = rec_get_offsets(r, index, NULL, true,
ULINT_UNDEFINED, &heap); ULINT_UNDEFINED, &heap);
......
...@@ -5450,7 +5450,8 @@ btr_cur_optimistic_delete_func( ...@@ -5450,7 +5450,8 @@ btr_cur_optimistic_delete_func(
if (UNIV_UNLIKELY(block->page.id().page_no() == cursor->index->page if (UNIV_UNLIKELY(block->page.id().page_no() == cursor->index->page
&& page_get_n_recs(block->frame) == 1 && page_get_n_recs(block->frame) == 1
+ (cursor->index->is_instant() + (cursor->index->is_instant()
&& !rec_is_metadata(rec, *cursor->index)))) { && !rec_is_metadata(rec, *cursor->index))
&& !cursor->index->must_avoid_clear_instant_add())) {
/* The whole index (and table) becomes logically empty. /* The whole index (and table) becomes logically empty.
Empty the whole page. That is, if we are deleting the Empty the whole page. That is, if we are deleting the
only user record, also delete the metadata record only user record, also delete the metadata record
...@@ -5687,7 +5688,8 @@ btr_cur_pessimistic_delete( ...@@ -5687,7 +5688,8 @@ btr_cur_pessimistic_delete(
goto discard_page; goto discard_page;
} }
} else if (page_get_n_recs(page) == 1 } else if (page_get_n_recs(page) == 1
+ (index->is_instant() && !is_metadata)) { + (index->is_instant() && !is_metadata)
&& !index->must_avoid_clear_instant_add()) {
/* The whole index (and table) becomes logically empty. /* The whole index (and table) becomes logically empty.
Empty the whole page. That is, if we are deleting the Empty the whole page. That is, if we are deleting the
only user record, also delete the metadata record only user record, also delete the metadata record
......
...@@ -149,7 +149,13 @@ btr_pcur_store_position( ...@@ -149,7 +149,13 @@ btr_pcur_store_position(
ut_ad(!page_rec_is_infimum(rec)); ut_ad(!page_rec_is_infimum(rec));
if (UNIV_UNLIKELY(rec_is_metadata(rec, *index))) { if (UNIV_UNLIKELY(rec_is_metadata(rec, *index))) {
#if 0 /* MDEV-22867 had to relax this */
/* If the table is emptied during an ALGORITHM=NOCOPY
DROP COLUMN ... that is not ALGORITHM=INSTANT,
then we must preserve any instant ADD metadata. */
ut_ad(index->table->instant); ut_ad(index->table->instant);
#endif
ut_ad(index->is_instant());
ut_ad(page_get_n_recs(block->frame) == 1); ut_ad(page_get_n_recs(block->frame) == 1);
ut_ad(page_is_leaf(block->frame)); ut_ad(page_is_leaf(block->frame));
ut_ad(!page_has_siblings(block->frame)); ut_ad(!page_has_siblings(block->frame));
......
...@@ -1306,6 +1306,16 @@ struct dict_index_t { ...@@ -1306,6 +1306,16 @@ struct dict_index_t {
void set_freed() { ut_ad(!freed()); page= 1; } void set_freed() { ut_ad(!freed()); page= 1; }
#endif /* BTR_CUR_HASH_ADAPT */ #endif /* BTR_CUR_HASH_ADAPT */
/** @return whether it is forbidden to invoke clear_instant_add() */
bool must_avoid_clear_instant_add() const
{
if (is_instant())
for (auto i= this; (i= UT_LIST_GET_NEXT(indexes, i)) != nullptr; )
if (i->to_be_dropped /* || i->online_log*/)
return true;
return false;
}
/** This ad-hoc class is used by record_size_info only. */ /** This ad-hoc class is used by record_size_info only. */
class record_size_info_t { class record_size_info_t {
public: public:
......
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