mariadb/mysql-test/suite/rpl/t/rpl_gtid_strict.test
Monty e62dc52420 MDEV-25292 Atomic CREATE OR REPLACE TABLE
Atomic CREATE OR REPLACE allows to keep an old table intact if the
command fails or during the crash. That is done by renaming the
original table to temporary name, as a backup and restoring it if the
CREATE fails. When the command is complete and logged the backup
table is deleted.

Atomic replace algorithm

  Two DDL chains are used for CREATE OR REPLACE:
  ddl_log_state_create (C) and ddl_log_state_rm (D).

  1. (C) Log rename of ORIG to TMP table (Rename TMP to original).
  2. Rename orignal to TMP.
  3. (C) Log CREATE_TABLE_ACTION of ORIG (drops ORIG);
  4. Do everything with ORIG (like insert data)
  5. (D) Log drop of TMP
  6. Write query to binlog (this marks (C) to be closed in
     case of failure)
  7. Execute drop of TMP through (D)
  8. Close (C) and (D)

  If there is a failure before 6) we revert the changes in (C)
  Chain (D) is only executed if 6) succeded (C is closed on
  crash recovery).

Foreign key errors will be found at the 1) stage.

Additional notes

  - CREATE TABLE without REPLACE and temporary tables is not affected
    by this commit.
    set @@drop_before_create_or_replace=1 can be used to
    get old behaviour where existing tables are dropped
    in CREATE OR REPLACE.

  - CREATE TABLE is reverted if binlogging the query fails.

  - Engines having HTON_EXPENSIVE_RENAME flag set are not affected by
    this commit. Conflicting tables marked with this flag will be
    deleted with CREATE OR REPLACE.

  - Replication execution is not affected by this commit.
    - Replication will first drop the conflicting table and then
      creating the new one.

  - CREATE TABLE .. SELECT XID usage is fixed and now there is no need
    to log DROP TABLE via DDL_CREATE_TABLE_PHASE_LOG (see comments in
    do_postlock()). XID is now correctly updated so it disables
    DDL_LOG_DROP_TABLE_ACTION. Note that binary log is flushed at the
    final stage when the table is ready. So if we have XID in the
    binary log we don't need to drop the table.

  - Three variations of CREATE OR REPLACE handled:

    1. CREATE OR REPLACE TABLE t1 (..);
    2. CREATE OR REPLACE TABLE t1 LIKE t2;
    3. CREATE OR REPLACE TABLE t1 SELECT ..;

  - Test case uses 6 combinations for engines (aria, aria_notrans,
    myisam, ib, lock_tables, expensive_rename) and 2 combinations for
    binlog types (row, stmt). Combinations help to check differences
    between the results. Error failures are tested for the above three
    variations.

  - expensive_rename tests CREATE OR REPLACE without atomic
    replace. The effect should be the same as with the old behaviour
    before this commit.

  - Triggers mechanism is unaffected by this change. This is tested in
    create_replace.test.

  - LOCK TABLES is affected. Lock restoration must be done after new
    table is created or TMP is renamed back to ORIG

  - Moved ddl_log_complete() from send_eof() to finalize_ddl(). This
    checkpoint was not executed before for normal CREATE TABLE but is
    executed now.

  - CREATE TABLE will now rollback also if writing to the binary
    logging failed. See rpl_gtid_strict.test

backup ddl log changes

- In case of a successfull CREATE OR REPLACE we only log
  the CREATE event, not the DROP TABLE event of the old table.

ddl_log.cc changes

  ddl_log_execute_action() now properly return error conditions.
  ddl_log_disable_entry() added to allow one to disable one entry.
  The entry on disk is still reserved until ddl_log_complete() is
  executed.

On XID usage

  Like with all other atomic DDL operations XID is used to avoid
  inconsistency between master and slave in the case of a crash after
  binary log is written and before ddl_log_state_create is closed. On
  recovery XIDs are taken from binary log and corresponding DDL log
  events get disabled.  That is done by
  ddl_log_close_binlogged_events().

On linking two chains together

  Chains are executed in the ascending order of entry_pos of execute
  entries. But entry_pos assignment order is undefined: it may assign
  bigger number for the first chain and then smaller number for the
  second chain. So the execution order in that case will be reverse:
  second chain will be executed first.

  To avoid that we link one chain to another. While the base chain
  (ddl_log_state_create) is active the secondary chain
  (ddl_log_state_rm) is not executed. That is: only one chain can be
  executed in two linked chains.

  The interface ddl_log_link_chains() was defined in "MDEV-22166
  ddl_log_write_execute_entry() extension".

Atomic info parameters in HA_CREATE_INFO

  Many functions in CREATE TABLE pass the same parameters. These
  parameters are part of table creation info and should be in
  HA_CREATE_INFO (or whatever). Passing parameters via single
  structure is much easier for adding new data and
  refactoring.

InnoDB changes
  Added ha_innobase::can_be_renamed_to_backup() to check if
  a table with foreign keys can be renamed.

Aria changes:
- Fixed issue in Aria engine with CREATE + locked tables
  that data was not properly commited in some cases in
  case of crashes.

Known issues:
- InnoDB tables with foreign key definitions are not fully supported
  with atomic create and replace:
  - ha_innobase::can_be_renamed_to_backup() can detect some cases
    where InnoDB does not support renaming table with foreign key
    constraints.  In this case MariaDB will drop the old table before
    creating the new one.
    The detected cases are:
    - The new and old table is using the same foreign key constraint
      name.
    - The old table has self referencing constraints.
  - If the old and new table uses the same name for a constraint the
    create of the new table will fail. The orignal table will be
    restored in this case.
  - The above issues will be fixed in a future commit.
- CREATE OR REPLACE TEMPORARY table is not full atomic. Any conflicting
  table will always be dropped before creating a new one. (Old behaviour).
2025-03-18 18:28:16 +01:00

174 lines
4.7 KiB
Text

--source include/have_debug.inc
--source include/have_innodb.inc
--source include/have_binlog_format_statement.inc
--let $rpl_topology=1->2
--source include/rpl_init.inc
--connection server_1
ALTER TABLE mysql.gtid_slave_pos ENGINE=InnoDB;
SET sql_log_bin= 0;
call mtr.add_suppression("Error writing file .*errno: 1950");
SET sql_log_bin= 1;
SET @old_gtid_strict_mode= @@GLOBAL.gtid_strict_mode;
SET GLOBAL gtid_strict_mode= 1;
--connection server_2
--source include/stop_slave.inc
SET @old_gtid_strict_mode= @@GLOBAL.gtid_strict_mode;
SET GLOBAL gtid_strict_mode=1;
CHANGE MASTER TO master_use_gtid=slave_pos;
--source include/start_slave.inc
--connection server_1
CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB;
INSERT INTO t1 VALUES (1);
--source include/show_binlog_events.inc
SET server_id= 3;
--error ER_GTID_STRICT_OUT_OF_ORDER
SET gtid_seq_no= 3;
SET @old_dbug = @@session.debug_dbug;
SET SESSION debug_dbug="d,ignore_set_gtid_seq_no_check";
SET gtid_seq_no= 3;
SET SESSION debug_dbug=@old_dbug;
--error ER_GTID_STRICT_OUT_OF_ORDER
INSERT INTO t1 VALUES (2);
--error ER_GTID_STRICT_OUT_OF_ORDER
SET gtid_seq_no= 2;
SET SESSION debug_dbug="d,ignore_set_gtid_seq_no_check";
SET gtid_seq_no= 2;
SET SESSION debug_dbug=@old_dbug;
--error ER_GTID_STRICT_OUT_OF_ORDER
INSERT INTO t1 VALUES (3);
SET server_id= 1;
SET gtid_seq_no= 4;
INSERT INTO t1 VALUES (4);
SELECT * FROM t1 ORDER BY 1;
--source include/show_binlog_events.inc
--echo *** Test non-transactional GTID error ***
SET server_id= 3;
--error ER_GTID_STRICT_OUT_OF_ORDER
SET gtid_seq_no= 1;
SET SESSION debug_dbug="d,ignore_set_gtid_seq_no_check";
SET gtid_seq_no= 1;
SET SESSION debug_dbug=@old_dbug;
--error ER_GTID_STRICT_OUT_OF_ORDER
CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=MyISAM;
CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=MyISAM;
--error ER_GTID_STRICT_OUT_OF_ORDER
SET gtid_seq_no= 1;
SET SESSION debug_dbug="d,ignore_set_gtid_seq_no_check";
SET gtid_seq_no= 1;
SET SESSION debug_dbug=@old_dbug;
--error ER_GTID_STRICT_OUT_OF_ORDER
INSERT INTO t2 VALUES (1);
# The value is still inserted, cannot be rolled back.
SET server_id= 1;
SET gtid_seq_no= 6;
INSERT INTO t2 VALUES (2);
SELECT * FROM t2 ORDER BY a;
--source include/show_binlog_events.inc
--echo *** Test that slave stops if it tries to apply a GTID that would create out-of-order binlog GTID sequence numbers. ***
--save_master_pos
--connection server_2
--sync_with_master
SELECT * FROM t1 ORDER BY a;
SELECT * FROM t2 ORDER BY a;
SET sql_log_bin= 0;
call mtr.add_suppression("An attempt was made to binlog GTID .* which would create an out-of-order sequence number with existing GTID .*, and gtid strict mode is enabled");
call mtr.add_suppression("The binlog on the master is missing the GTID [-0-9]+ requested by the slave");
SET sql_log_bin= 1;
# Create some out-of-order stuff on slave.
INSERT INTO t1 VALUES (5);
--connection server_1
INSERT INTO t1 VALUES (6);
--save_master_pos
--connection server_2
--let $slave_sql_errno=1950
--source include/wait_for_slave_sql_error.inc
STOP SLAVE IO_THREAD;
SET GLOBAL gtid_strict_mode=0;
--source include/start_slave.inc
--sync_with_master
SET GLOBAL gtid_strict_mode=1;
SELECT * FROM t1 ORDER BY a;
INSERT INTO t1 VALUES (7);
--connection server_1
CREATE TABLE t3 (a INT PRIMARY KEY);
--save_master_pos
--connection server_2
--let $slave_sql_errno=1950
--source include/wait_for_slave_sql_error.inc
--error ER_NO_SUCH_TABLE
--query_vertical SHOW CREATE TABLE t3
STOP SLAVE IO_THREAD;
SET GLOBAL gtid_strict_mode=0;
--source include/start_slave.inc
--sync_with_master
SET GLOBAL gtid_strict_mode=1;
--query_vertical SHOW CREATE TABLE t3
INSERT INTO t1 VALUES (8);
--connection server_1
INSERT INTO t2 VALUES (3);
--save_master_pos
--connection server_2
--let $slave_sql_errno=1950
--source include/wait_for_slave_sql_error.inc
SELECT * FROM t2 ORDER BY a;
STOP SLAVE IO_THREAD;
SET GLOBAL gtid_strict_mode=0;
--source include/start_slave.inc
--sync_with_master
SET GLOBAL gtid_strict_mode=1;
SELECT * FROM t2 ORDER BY a;
--echo *** Check slave requests starting from a hole on the master. ***
--connection server_2
--source include/stop_slave.inc
--connection server_1
INSERT INTO t1 VALUES (10);
SET gtid_seq_no= 100;
INSERT INTO t1 VALUES (11);
INSERT INTO t1 VALUES (12);
--save_master_pos
--connection server_2
SET GLOBAL gtid_slave_pos= "0-1-50";
START SLAVE;
--let $slave_io_errno=1236
--source include/wait_for_slave_io_error.inc
STOP SLAVE SQL_THREAD;
SET GLOBAL gtid_strict_mode= 0;
--source include/start_slave.inc
--sync_with_master
SELECT * FROM t1 ORDER BY a;
SET GLOBAL gtid_strict_mode= 1;
# Clean up.
--connection server_1
DROP TABLE t1, t2, t3;
SET GLOBAL gtid_strict_mode= @old_gtid_strict_mode;
--connection server_2
SET GLOBAL gtid_strict_mode= @old_gtid_strict_mode;
--source include/rpl_end.inc