mirror of
https://github.com/MariaDB/server.git
synced 2025-01-18 04:53:01 +01:00
4b80c11f52
- 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.
229 lines
6.9 KiB
Text
229 lines
6.9 KiB
Text
# Crash-safe InnoDB ALTER operations
|
|
|
|
--source include/not_valgrind.inc
|
|
--source include/not_embedded.inc
|
|
--source include/have_innodb.inc
|
|
--source include/have_debug.inc
|
|
--source include/have_debug_sync.inc
|
|
--source include/not_crashrep.inc
|
|
|
|
--disable_query_log
|
|
call mtr.add_suppression('InnoDB: Flagged corruption of `c[23]`');
|
|
call mtr.add_suppression('InnoDB: Index `c[23]` .*is corrupted');
|
|
--enable_query_log
|
|
|
|
--echo #
|
|
--echo # Bug#20015132 ALTER TABLE FAILS TO CHECK IF TABLE IS CORRUPTED
|
|
--echo #
|
|
|
|
CREATE TABLE t1(c1 INT PRIMARY KEY, c2 CHAR(1), c3 INT UNSIGNED) ENGINE=InnoDB;
|
|
SET @saved_debug_dbug = @@SESSION.debug_dbug;
|
|
SET DEBUG_DBUG='+d,create_index_metadata_fail';
|
|
--error ER_RECORD_FILE_FULL
|
|
ALTER TABLE t1 ADD INDEX (c2), ADD INDEX (c3);
|
|
|
|
SET DEBUG_DBUG='+d,ib_create_table_fail_too_many_trx';
|
|
--error ER_TOO_MANY_CONCURRENT_TRXS
|
|
ALTER TABLE t1 ADD INDEX (c2), ADD INDEX (c3);
|
|
|
|
SET DEBUG_DBUG=@saved_debug_dbug;
|
|
ALTER TABLE t1 ADD INDEX (c2), ADD INDEX (c3);
|
|
# Flag the secondary indexes corrupted.
|
|
SET DEBUG_DBUG='+d,dict_set_index_corrupted';
|
|
CHECK TABLE t1;
|
|
|
|
# Ensure that the corruption is permanent.
|
|
--source include/restart_mysqld.inc
|
|
CHECK TABLE t1;
|
|
ALTER TABLE t1 DROP INDEX c2;
|
|
CHECK TABLE t1;
|
|
# We refuse an ALTER TABLE that would modify the InnoDB data dictionary
|
|
# while leaving some of the table corrupted.
|
|
--error ER_INDEX_CORRUPT
|
|
ALTER TABLE t1 ADD INDEX (c2,c3);
|
|
# This will rebuild the table, uncorrupting all secondary indexes.
|
|
ALTER TABLE t1 CHANGE c3 c3 INT NOT NULL;
|
|
CHECK TABLE t1;
|
|
ALTER TABLE t1 ADD INDEX (c2,c3);
|
|
DROP TABLE t1;
|
|
|
|
let $MYSQLD_DATADIR= `select @@datadir`;
|
|
let datadir= `select @@datadir`;
|
|
|
|
# These are from include/shutdown_mysqld.inc and allow to call start_mysqld.inc
|
|
--let $_server_id= `SELECT @@server_id`
|
|
--let $_expect_file_name= $MYSQLTEST_VARDIR/tmp/mysqld.$_server_id.expect
|
|
|
|
--echo #
|
|
--echo # Bug #14669848 CRASH DURING ALTER MAKES ORIGINAL TABLE INACCESSIBLE
|
|
--echo #
|
|
--echo # -- Scenario 1:
|
|
--echo # Crash the server in ha_innobase::commit_inplace_alter_table()
|
|
--echo # just after committing the dictionary changes.
|
|
|
|
CREATE TABLE t1 (f1 INT NOT NULL, f2 INT NOT NULL) ENGINE=innodb;
|
|
INSERT INTO t1 VALUES (1,2),(3,4);
|
|
SET DEBUG_DBUG='+d,innodb_alter_commit_crash_after_commit';
|
|
|
|
let $orig_table_id = `SELECT table_id
|
|
FROM information_schema.innodb_sys_tables
|
|
WHERE name = 'test/t1'`;
|
|
|
|
# Write file to make mysql-test-run.pl expect crash
|
|
--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
|
|
|
|
--error 2013
|
|
ALTER TABLE t1 ADD PRIMARY KEY (f2, f1);
|
|
|
|
--echo # Restart mysqld after the crash and reconnect.
|
|
--source include/start_mysqld.inc
|
|
|
|
--replace_result $orig_table_id ID
|
|
eval SELECT * FROM information_schema.innodb_sys_tables
|
|
WHERE table_id = $orig_table_id;
|
|
|
|
--echo # Files in datadir after manual recovery.
|
|
--list_files $MYSQLD_DATADIR/test
|
|
|
|
SHOW TABLES;
|
|
SHOW CREATE TABLE t1;
|
|
INSERT INTO t1 VALUES (5,6),(7,8);
|
|
SELECT * FROM t1;
|
|
DROP TABLE t1;
|
|
|
|
CREATE TABLE t1 (f1 INT NOT NULL, f2 INT NOT NULL) ENGINE=InnoDB;
|
|
ALTER TABLE t1 ADD PRIMARY KEY (f2, f1);
|
|
DROP TABLE t1;
|
|
|
|
--echo # -- Scenario 2:
|
|
--echo # Crash the server in ha_innobase::commit_inplace_alter_table()
|
|
--echo # just before committing the dictionary changes, but after
|
|
--echo # writing the MLOG_FILE_RENAME records. As the mini-transaction
|
|
--echo # is not committed, the renames will not be replayed.
|
|
|
|
CREATE TABLE t2 (f1 int not null, f2 int not null) ENGINE=InnoDB;
|
|
INSERT INTO t2 VALUES (1,2),(3,4);
|
|
SET DEBUG_DBUG='+d,innodb_alter_commit_crash_before_commit';
|
|
|
|
let $orig_table_id = `SELECT table_id
|
|
FROM information_schema.innodb_sys_tables
|
|
WHERE name = 'test/t2'`;
|
|
|
|
# Write file to make mysql-test-run.pl expect crash
|
|
--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
|
|
|
|
--error 2013
|
|
ALTER TABLE t2 ADD PRIMARY KEY (f2, f1);
|
|
|
|
--echo # Startup the server after the crash
|
|
--source include/start_mysqld.inc
|
|
|
|
SELECT * FROM information_schema.innodb_sys_tables
|
|
WHERE name LIKE 'test/#sql-%';
|
|
|
|
SHOW TABLES;
|
|
INSERT INTO t2 VALUES (5,6),(7,8);
|
|
SELECT * from t2;
|
|
SHOW CREATE TABLE t2;
|
|
DROP TABLE t2;
|
|
|
|
CREATE TABLE t2 (f1 INT NOT NULL, f2 INT NOT NULL) ENGINE=InnoDB;
|
|
ALTER TABLE t2 ADD PRIMARY KEY (f2, f1);
|
|
DROP TABLE t2;
|
|
--list_files $MYSQLD_DATADIR/test
|
|
|
|
--echo # -------------------------
|
|
--echo # End of Testing Scenario 2
|
|
--echo # -------------------------
|
|
|
|
--echo #
|
|
--echo # Bug#19330255 WL#7142 - CRASH DURING ALTER TABLE LEADS TO
|
|
--echo # DATA DICTIONARY INCONSISTENCY
|
|
--echo #
|
|
|
|
CREATE TABLE t1(a int PRIMARY KEY, b varchar(255), c int NOT NULL)
|
|
ENGINE=InnoDB;
|
|
INSERT INTO t1 SET a=1,c=2;
|
|
SET DEBUG_DBUG='+d,innodb_alter_commit_crash_after_commit';
|
|
|
|
let $orig_table_id = `select table_id from
|
|
information_schema.innodb_sys_tables where name = 'test/t1'`;
|
|
|
|
# Write file to make mysql-test-run.pl expect crash
|
|
--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
|
|
#
|
|
--error 2013
|
|
ALTER TABLE t1 ADD INDEX (b), CHANGE c d int, ALGORITHM=INPLACE;
|
|
|
|
--echo # Restart mysqld after the crash and reconnect.
|
|
--source include/start_mysqld.inc
|
|
|
|
--replace_result $orig_table_id ID
|
|
eval SELECT * FROM information_schema.innodb_sys_tables
|
|
WHERE table_id = $orig_table_id;
|
|
|
|
--list_files $MYSQLD_DATADIR/test
|
|
|
|
SHOW TABLES;
|
|
SHOW CREATE TABLE t1;
|
|
UPDATE t1 SET d=NULL;
|
|
SELECT * FROM t1;
|
|
DROP TABLE t1;
|
|
|
|
--echo #
|
|
--echo # MDEV-22637 Rollback of insert fails when column reorder happens
|
|
--echo #
|
|
SET @@SQL_MODE = REPLACE(@@SQL_MODE, 'STRICT_TRANS_TABLES', '');
|
|
SET @@SQL_MODE = REPLACE(@@SQL_MODE, 'STRICT_ALL_TABLES', '');
|
|
CREATE TABLE t1(f1 INT NOT NULL, f2 CHAR(100),
|
|
f3 CHAR(100), f4 CHAR(100))ENGINE=InnoDB;
|
|
INSERT INTO t1 VALUES(1, "This is column2", "This is column3",
|
|
"This is column4");
|
|
set DEBUG_SYNC = 'row_log_table_apply1_before SIGNAL scanned WAIT_FOR insert_done';
|
|
--send ALTER TABLE t1 ADD COLUMN f6 int after f3, add primary key(f6, f4(3), f3(3))
|
|
connect(con1,localhost,root,,);
|
|
SET DEBUG_SYNC = 'now WAIT_FOR scanned';
|
|
BEGIN;
|
|
INSERT INTO t1(f1, f2) VALUES(2, "This is column2 value");
|
|
ROLLBACK;
|
|
set DEBUG_SYNC = 'now SIGNAL insert_done';
|
|
|
|
connection default;
|
|
reap;
|
|
SHOW CREATE TABLE t1;
|
|
SELECT COUNT(*) FROM t1;
|
|
disconnect con1;
|
|
DROP TABLE t1;
|
|
SET DEBUG_SYNC = 'RESET';
|
|
SET SQL_MODE=DEFAULT;
|
|
|
|
--echo #
|
|
--echo # MDEV-26936 Recovery crash on rolling back DELETE FROM SYS_INDEXES
|
|
--echo #
|
|
|
|
CREATE TABLE t1(a INT PRIMARY KEY, b INT) ENGINE=InnoDB;
|
|
INSERT INTO t1 VALUES(1,1);
|
|
|
|
connect ddl, localhost, root;
|
|
SET DEBUG_SYNC = 'row_merge_after_scan SIGNAL scanned WAIT_FOR commit';
|
|
SET DEBUG_SYNC = 'before_commit_rollback_inplace SIGNAL c WAIT_FOR ever';
|
|
send ALTER TABLE t1 ADD UNIQUE INDEX(b), ALGORITHM=INPLACE;
|
|
|
|
connection default;
|
|
SET DEBUG_SYNC = 'now WAIT_FOR scanned';
|
|
BEGIN;
|
|
INSERT INTO t1 VALUES(2,1);
|
|
COMMIT;
|
|
SET DEBUG_SYNC = 'now SIGNAL commit';
|
|
SET DEBUG_SYNC = 'now WAIT_FOR c';
|
|
# Make all pending changes durable for recovery.
|
|
SET GLOBAL innodb_fil_make_page_dirty_debug=0;
|
|
|
|
--source include/kill_mysqld.inc
|
|
disconnect ddl;
|
|
--source include/start_mysqld.inc
|
|
|
|
CHECK TABLE t1;
|
|
SHOW CREATE TABLE t1;
|
|
SELECT * FROM t1;
|
|
DROP TABLE t1;
|