diff --git a/mysql-test/suite/innodb/r/alter_crash.result b/mysql-test/suite/innodb/r/alter_crash.result index c44b3e067c6c5..c37bf397b7ae6 100644 --- a/mysql-test/suite/innodb/r/alter_crash.result +++ b/mysql-test/suite/innodb/r/alter_crash.result @@ -268,3 +268,10 @@ CHECK TABLE t1 EXTENDED; Table Op Msg_type Msg_text test.t1 check status OK DROP TABLE t1; +# +# MDEV-38993 Assertion `trx->undo_no == 1' fails upon ALTER IGNORE +# +CREATE TABLE t (a INT) ENGINE=InnoDB; +INSERT INTO t VALUES (1),(2),(1),(2); +ALTER IGNORE TABLE t ADD UNIQUE(a); +DROP TABLE t; diff --git a/mysql-test/suite/innodb/t/alter_crash.test b/mysql-test/suite/innodb/t/alter_crash.test index c185ccb99a317..6f2debb11ed1f 100644 --- a/mysql-test/suite/innodb/t/alter_crash.test +++ b/mysql-test/suite/innodb/t/alter_crash.test @@ -267,3 +267,11 @@ INSERT INTO t1 VALUES(1), (1); ALTER IGNORE TABLE t1 ADD UNIQUE(f1); CHECK TABLE t1 EXTENDED; DROP TABLE t1; + +--echo # +--echo # MDEV-38993 Assertion `trx->undo_no == 1' fails upon ALTER IGNORE +--echo # +CREATE TABLE t (a INT) ENGINE=InnoDB; +INSERT INTO t VALUES (1),(2),(1),(2); +ALTER IGNORE TABLE t ADD UNIQUE(a); +DROP TABLE t; diff --git a/storage/innobase/trx/trx0rec.cc b/storage/innobase/trx/trx0rec.cc index f7c89c4b30538..276d4d5d55777 100644 --- a/storage/innobase/trx/trx0rec.cc +++ b/storage/innobase/trx/trx0rec.cc @@ -1807,6 +1807,30 @@ static bool trx_has_lock_x(const trx_t &trx, dict_table_t& table) return false; } +/** For ALTER TABLE...IGNORE ALGORITHM=COPY, rewind the undo log +to maintain only the latest insert undo record. This allows easy +rollback of the last inserted row on duplicate key errors. +@param mtr mini-transaction +@param undo_block undo log page +@param table table being altered +@param trx transaction +@param undo insert undo log +@return mod_tables entry after inserting the table */ +static ATTRIBUTE_COLD ATTRIBUTE_NOINLINE +std::pair +trx_undo_rewrite_ignore(mtr_t *mtr, buf_block_t *undo_block, + dict_table_t *table, trx_t *trx, trx_undo_t *undo) +{ + mtr->write<2>(*undo_block, undo_block->page.frame + TRX_UNDO_PAGE_HDR + + TRX_UNDO_PAGE_FREE, undo->old_offset); + ut_ad(trx->undo_no == 1); + undo->top_offset= undo->old_offset; + undo->top_undo_no= 0; + trx->undo_no= 0; + trx->mod_tables.clear(); + return trx->mod_tables.emplace(table, 0); +} + /***********************************************************************//** Writes information to an undo log about an insert, update, or a delete marking of a clustered index record. This information is used in a rollback of the @@ -1912,32 +1936,18 @@ trx_undo_report_row_operation( ut_ad(!trx->read_only); ut_ad(trx->id); pundo = &trx->rsegs.m_redo.undo; - const bool empty{!*pundo}; + const bool clear_ignore = *pundo && trx->undo_no + && (*pundo)->old_offset <= (*pundo)->top_offset + && index->table->skip_alter_undo + == dict_table_t::IGNORE_UNDO; + rseg = trx->rsegs.m_redo.rseg; undo_block = trx_undo_assign_low(trx, rseg, pundo, &mtr, &err); - /* For ALTER IGNORE, implement undo log rewriting - to maintain only the latest insert undo record. - This allows easy rollback of the last inserted row - on duplicate key errors. Before writing a new undo - record, rewind the undo log to the previous record - position, effectively discarding all intermediate - undo records and keeping only the most recent one. */ - if (!empty && (*pundo)->old_offset <= (*pundo)->top_offset && - index->table->skip_alter_undo == - dict_table_t::IGNORE_UNDO) { - + if (clear_ignore) { ut_ad(!rec); - ut_ad(trx->undo_no == 1); - (*pundo)->top_offset = (*pundo)->old_offset; - (*pundo)->top_undo_no = 0; - trx->undo_no = 0; - trx->mod_tables.clear(); - m = trx->mod_tables.emplace(index->table, 0); - mtr.write<2>( - *undo_block, - undo_block->page.frame + TRX_UNDO_PAGE_HDR + - TRX_UNDO_PAGE_FREE, (*pundo)->old_offset); + m = trx_undo_rewrite_ignore(&mtr, undo_block, + index->table, trx, *pundo); } }