mirror of
https://github.com/MariaDB/server.git
synced 2025-09-09 18:40:27 +03:00
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.
207 lines
5.7 KiB
Plaintext
207 lines
5.7 KiB
Plaintext
--source include/have_innodb.inc
|
|
# The embedded server tests do not support restarting.
|
|
--source include/not_embedded.inc
|
|
--source include/have_debug.inc
|
|
--source include/have_debug_sync.inc
|
|
|
|
FLUSH TABLES;
|
|
|
|
let INNODB_PAGE_SIZE=`select @@innodb_page_size`;
|
|
let MYSQLD_DATADIR=`select @@datadir`;
|
|
|
|
--echo #
|
|
--echo # MDEV-11369: Instant ADD COLUMN for InnoDB
|
|
--echo #
|
|
|
|
CREATE TABLE t1(id INT PRIMARY KEY, c2 INT UNIQUE)
|
|
ENGINE=InnoDB ROW_FORMAT=REDUNDANT;
|
|
CREATE TABLE t2 LIKE t1;
|
|
INSERT INTO t1 VALUES(0,2);
|
|
INSERT INTO t2 VALUES(2,1);
|
|
ALTER TABLE t2 ADD COLUMN (c3 TEXT NOT NULL DEFAULT 'De finibus bonorum');
|
|
BEGIN;
|
|
INSERT INTO t2 VALUES(3,4,'accusantium doloremque laudantium');
|
|
|
|
connect ddl, localhost, root;
|
|
SET DEBUG_SYNC='innodb_alter_inplace_before_commit SIGNAL ddl WAIT_FOR ever';
|
|
--send
|
|
ALTER TABLE t1 ADD COLUMN (c3 TEXT NOT NULL DEFAULT ' et malorum');
|
|
|
|
connection default;
|
|
SET DEBUG_SYNC='now WAIT_FOR ddl';
|
|
SET GLOBAL innodb_flush_log_at_trx_commit=1;
|
|
COMMIT;
|
|
|
|
--source include/kill_mysqld.inc
|
|
disconnect ddl;
|
|
--source include/start_mysqld.inc
|
|
|
|
SET GLOBAL innodb_purge_rseg_truncate_frequency=1;
|
|
SELECT * FROM t1;
|
|
SELECT * FROM t2;
|
|
BEGIN;
|
|
DELETE FROM t1;
|
|
ROLLBACK;
|
|
--source include/wait_all_purged.inc
|
|
|
|
INSERT INTO t2 VALUES
|
|
(16,1551,'Omnium enim rerum'),(128,1571,' principia parva sunt');
|
|
|
|
connect ddl, localhost, root;
|
|
SET DEBUG_SYNC='innodb_alter_inplace_before_commit SIGNAL ddl WAIT_FOR ever';
|
|
--send
|
|
ALTER TABLE t2 DROP COLUMN c3, ADD COLUMN c5 TEXT DEFAULT 'naturam abhorrere';
|
|
|
|
connection default;
|
|
SET DEBUG_SYNC='now WAIT_FOR ddl';
|
|
SET GLOBAL innodb_flush_log_at_trx_commit=1;
|
|
SET debug_dbug='+d,dict_sys_mutex_avoid';
|
|
UPDATE t1 SET c2=c2+1;
|
|
|
|
--source include/kill_mysqld.inc
|
|
disconnect ddl;
|
|
--source include/start_mysqld.inc
|
|
|
|
SET @saved_frequency= @@GLOBAL.innodb_purge_rseg_truncate_frequency;
|
|
SET GLOBAL innodb_purge_rseg_truncate_frequency=1;
|
|
|
|
SELECT * FROM t1;
|
|
SELECT * FROM t2;
|
|
BEGIN;
|
|
INSERT INTO t1 SET id=1;
|
|
DELETE FROM t2;
|
|
ROLLBACK;
|
|
--source include/wait_all_purged.inc
|
|
|
|
INSERT INTO t2 VALUES (64,42,'De finibus bonorum'), (347,33101,' et malorum');
|
|
|
|
connect ddl, localhost, root;
|
|
ALTER TABLE t2 DROP COLUMN c3;
|
|
SET DEBUG_SYNC='innodb_alter_inplace_before_commit SIGNAL ddl WAIT_FOR ever';
|
|
--send
|
|
ALTER TABLE t2 ADD COLUMN (c4 TEXT NOT NULL DEFAULT ' et malorum');
|
|
|
|
connection default;
|
|
SET DEBUG_SYNC='now WAIT_FOR ddl';
|
|
SET GLOBAL innodb_flush_log_at_trx_commit=1;
|
|
SET debug_dbug='+d,dict_sys_mutex_avoid';
|
|
DELETE FROM t1;
|
|
|
|
--source include/kill_mysqld.inc
|
|
disconnect ddl;
|
|
--source include/start_mysqld.inc
|
|
|
|
SET GLOBAL innodb_purge_rseg_truncate_frequency=1;
|
|
|
|
let SEARCH_FILE= $MYSQLTEST_VARDIR/log/mysqld.1.err;
|
|
let SEARCH_PATTERN= \[Note\] InnoDB: Rolled back recovered transaction ;
|
|
-- source include/search_pattern_in_file.inc
|
|
|
|
SELECT * FROM t1;
|
|
SELECT * FROM t2;
|
|
BEGIN;
|
|
INSERT INTO t1 SET id=1;
|
|
DELETE FROM t2;
|
|
ROLLBACK;
|
|
--source include/wait_all_purged.inc
|
|
|
|
FLUSH TABLE t1,t2 FOR EXPORT;
|
|
|
|
# At this point, t1 is empty and t2 contains a 'default row'.
|
|
|
|
# The following is based on innodb.table_flags and innodb.dml_purge:
|
|
--perl
|
|
use strict;
|
|
my $ps= $ENV{INNODB_PAGE_SIZE};
|
|
foreach my $table ('t1','t2') {
|
|
my $file= "$ENV{MYSQLD_DATADIR}/test/$table.ibd";
|
|
open(FILE, "<", $file) || die "Unable to open $file\n";
|
|
my $page;
|
|
sysseek(FILE, 3*$ps, 0) || die "Unable to seek $file";
|
|
die "Unable to read $file" unless sysread(FILE, $page, $ps) == $ps;
|
|
print "$table clustered index root page";
|
|
print "(type ", unpack("n", substr($page,24,2)), "):\n";
|
|
print "N_RECS=", unpack("n", substr($page,38+16,2));
|
|
print "; LEVEL=", unpack("n", substr($page,38+26,2)), "\n";
|
|
my @fields=("id","DB_TRX_ID","DB_ROLL_PTR", "c2","c3","c4");
|
|
for (my $offset= 0x65; $offset;
|
|
$offset= unpack("n", substr($page,$offset-2,2)))
|
|
{
|
|
print "header=0x", unpack("H*",substr($page,$offset-6,6)), " (";
|
|
my $n_fields= unpack("n", substr($page,$offset-4,2)) >> 1 & 0x3ff;
|
|
my $start= 0;
|
|
my $name;
|
|
if (unpack("C", substr($page,$offset-3,1)) & 1) {
|
|
for (my $i= 0; $i < $n_fields; $i++) {
|
|
my $end= unpack("C", substr($page, $offset-7-$i, 1));
|
|
print ",\n " if $i;
|
|
print "$fields[$i]=";
|
|
if ($end & 0x80) {
|
|
print "NULL(", ($end & 0x7f) - $start, " bytes)"
|
|
} else {
|
|
print "0x", unpack("H*", substr($page,$offset+$start,$end-$start))
|
|
}
|
|
$start= $end & 0x7f;
|
|
}
|
|
} else {
|
|
for (my $i= 0; $i < $n_fields; $i++) {
|
|
my $end= unpack("n", substr($page, $offset-8-2*$i, 2));
|
|
print ",\n " if $i;
|
|
if ($i > 2 && !(~unpack("C",substr($page,$offset-6,1)) & 0x30)) {
|
|
if ($i == 3) {
|
|
print "BLOB=";
|
|
$start += 8; # skip the space_id,page_number
|
|
} else {
|
|
print "$fields[$i - 1]=";
|
|
}
|
|
} else {
|
|
print "$fields[$i]=";
|
|
}
|
|
if ($end & 0x8000) {
|
|
print "NULL(", ($end & 0x7fff) - $start, " bytes)"
|
|
} else {
|
|
print "0x", unpack("H*", substr($page,$offset+$start,($end-$start) & 0x3fff))
|
|
}
|
|
$start= $end & 0x3fff;
|
|
}
|
|
}
|
|
print ")\n";
|
|
}
|
|
close(FILE) || die "Unable to close $file\n";
|
|
}
|
|
EOF
|
|
|
|
UNLOCK TABLES;
|
|
|
|
DELETE FROM t2;
|
|
--source include/wait_all_purged.inc
|
|
|
|
--echo #
|
|
--echo # MDEV-24323 Crash on recovery after kill during instant ADD COLUMN
|
|
--echo #
|
|
connect ddl, localhost, root;
|
|
CREATE TABLE t3(id INT PRIMARY KEY, c2 INT, v2 INT AS(c2) VIRTUAL, UNIQUE(v2))
|
|
ENGINE=InnoDB;
|
|
INSERT INTO t3 SET id=1,c2=1;
|
|
|
|
SET DEBUG_SYNC='innodb_alter_inplace_before_commit SIGNAL ddl WAIT_FOR ever';
|
|
--send
|
|
ALTER TABLE t3 ADD COLUMN c3 TEXT NOT NULL DEFAULT 'sic transit gloria mundi';
|
|
|
|
connection default;
|
|
SET DEBUG_SYNC='now WAIT_FOR ddl';
|
|
SET GLOBAL innodb_flush_log_at_trx_commit=1;
|
|
SET debug_dbug='+d,dict_sys_mutex_avoid';
|
|
INSERT INTO t1 VALUES(0,0);
|
|
|
|
--source include/kill_mysqld.inc
|
|
disconnect ddl;
|
|
--source include/start_mysqld.inc
|
|
|
|
SHOW CREATE TABLE t1;
|
|
SHOW CREATE TABLE t2;
|
|
SHOW CREATE TABLE t3;
|
|
DROP TABLE t1,t2,t3;
|
|
|
|
--list_files $MYSQLD_DATADIR/test
|