1
0
mirror of https://github.com/MariaDB/server.git synced 2025-09-09 18:40:27 +03:00
Files
mariadb/mysql-test/suite/innodb/t/alter_crash.test
Monty 7762ee5dbe MDEV-25180 Atomic ALTER TABLE
MDEV-25604 Atomic DDL: Binlog event written upon recovery does not
           have default database

The purpose of this task is to ensure that ALTER TABLE is atomic even if
the MariaDB server would be killed at any point of the alter table.
This means that either the ALTER TABLE succeeds (including that triggers,
the status tables and the binary log are updated) or things should be
reverted to their original state.

If the server crashes before the new version is fully up to date and
commited, it will revert to the original table and remove all
temporary files and tables.
If the new version is commited, crash recovery will use the new version,
and update triggers, the status tables and the binary log.
The one execption is ALTER TABLE .. RENAME .. where no changes are done
to table definition. This one will work as RENAME and roll back unless
the whole statement completed, including updating the binary log (if
enabled).

Other changes:
- Added handlerton->check_version() function to allow the ddl recovery
  code to check, in case of inplace alter table, if the table in the
  storage engine is of the new or old version.
- Added handler->table_version() so that an engine can report the current
  version of the table. This should be changed each time the table
  definition changes.
- Added  ha_signal_ddl_recovery_done() and
  handlerton::signal_ddl_recovery_done() to inform all handlers when
  ddl recovery has been done. (Needed by InnoDB).
- Added handlerton call inplace_alter_table_committed, to signal engine
  that ddl_log has been closed for the alter table query.
- Added new handerton flag
  HTON_REQUIRES_NOTIFY_TABLEDEF_CHANGED_AFTER_COMMIT to signal when we
  should call hton->notify_tabledef_changed() during
  mysql_inplace_alter_table. This was required as MyRocks and InnoDB
  needed the call at different times.
- Added function server_uuid_value() to be able to generate a temporary
  xid when ddl recovery writes the query to the binary log. This is
  needed to be able to handle crashes during ddl log recovery.
- Moved freeing of the frm definition to end of mysql_alter_table() to
  remove duplicate code and have a common exit strategy.

-------
InnoDB part of atomic ALTER TABLE
(Implemented by Marko Mäkelä)
innodb_check_version(): Compare the saved dict_table_t::def_trx_id
to determine whether an ALTER TABLE operation was committed.

We must correctly recover dict_table_t::def_trx_id for this to work.
Before purge removes any trace of DB_TRX_ID from system tables, it
will make an effort to load the user table into the cache, so that
the dict_table_t::def_trx_id can be recovered.

ha_innobase::table_version(): return garbage, or the trx_id that would
be used for committing an ALTER TABLE operation.

In InnoDB, table names starting with #sql-ib will remain special:
they will be dropped on startup. This may be revisited later in
MDEV-18518 when we implement proper undo logging and rollback
for creating or dropping multiple tables in a transaction.

Table names starting with #sql will retain some special meaning:
dict_table_t::parse_name() will not consider such names for
MDL acquisition, and dict_table_rename_in_cache() will treat such
names specially when handling FOREIGN KEY constraints.

Simplify InnoDB DROP INDEX.
Prevent purge wakeup

To ensure that dict_table_t::def_trx_id will be recovered correctly
in case the server is killed before ddl_log_complete(), we will block
the purge of any history in SYS_TABLES, SYS_INDEXES, SYS_COLUMNS
between ha_innobase::commit_inplace_alter_table(commit=true)
(purge_sys.stop_SYS()) and purge_sys.resume_SYS().
The completion callback purge_sys.resume_SYS() must be between
ddl_log_complete() and MDL release.

--------

MyRocks support for atomic ALTER TABLE
(Implemented by Sergui Petrunia)

Implement these SE API functions:
- ha_rocksdb::table_version()
- hton->check_version = rocksdb_check_versionMyRocks data dictionary
  now stores table version for each table.
  (Absence of table version record is interpreted as table_version=0,
  that is, which means no upgrade changes are needed)
- For inplace alter table of a partitioned table, call the underlying
  handlerton when checking if the table is ok. This assumes that the
  partition engine commits all changes at once.
2021-05-19 22:54:13 +02:00

198 lines
6.0 KiB
Plaintext

# 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/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;