mariadb/mysql-test/suite/innodb/t/alter_candidate_key.test
Thirunarayanan Balathandayuthapani 4b80c11f52 MDEV-15250 UPSERT during ALTER TABLE results in 'Duplicate entry' error for alter
- InnoDB DDL results in `Duplicate entry' if concurrent DML throws
duplicate key error. The following scenario explains the problem

connection con1:
  ALTER TABLE t1 FORCE;

connection con2:
  INSERT INTO t1(pk, uk) VALUES (2, 2), (3, 2);

In connection con2, InnoDB throws the 'DUPLICATE KEY' error because
of unique index. Alter operation will throw the error when applying
the concurrent DML log.

- Inserting the duplicate key for unique index logs the insert
operation for online ALTER TABLE. When insertion fails,
transaction does rollback and it leads to logging of
delete operation for online ALTER TABLE.
While applying the insert log entries, alter operation
encounters 'DUPLICATE KEY' error.

- To avoid the above fake duplicate scenario, InnoDB should
not write any log for online ALTER TABLE before DML transaction
commit.

- User thread which does DML can apply the online log if
InnoDB ran out of online log and index is marked as completed.
Set online log error if apply phase encountered any error.
It can also clear all other indexes log, marks the newly
added indexes as corrupted.

- Removed the old online code which was a part of DML operations

commit_inplace_alter_table() : Does apply the online log
for the last batch of secondary index log and does frees
the log for the completed index.

trx_t::apply_online_log: Set to true while writing the undo
log if the modified table has active DDL

trx_t::apply_log(): Apply the DML changes to online DDL tables

dict_table_t::is_active_ddl(): Returns true if the table
has an active DDL

dict_index_t::online_log_make_dummy(): Assign dummy value
for clustered index online log to indicate the secondary
indexes are being rebuild.

dict_index_t::online_log_is_dummy(): Check whether the online
log has dummy value

ha_innobase_inplace_ctx::log_failure(): Handle the apply log
failure for online DDL transaction

row_log_mark_other_online_index_abort(): Clear out all other
online index log after encountering the error during
row_log_apply()

row_log_get_error(): Get the error happened during row_log_apply()

row_log_online_op(): Does apply the online log if index is
completed and ran out of memory. Returns false if apply log fails

UndorecApplier: Introduced a class to maintain the undo log
record, latched undo buffer page, parse the undo log record,
maintain the undo record type, info bits and update vector

UndorecApplier::get_old_rec(): Get the correct version of the
clustered index record that was modified by the current undo
log record

UndorecApplier::clear_undo_rec(): Clear the undo log related
information after applying the undo log record

UndorecApplier::log_update(): Handle the update, delete undo
log and apply it on online indexes

UndorecApplier::log_insert(): Handle the insert undo log
and apply it on online indexes

UndorecApplier::is_same(): Check whether the given roll pointer
is generated by the current undo log record information

trx_t::rollback_low(): Set apply_online_log for the transaction
after partially rollbacked transaction has any active DDL

prepare_inplace_alter_table_dict(): After allocating the online
log, InnoDB does create fulltext common tables. Fulltext index
doesn't allow the index to be online. So removed the dead
code of online log removal

Thanks to Marko Mäkelä for providing the initial prototype and
Matthias Leich for testing the issue patiently.
2022-04-25 18:52:19 +05:30

74 lines
2.1 KiB
Text

--source include/have_innodb.inc
--source include/have_debug.inc
--source include/have_debug_sync.inc
CREATE TABLE t1 (f1 INT NOT NULL, f2 INT NOT NULL,
UNIQUE KEY uidx2(f1,f2),
UNIQUE KEY uidx1(f2)) ENGINE=InnoDB;
INSERT INTO t1 VALUES(1, 1);
SHOW CREATE TABLE t1;
SET DEBUG_SYNC = 'innodb_inplace_alter_table_enter
SIGNAL conc_dml WAIT_FOR go_ahead';
--send ALTER TABLE t1 CHANGE COLUMN f1 f11 INT, ALGORITHM=INPLACE
connect (con1,localhost,root,,);
SET DEBUG_SYNC = 'now WAIT_FOR conc_dml';
DELETE FROM t1;
SET DEBUG_SYNC = 'now SIGNAL go_ahead';
connection default;
reap;
SHOW CREATE TABLE t1;
CHECK TABLE t1;
DROP TABLE t1;
CREATE TABLE t1(f1 INT, f2 INT,
PRIMARY KEY(f1, f2),
UNIQUE INDEX uidx2 (f1, f2),
UNIQUE INDEX uidx1 (f2))ENGINE=InnoDB;
INSERT INTO t1 VALUES(2, 2);
ALTER TABLE t1 DROP PRIMARY KEY;
SHOW CREATE TABLE t1;
SET DEBUG_SYNC = 'innodb_inplace_alter_table_enter
SIGNAL conc_dml WAIT_FOR go_ahead';
--send ALTER TABLE t1 CHANGE COLUMN f1 f11 INT, ALGORITHM=INPLACE
connection con1;
SET DEBUG_SYNC = 'now WAIT_FOR conc_dml';
--error ER_DUP_ENTRY
INSERT INTO t1 VALUES(1, 1), (1, 1);
SET DEBUG_SYNC = 'now SIGNAL go_ahead';
connection default;
reap;
SHOW CREATE TABLE t1;
CHECK TABLE t1;
DROP TABLE t1;
SET SQL_MODE= strict_trans_tables;
CREATE TABLE t1(a INT UNIQUE) ENGINE=InnoDB;
INSERT INTO t1 VALUES(1);
SET DEBUG_SYNC='row_log_table_apply1_before SIGNAL dml WAIT_FOR dml_done';
--send ALTER TABLE t1 MODIFY COLUMN a INT NOT NULL
connection con1;
SET DEBUG_SYNC='now WAIT_FOR dml';
BEGIN;
INSERT INTO t1 SET a=NULL;
COMMIT;
set DEBUG_SYNC='now SIGNAL dml_done';
connection default;
--error ER_INVALID_USE_OF_NULL
reap;
DROP TABLE t1;
disconnect con1;
SET DEBUG_SYNC="RESET";
SET SQL_MODE=DEFAULT;
CREATE TABLE t1(f1 INT NOT NULL, f2 INT NOT NULL, PRIMARY KEY(f1, f2),
UNIQUE KEY(f2))ENGINE=InnoDB;
ALTER TABLE t1 DROP PRIMARY KEY;
SHOW CREATE TABLE t1;
DROP TABLE t1;
CREATE TABLE t1(f1 INT NOT NULL, f2 INT NOT NULL,
UNIQUE KEY(f2), UNIQUE KEY(f2))ENGINE=InnoDB;
SHOW CREATE TABLE t1;
ALTER TABLE t1 DROP INDEX f2, ALGORITHM=INPLACE;
SHOW CREATE TABLE t1;
DROP TABLE t1;