diff --git a/mysql-test/suite/mariabackup/slave_provision_prepared_trx.cnf b/mysql-test/suite/mariabackup/slave_provision_prepared_trx.cnf new file mode 100644 index 0000000000000..13892d60ef457 --- /dev/null +++ b/mysql-test/suite/mariabackup/slave_provision_prepared_trx.cnf @@ -0,0 +1,14 @@ +[mysqld.1] +log-slave-updates +loose-innodb + +[mysqld.2] +log-slave-updates +loose-innodb +skip-slave-start + +[ENV] +SERVER_MYPORT_1= @mysqld.1.port +SERVER_MYSOCK_1= @mysqld.1.socket +SERVER_MYPORT_2= @mysqld.2.port +SERVER_MYSOCK_2= @mysqld.2.socket diff --git a/mysql-test/suite/mariabackup/slave_provision_prepared_trx.result b/mysql-test/suite/mariabackup/slave_provision_prepared_trx.result new file mode 100644 index 0000000000000..4e20700911363 --- /dev/null +++ b/mysql-test/suite/mariabackup/slave_provision_prepared_trx.result @@ -0,0 +1,70 @@ +### MDEV-36025 backup taken from a slave with optimistic parallel replication fails to restore most of the time +### Test provisioning a slave from an existing server running optimistic +### parallel replication so that prepared transactions are left in the +### backup when the restored server is started. +RESET MASTER; +CREATE TABLE t1(a INT PRIMARY KEY, b VARCHAR(60)) ENGINE INNODB; +INSERT INTO t1 VALUES(1, NULL); +INSERT INTO t1 VALUES(2, NULL); +INSERT INTO t1 VALUES(3, NULL); +connect server2,127.0.0.1,root,,,$SERVER_MYPORT_2; +SET STATEMENT sql_log_bin=0 FOR ALTER TABLE mysql.gtid_slave_pos ENGINE=InnoDB; +RESET MASTER; +SET GLOBAL gtid_slave_pos= ''; +CHANGE MASTER TO +master_port=PORT, master_host='127.0.0.1', master_user='root', +master_use_gtid= slave_pos; +SET GLOBAL slave_parallel_threads=3; +SET GLOBAL slave_parallel_mode=optimistic; +START SLAVE; +connection default; +include/save_master_gtid.inc +connection server2; +include/sync_with_master_gtid.inc +connect server2a,127.0.0.1,root,,,$SERVER_MYPORT_2; +BEGIN; +SELECT * FROM t1 WHERE a=1 FOR UPDATE; +a b +1 NULL +connection default; +UPDATE t1 SET b='xyzzy' WHERE a=1; +INSERT INTO t1 VALUES (4, NULL); +UPDATE t1 SET b='foobar' WHERE a=2; +include/save_master_gtid.inc +*** Doing backup... +*** Doing prepare... +connection server2a; +ROLLBACK; +include/sync_with_master_gtid.inc +connection server2; +*** Provision a new slave from the backup +*** Stopping provisioned server +*** Removing old datadir for provisioned server +*** Provision new server from backup +disconnect server2a; +# restart +CHANGE MASTER TO +master_port=PORT, master_host='127.0.0.1', master_user='root', +master_use_gtid= slave_pos; +START SLAVE; +connection default; +include/save_master_gtid.inc +SELECT * FROM t1 ORDER BY a; +a b +1 xyzzy +2 foobar +3 NULL +4 NULL +connection server2; +include/sync_with_master_gtid.inc +SELECT * FROM t1 ORDER BY a; +a b +1 xyzzy +2 foobar +3 NULL +4 NULL +STOP SLAVE; +RESET SLAVE ALL; +DROP TABLE t1; +connection default; +DROP TABLE t1; diff --git a/mysql-test/suite/mariabackup/slave_provision_prepared_trx.test b/mysql-test/suite/mariabackup/slave_provision_prepared_trx.test new file mode 100644 index 0000000000000..0a396fc2e1c4b --- /dev/null +++ b/mysql-test/suite/mariabackup/slave_provision_prepared_trx.test @@ -0,0 +1,99 @@ +--source include/have_innodb.inc +--source include/have_log_bin.inc + +--echo ### MDEV-36025 backup taken from a slave with optimistic parallel replication fails to restore most of the time +--echo ### Test provisioning a slave from an existing server running optimistic +--echo ### parallel replication so that prepared transactions are left in the +--echo ### backup when the restored server is started. + + +RESET MASTER; +CREATE TABLE t1(a INT PRIMARY KEY, b VARCHAR(60)) ENGINE INNODB; +INSERT INTO t1 VALUES(1, NULL); +INSERT INTO t1 VALUES(2, NULL); +INSERT INTO t1 VALUES(3, NULL); + +--connect (server2,127.0.0.1,root,,,$SERVER_MYPORT_2) +let MYSQLD_DATADIR=`select @@datadir`; +let $basedir=$MYSQLTEST_VARDIR/tmp/backup; +SET STATEMENT sql_log_bin=0 FOR ALTER TABLE mysql.gtid_slave_pos ENGINE=InnoDB; +RESET MASTER; +SET GLOBAL gtid_slave_pos= ''; + +--replace_result $SERVER_MYPORT_1 PORT +eval CHANGE MASTER TO + master_port=$SERVER_MYPORT_1, master_host='127.0.0.1', master_user='root', + master_use_gtid= slave_pos; +SET GLOBAL slave_parallel_threads=3; +SET GLOBAL slave_parallel_mode=optimistic; +START SLAVE; + +--connection default +--source include/save_master_gtid.inc +--connection server2 +--source include/sync_with_master_gtid.inc + +# Block transaction T1 from committing on the slave, forcing a following +# T2 to remain in "prepared" state when the backup is taken. +--connect (server2a,127.0.0.1,root,,,$SERVER_MYPORT_2) +BEGIN; +SELECT * FROM t1 WHERE a=1 FOR UPDATE; + +--connection default +UPDATE t1 SET b='xyzzy' WHERE a=1; +INSERT INTO t1 VALUES (4, NULL); +UPDATE t1 SET b='foobar' WHERE a=2; +--source include/save_master_gtid.inc + +# Sleep a small amount to give a higher chance for the transactions to reach +# their prepared state when the backup runs. +--sleep 0.3 + +--echo *** Doing backup... +--exec $XTRABACKUP --defaults-file=$MYSQLTEST_VARDIR/my.cnf --socket=$SERVER_MYSOCK_2 --backup --target-dir=$basedir +--echo *** Doing prepare... +--exec $XTRABACKUP --prepare --binlog-info=1 --target-dir=$basedir + +--connection server2a +ROLLBACK; +--source include/sync_with_master_gtid.inc + +--connection server2 +--echo *** Provision a new slave from the backup + +--echo *** Stopping provisioned server +--source include/shutdown_mysqld.inc + +--echo *** Removing old datadir for provisioned server +--rmdir $MYSQLD_DATADIR + +--echo *** Provision new server from backup +--exec $XTRABACKUP --copy-back --datadir=$MYSQLD_DATADIR --target-dir=$basedir + +--disconnect server2a +--source include/start_mysqld.inc + +--replace_result $SERVER_MYPORT_1 PORT +eval CHANGE MASTER TO + master_port=$SERVER_MYPORT_1, master_host='127.0.0.1', master_user='root', + master_use_gtid= slave_pos; +# The slave GTID position was saved transactionally to mysql.gtid_slave_pos +# as part of the backup. +START SLAVE; + +--connection default +--source include/save_master_gtid.inc +SELECT * FROM t1 ORDER BY a; + +--connection server2 +--source include/sync_with_master_gtid.inc +SELECT * FROM t1 ORDER BY a; + +STOP SLAVE; +RESET SLAVE ALL; +DROP TABLE t1; + +--connection default +DROP TABLE t1; + +rmdir $basedir; diff --git a/sql/log.cc b/sql/log.cc index 00544f71b17cc..b88cdc72d789b 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -10039,7 +10039,8 @@ int TC_LOG_MMAP::open(const char *opt_name) inited= 1; crashed= TRUE; sql_print_information("Recovering after a crash using %s", opt_name); - if (tc_heuristic_recover) + if (tc_heuristic_recover && + tc_heuristic_recover != TC_HEURISTIC_RECOVER_AUTO) { sql_print_error("Cannot perform automatic crash recovery when " "--tc-heuristic-recover is used"); @@ -10562,7 +10563,8 @@ TC_LOG_MMAP tc_log_mmap; int TC_LOG::using_heuristic_recover() { - if (!tc_heuristic_recover) + if (!tc_heuristic_recover || + tc_heuristic_recover == TC_HEURISTIC_RECOVER_AUTO) return 0; sql_print_information("Heuristic crash recovery mode"); diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 52938056f0b5d..7d50735fc1195 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -294,7 +294,7 @@ const char *show_comp_option_name[]= {"YES", "NO", "DISABLED"}; static const char *tc_heuristic_recover_names[]= { - "OFF", "COMMIT", "ROLLBACK", NullS + "OFF", "COMMIT", "ROLLBACK", "AUTO", NullS }; static TYPELIB tc_heuristic_recover_typelib= { @@ -7027,7 +7027,7 @@ struct my_option my_long_options[]= {"tc-heuristic-recover", 0, "Decision to use in heuristic recover process", &tc_heuristic_recover, &tc_heuristic_recover, - &tc_heuristic_recover_typelib, GET_ENUM, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + &tc_heuristic_recover_typelib, GET_ENUM, REQUIRED_ARG, 3, 0, 0, 0, 0, 0}, {"temp-pool", 0, #if (ENABLE_TEMP_POOL) "Using this option will cause most temporary files created to use a small " diff --git a/sql/sql_class.h b/sql/sql_class.h index 20b3385b0582d..5d6815a99ce8b 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -280,6 +280,7 @@ class Recreate_info #define TC_HEURISTIC_RECOVER_COMMIT 1 #define TC_HEURISTIC_RECOVER_ROLLBACK 2 +#define TC_HEURISTIC_RECOVER_AUTO 3 extern ulong tc_heuristic_recover; typedef struct st_user_var_events