From ca86986a14ed0be89cc3d7958c6678aeedf82ac8 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 1 Nov 2023 20:32:45 +0200 Subject: [PATCH 1/8] MDEV-32830 I. refactor XA binlogging for better integration with BGC/replication/recovery This commit is the part I of the series of four that addresses MDEV-31949 in two main directions which are xa parallel slave performance and xa transaction crash-recovery. This one improves upon MDEV-742 design's XA binlogging to facilitate to the crash-recovery (the actual binlog-based recovery is coming in the part IV of MDEV-33168 et al). With the refactoring changes, when binlog is ON, handling of execution of a XA transaction, including binlogging, is made conceptually uniform with the normal BEGIN-COMMIT transaction. That is At XA-PREPARE the transaction first is prepared in engines and after that accumulated replication events are written to the binary log, naturally without any completion event as it's unknown yet. When later XA-"COMPLETE" that is XA-COMMIT and XA-PREPARE follows up, the binary logging of respective Query event takes place first. One can perceive such scheme as if a normal transaction logging is split in the middle into two parts (and nothing really happens in between of them but time passed by). And after the second chunk is sent to binlog the transaction gets committed (or rolled back) in engine. With binlog is enabled both phases' loggings go through binlog-group-commit, where XA-PREPARE "sub-transaction" merely groups for binary logging so skips the engine action while XA-"COMPLETE" does both, that is the logging and an ordered "complete". This behavior is also consistent between completions from the native and external connections. Being a participant of binlog-group-commit designates either XA phase is recoverable (not implemented here) from active binlogs determined by binlog-checkpoint. For the latter specifically this patch removes custom unlogging of XA-prepare group. See entry.need_unlog= 0 et al in MYSQL_BIN_LOG::write_transaction_to_binlog(). In addition to the above a corner case of engine read-only XA transaction is addressed. Previously it was streamlined with logging an empty XA-PREPARE group of binlog events concluded by XA-"COMPLETE" query-event. Now when a preparing XA transaction is found to have only read-only engine branches or none it is marked for rollback as XA_RDONLY optimization: - nothing gets logged at the prepare time an XA_RDONLY note is generated and - it's rolled back at disconnect For XA-COMPLETE to tell whether the prepare phase was logged or not the XID state object is extended with a boolean flag which is a part internal interface for recovery implementation. The flag is normally raised by XA-prepare at flushing to binlog and also at binlog recovery (will be done so it's fully implemented). Notable changes: sql/handler.cc - ha_prepare() a. is ensured to execute binlog_hton::prepare() as the last XA's branch for preparing; b. engine read-only is marked in the xid state to rollback and ER_XA_RDONLY *note* is generated. - conversely ha_rollback_trans() executes binlog hton::rollback() as first branch (the commit method was already equipped to do so) - ditto to the external completion of XA via ha_commit_or_rollback_by_xid(); the function is made a sort of recursive. It may be first be invoked on a top level to take on the binlog hton "completion" to be called from its stack once again now having is_xap_binlogged() false, so to carry out the engine commit. - xarecover_handlerton() now only simulates successful find of the user xid in binlog. sql/log.cc - binlog_commit,rollback() et al are simplified and cleaned up (like binlog_complete_by_xid() introduction). In particular binlog_{commit,rollback}() are rendered to retain just a single piece of XA footprint in either. The methods recognize naturally empty transaction caches at XA completion to proceed anyway into binlog-group-commit thickness; the binlog_commit's binlog_commit_flush_trx_cache() decides which type of transaction and which XA phase is being handled so a proper group event closure is computed. - MYSQL_BIN_LOG::trx_group_commit_with_engines() takes care to raise or drop XID::binlogged flag via xid_cache_update_xa_binlog_state(). - the new run_xa_complete_ordered() encapsulates XA specifics at execution of the engine ordered commit. It's defined with asserts due to MDEV-32455. It's not done as TC_LOG::member because of the scope of this work is limited. The new function mirrors the logic of the normal run_commit_ordered() in that it skips engine completion for those that lack hton::commit_ordered() in favor of doing that on the top level of ha_commit,rollback_trans(). sql/log_event_server.cc - use the transaction cache at XA-PREPARE handling (specifically when XA-END Query event is logged). sql/xa.cc - XID_cache_element extended with is-binlogged meaning flag and few rating functions added to use by binlogging and recovery (xid_cache_update_xa_binlog_state()); - trans_xa_commit,rollback() external action branches are converted into calls of a new largely common function. sql/xa.h - xid_cache_insert(XID *xid, bool is_binlogged) parameter list is extended for xa binlog recovery. --- mysql-test/include/show_binlog_events2.inc | 8 + mysql-test/include/wait_for_binlog_event.inc | 13 +- .../include/binlog_xa_prepare_disconnect.inc | 4 +- .../binlog/r/binlog_empty_xa_prepared.result | 95 ++- .../binlog/r/binlog_xa_checkpoint.result | 66 +- .../binlog/r/binlog_xa_prepared_bugs.result | 50 ++ .../r/binlog_xa_prepared_disconnect.result | 60 +- .../binlog/t/binlog_empty_xa_prepared.test | 56 +- .../suite/binlog/t/binlog_xa_checkpoint.test | 147 +++- .../binlog/t/binlog_xa_prepared_bugs.test | 36 + .../rpl/include/rpl_xa_concurrent_2pc.inc | 334 ++++++++ .../rpl_xa_empty_transaction_test_case.inc | 2 +- .../rpl/include/rpl_xa_mixed_engines.inc | 1 + mysql-test/suite/rpl/r/rpl_xa.result | 43 +- .../suite/rpl/r/rpl_xa_concurrent_2pc.result | 780 ++++++++++++++++++ .../r/rpl_xa_concurrent_2pc_lsu_off.result | 633 ++++++++++++++ .../rpl/r/rpl_xa_empty_transaction.result | 138 +++- .../rpl/r/rpl_xa_gtid_pos_auto_engine.result | 43 +- .../rpl/r/rpl_xa_survive_disconnect.result | 2 + .../rpl_xa_survive_disconnect_lsu_off.result | 2 + ...xa_survive_disconnect_mixed_engines.result | 22 + .../suite/rpl/t/rpl_create_xa_prepared.inc | 2 +- .../rpl/t/rpl_parallel_multi_domain_xa.test | 2 + .../rpl/t/rpl_parallel_optimistic_xa.test | 24 +- mysql-test/suite/rpl/t/rpl_xa.inc | 54 +- .../suite/rpl/t/rpl_xa_concurrent_2pc.test | 111 +++ .../t/rpl_xa_concurrent_2pc_lsu_off-slave.opt | 1 + .../rpl/t/rpl_xa_concurrent_2pc_lsu_off.test | 1 + .../suite/rpl/t/rpl_xa_empty_transaction.test | 54 +- .../suite/rpl/t/rpl_xa_prepare_gtid_fail.test | 2 +- sql/handler.cc | 127 ++- sql/handler.h | 3 +- sql/log.cc | 385 +++++---- sql/log.h | 10 - sql/log_event_server.cc | 2 +- sql/share/errmsg-utf8.txt | 3 + sql/xa.cc | 347 ++++---- sql/xa.h | 7 +- 38 files changed, 3119 insertions(+), 551 deletions(-) create mode 100644 mysql-test/suite/binlog/r/binlog_xa_prepared_bugs.result create mode 100644 mysql-test/suite/binlog/t/binlog_xa_prepared_bugs.test create mode 100644 mysql-test/suite/rpl/include/rpl_xa_concurrent_2pc.inc create mode 100644 mysql-test/suite/rpl/r/rpl_xa_concurrent_2pc.result create mode 100644 mysql-test/suite/rpl/r/rpl_xa_concurrent_2pc_lsu_off.result create mode 100644 mysql-test/suite/rpl/t/rpl_xa_concurrent_2pc.test create mode 100644 mysql-test/suite/rpl/t/rpl_xa_concurrent_2pc_lsu_off-slave.opt create mode 100644 mysql-test/suite/rpl/t/rpl_xa_concurrent_2pc_lsu_off.test diff --git a/mysql-test/include/show_binlog_events2.inc b/mysql-test/include/show_binlog_events2.inc index 69391ff8fa2f0..593965b9133ca 100644 --- a/mysql-test/include/show_binlog_events2.inc +++ b/mysql-test/include/show_binlog_events2.inc @@ -19,6 +19,9 @@ # $regexp_replace # A user's custom addon to standard preexisting list. # +# $filter_cid +# boolean whether to filer out commit id (0) or not (1) +# if ($binlog_start) { @@ -26,6 +29,7 @@ if ($binlog_start) } if (!$binlog_start) { + # consider instead ./binlog_start_pos.inc --let $_binlog_start=256 } if ($binlog_file) @@ -36,4 +40,8 @@ if ($binlog_file) --replace_result "$_from_binlog_start" "from " $MYSQLTEST_VARDIR MYSQLTEST_VARDIR --replace_column 2 # 5 # --replace_regex /\/\* xid=.* \*\//\/* XID *\// /table_id: [0-9]+/table_id: #/ /file_id=[0-9]+/file_id=#/ /GTID [0-9]+-[0-9]+-[0-9]+/GTID #-#-#/ $replace_regexp +if ($filter_cid) +{ +--replace_regex /\/\* xid=.* \*\//\/* XID *\// /table_id: [0-9]+/table_id: #/ /file_id=[0-9]+/file_id=#/ /GTID [0-9]+-[0-9]+-[0-9]+/GTID #-#-#/ / cid=[0-9]+// $replace_regexp +} --eval show binlog events $_in_binlog_file from $_binlog_start diff --git a/mysql-test/include/wait_for_binlog_event.inc b/mysql-test/include/wait_for_binlog_event.inc index b251ae226d3d7..33a8ad8d37149 100644 --- a/mysql-test/include/wait_for_binlog_event.inc +++ b/mysql-test/include/wait_for_binlog_event.inc @@ -6,13 +6,20 @@ # # USAGE # +# let $wait_binlog_file= 'master-bin.000001', defaults to the current log # let $wait_binlog_event= DROP; +# OR +# let $wait_binlog_event= ;pos=4 # as Rotate event signature in the SBE Info + # --source include/wait_for_binlog_event.inc let $_loop_count= 300; let $_last_event= ; let $_event_pos= 1; - +if (!$wait_binlog_file) +{ + let $wait_binlog_file=query_get_value(SHOW MASTER STATUS, File, 1); +} while (`SELECT INSTR("$_last_event","$wait_binlog_event") = 0`) { dec $_loop_count; @@ -22,12 +29,12 @@ while (`SELECT INSTR("$_last_event","$wait_binlog_event") = 0`) --die ERROR: failed while waiting for $wait_binlog_event in binlog } real_sleep 0.1; - let $_event= query_get_value(SHOW BINLOG EVENTS, Info, $_event_pos); + let $_event= query_get_value(SHOW BINLOG EVENTS IN '$wait_binlog_file', Info, $_event_pos); let $_last_event= $_event; while ($_event != "No such row") { inc $_event_pos; let $_last_event= $_event; - let $_event= query_get_value(SHOW BINLOG EVENTS, Info, $_event_pos); + let $_event= query_get_value(SHOW BINLOG EVENTS IN '$wait_binlog_file', Info, $_event_pos); } } diff --git a/mysql-test/suite/binlog/include/binlog_xa_prepare_disconnect.inc b/mysql-test/suite/binlog/include/binlog_xa_prepare_disconnect.inc index 4a83aa5c28241..4f4252904a453 100644 --- a/mysql-test/suite/binlog/include/binlog_xa_prepare_disconnect.inc +++ b/mysql-test/suite/binlog/include/binlog_xa_prepare_disconnect.inc @@ -23,7 +23,7 @@ XA RECOVER; --source include/wait_condition.inc # It will conclude now ---error 0,1402 +--error 0,1402,ER_XAER_NOTA --eval $terminate_with 'trx1$type' --replace_result $conn3_id CONN_ID @@ -33,5 +33,5 @@ XA RECOVER; --source include/wait_condition.inc # It will conclude now ---error 0,1402 +--error 0,1402,ER_XAER_NOTA --eval $terminate_with 'trx3$type' diff --git a/mysql-test/suite/binlog/r/binlog_empty_xa_prepared.result b/mysql-test/suite/binlog/r/binlog_empty_xa_prepared.result index c23b0cc30592e..a16bccdd1e613 100644 --- a/mysql-test/suite/binlog/r/binlog_empty_xa_prepared.result +++ b/mysql-test/suite/binlog/r/binlog_empty_xa_prepared.result @@ -4,18 +4,20 @@ XA START '3'; CREATE TEMPORARY TABLE tmp_1(c INT); XA END '3'; XA PREPARE '3'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back DROP TEMPORARY TABLE tmp_1; -ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the PREPARED state +ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the ROLLBACK ONLY state ALTER TABLE tmp_1 DROP COLUMN c; -ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the PREPARED state +ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the ROLLBACK ONLY state DROP TEMPORARY SEQUENCE seq_1; -ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the PREPARED state +ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the ROLLBACK ONLY state ALTER SEQUENCE seq_1 INCREMENT BY 1; -ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the PREPARED state +ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the ROLLBACK ONLY state CREATE TEMPORARY TABLE tmp_2(c INT); -ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the PREPARED state +ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the ROLLBACK ONLY state CREATE TEMPORARY SEQUENCE seq_2; -ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the PREPARED state +ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the ROLLBACK ONLY state XA ROLLBACK '3'; DROP SEQUENCE seq_1; DROP TABLE tmp_1; @@ -50,11 +52,6 @@ master-bin.000001 # Query # # use `test`; CREATE TEMPORARY SEQUENCE seq_1 master-bin.000001 # Gtid # # BEGIN GTID #-#-# master-bin.000001 # Query # # use `test`; CREATE TEMPORARY TABLE tmp_1(c INT) master-bin.000001 # Query # # COMMIT -master-bin.000001 # Gtid # # XA START X'33',X'',1 GTID #-#-# -master-bin.000001 # Query # # XA END X'33',X'',1 -master-bin.000001 # XA_prepare # # XA PREPARE X'33',X'',1 -master-bin.000001 # Gtid # # GTID #-#-# -master-bin.000001 # Query # # XA ROLLBACK X'33',X'',1 RESET MASTER; CREATE TABLE t1 (a INT) ENGINE=MyISAM; CREATE TABLE t2 (id INT PRIMARY KEY) ENGINE=InnoDB; @@ -87,11 +84,6 @@ master-bin.000001 # Annotate_rows # # REPLACE INTO t1 SELECT * FROM t1 master-bin.000001 # Table_map # # table_id: # (test.t1) master-bin.000001 # Write_rows_v1 # # table_id: # flags: STMT_END_F master-bin.000001 # Query # # COMMIT -master-bin.000001 # Gtid # # XA START X'31',X'',1 GTID #-#-# -master-bin.000001 # Query # # XA END X'31',X'',1 -master-bin.000001 # XA_prepare # # XA PREPARE X'31',X'',1 -master-bin.000001 # Gtid # # GTID #-#-# -master-bin.000001 # Query # # XA ROLLBACK X'31',X'',1 master-bin.000001 # Gtid # # GTID #-#-# master-bin.000001 # Query # # use `test`; DROP TABLE `t1`,`t2` /* generated by server */ RESET MASTER; @@ -123,11 +115,6 @@ master-bin.000001 # Annotate_rows # # SELECT NEXT VALUE FOR s master-bin.000001 # Table_map # # table_id: # (test.s) master-bin.000001 # Write_rows_v1 # # table_id: # flags: STMT_END_F master-bin.000001 # Query # # COMMIT -master-bin.000001 # Gtid # # XA START X'32',X'',1 GTID #-#-# -master-bin.000001 # Query # # XA END X'32',X'',1 -master-bin.000001 # XA_prepare # # XA PREPARE X'32',X'',1 -master-bin.000001 # Gtid # # GTID #-#-# -master-bin.000001 # Query # # XA ROLLBACK X'32',X'',1 master-bin.000001 # Gtid # # GTID #-#-# master-bin.000001 # Query # # use `test`; DROP SEQUENCE `s` /* generated by server */ master-bin.000001 # Gtid # # GTID #-#-# @@ -142,13 +129,27 @@ SELECT * FROM t1 WHERE a = 2; a XA END '1'; XA PREPARE '1'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect con1; connection default; XA RECOVER; formatID gtrid_length bqual_length data -1 1 0 1 XA COMMIT '1'; -ERROR XA100: XA_RBROLLBACK: Transaction branch was rolled back +ERROR XAE04: XAER_NOTA: Unknown XID +connect con1_2,localhost,root,,; +XA START '1'; +INSERT INTO t1 VALUES (2),(1); +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +SELECT * FROM t1 WHERE a = 2; +a +XA END '1'; +XA PREPARE '1'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back +XA COMMIT '1'; +disconnect con1_2; +connection default; Must be no XA PREPARE group nor XA completion one: include/show_binlog_events.inc Log_name Pos Event_type Server_id End_log_pos Info @@ -164,11 +165,6 @@ master-bin.000001 # Annotate_rows # # SELECT NEXT VALUE FOR s master-bin.000001 # Table_map # # table_id: # (test.s) master-bin.000001 # Write_rows_v1 # # table_id: # flags: STMT_END_F master-bin.000001 # Query # # COMMIT -master-bin.000001 # Gtid # # XA START X'32',X'',1 GTID #-#-# -master-bin.000001 # Query # # XA END X'32',X'',1 -master-bin.000001 # XA_prepare # # XA PREPARE X'32',X'',1 -master-bin.000001 # Gtid # # GTID #-#-# -master-bin.000001 # Query # # XA ROLLBACK X'32',X'',1 master-bin.000001 # Gtid # # GTID #-#-# master-bin.000001 # Query # # use `test`; DROP SEQUENCE `s` /* generated by server */ master-bin.000001 # Gtid # # GTID #-#-# @@ -179,8 +175,8 @@ master-bin.000001 # Gtid # # BEGIN GTID #-#-# master-bin.000001 # Query # # use `test`; INSERT INTO t1 VALUES (1) master-bin.000001 # Xid # # COMMIT /* XID */ DROP TABLE t1; -connect con2,localhost,root,,; CREATE TABLE tm (a INT PRIMARY KEY) ENGINE=MyISAM; +connect con2,localhost,root,,; XA START '1'; INSERT INTO tm VALUES (1),(1); ERROR 23000: Duplicate entry '1' for key 'PRIMARY' @@ -188,13 +184,27 @@ SELECT * FROM tm WHERE a = 2; a XA END '1'; XA PREPARE '1'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect con2; connection default; XA RECOVER; formatID gtrid_length bqual_length data -1 1 0 1 XA ROLLBACK '1'; -ERROR XA100: XA_RBROLLBACK: Transaction branch was rolled back +ERROR XAE04: XAER_NOTA: Unknown XID +connect con2_2,localhost,root,,; +XA START '1'; +INSERT INTO tm VALUES (1),(1); +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +SELECT * FROM tm WHERE a = 2; +a +XA END '1'; +XA PREPARE '1'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back +XA ROLLBACK '1'; +disconnect con2_2; +connection default; Must be no XA PREPARE group nor XA completion one: include/show_binlog_events.inc Log_name Pos Event_type Server_id End_log_pos Info @@ -210,11 +220,6 @@ master-bin.000001 # Annotate_rows # # SELECT NEXT VALUE FOR s master-bin.000001 # Table_map # # table_id: # (test.s) master-bin.000001 # Write_rows_v1 # # table_id: # flags: STMT_END_F master-bin.000001 # Query # # COMMIT -master-bin.000001 # Gtid # # XA START X'32',X'',1 GTID #-#-# -master-bin.000001 # Query # # XA END X'32',X'',1 -master-bin.000001 # XA_prepare # # XA PREPARE X'32',X'',1 -master-bin.000001 # Gtid # # GTID #-#-# -master-bin.000001 # Query # # XA ROLLBACK X'32',X'',1 master-bin.000001 # Gtid # # GTID #-#-# master-bin.000001 # Query # # use `test`; DROP SEQUENCE `s` /* generated by server */ master-bin.000001 # Gtid # # GTID #-#-# @@ -237,6 +242,22 @@ SET pseudo_slave_mode=1; XA START 'a'; XA END 'a'; XA PREPARE 'a'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back XA ROLLBACK 'a'; -ERROR XA100: XA_RBROLLBACK: Transaction branch was rolled back +ERROR XAE04: XAER_NOTA: Unknown XID include/show_binlog_events.inc +connect con_32852,localhost,root,,; +XA START 'a'; +CREATE TEMPORARY TABLE t1 (a INT) ENGINE=InnoDB; +SELECT * FROM t1; +a +disconnect con_32852; +connection default; +include/show_binlog_events.inc +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; DROP /*!40005 TEMPORARY */ TABLE IF EXISTS `t1` +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Query # # use `test`; CREATE TEMPORARY TABLE t1 (a INT) ENGINE=InnoDB +master-bin.000001 # Query # # ROLLBACK diff --git a/mysql-test/suite/binlog/r/binlog_xa_checkpoint.result b/mysql-test/suite/binlog/r/binlog_xa_checkpoint.result index d8a5818674ff3..fdccb66ac4c28 100644 --- a/mysql-test/suite/binlog/r/binlog_xa_checkpoint.result +++ b/mysql-test/suite/binlog/r/binlog_xa_checkpoint.result @@ -1,33 +1,63 @@ RESET MASTER; CREATE TABLE t1 (a INT PRIMARY KEY, b MEDIUMTEXT) ENGINE=Innodb; +# case A: Binlog CP after XAP +set @@global.max_binlog_size=4096; connect con1,localhost,root,,; -SET DEBUG_SYNC= "at_unlog_xa_prepare SIGNAL con1_ready WAIT_FOR con1_go"; +SET SESSION binlog_annotate_row_events=OFF; XA START '1'; -INSERT INTO t1 SET a=1; +INSERT INTO t1 SET a=1,b=repeat('b',@@global.max_binlog_size);; XA END '1'; -XA PREPARE '1';; +XA PREPARE '1'; +### Repeatable check block in the cases A,B. connection default; -SET DEBUG_SYNC= "now WAIT_FOR con1_ready"; -FLUSH LOGS; -FLUSH LOGS; -FLUSH LOGS; show binary logs; Log_name File_size master-bin.000001 # master-bin.000002 # -master-bin.000003 # -master-bin.000004 # +*** the old must show the prepared xa include/show_binlog_events.inc Log_name Pos Event_type Server_id End_log_pos Info -master-bin.000004 # Format_desc # # SERVER_VERSION, BINLOG_VERSION -master-bin.000004 # Gtid_list # # [#-#-#] -master-bin.000004 # Binlog_checkpoint # # master-bin.000001 -SET DEBUG_SYNC= "now SIGNAL con1_go"; -connection con1; -*** master-bin.000004 checkpoint must show up now *** +master-bin.000001 # Gtid # # XA START X'31',X'',1 GTID #-#-# +master-bin.000001 # Table_map # # table_id: # (test.t1) +master-bin.000001 # Write_rows_v1 # # table_id: # flags: STMT_END_F +master-bin.000001 # Query # # XA END X'31',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'31',X'',1 +master-bin.000001 # Rotate # # master-bin.000002;pos=POS +*** the current master-bin.000001 must show the checkpoint event with its name *** +include/show_binlog_events.inc +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000002 # Format_desc # # SERVER_VERSION, BINLOG_VERSION +master-bin.000002 # Gtid_list # # [#-#-#] +master-bin.000002 # Binlog_checkpoint # # master-bin.000001 +master-bin.000002 # Binlog_checkpoint # # master-bin.000002 +### end of check block +# case B: Binlog CP after XAC +connection default; +SET STATEMENT binlog_annotate_row_events=OFF FOR INSERT INTO t1 SET a=2,b=repeat('b', 3530); connection con1; -XA ROLLBACK '1'; -SET debug_sync = 'reset'; +XA COMMIT '1'; +### Repeatable check block in the cases A,B. +connection default; +show binary logs; +Log_name File_size +master-bin.000001 # +master-bin.000002 # +master-bin.000003 # +*** the old master-bin.000002 must show the committed xa +include/show_binlog_events.inc +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000002 # Gtid # # GTID #-#-# +master-bin.000002 # Query # # XA COMMIT X'31',X'',1 +master-bin.000002 # Rotate # # master-bin.000003;pos=POS +*** the current master-bin.000002 must show the checkpoint event with its name *** +include/show_binlog_events.inc +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000003 # Format_desc # # SERVER_VERSION, BINLOG_VERSION +master-bin.000003 # Gtid_list # # [#-#-#] +master-bin.000003 # Binlog_checkpoint # # master-bin.000002 +master-bin.000003 # Binlog_checkpoint # # master-bin.000003 +### end of check block +disconnect con1; connection default; DROP TABLE t1; -SET debug_sync = 'reset'; +set @@global.max_binlog_size=1073741824; diff --git a/mysql-test/suite/binlog/r/binlog_xa_prepared_bugs.result b/mysql-test/suite/binlog/r/binlog_xa_prepared_bugs.result new file mode 100644 index 0000000000000..77c9bb20ba532 --- /dev/null +++ b/mysql-test/suite/binlog/r/binlog_xa_prepared_bugs.result @@ -0,0 +1,50 @@ +RESET MASTER; +CREATE TABLE ta (c INT KEY) engine=Aria; +XA START 'xid_a'; +INSERT INTO ta VALUES (1); +XA END 'xid_a'; +XA PREPARE 'xid_a'; +Warnings: +Warning 1030 Got error 131 "Command not supported by the engine" from storage engine Aria +Note 4183 Found to be read-only XA transaction is rolled back +LOAD INDEX INTO CACHE c KEY(PRIMARY); +Table Op Msg_type Msg_text +test.c preload_keys Error XAER_RMFAIL: The command cannot be executed when global transaction is in the ROLLBACK ONLY state +test.c preload_keys Error XAER_RMFAIL: The command cannot be executed when global transaction is in the ROLLBACK ONLY state +test.c preload_keys error Corrupt +Warnings: +Warning 1196 Some non-transactional changed tables couldn't be rolled back +XA ROLLBACK 'xid_a'; +CREATE TABLE ti (c INT KEY) engine=Innodb; +XA START 'xid_i'; +INSERT INTO ti VALUES (1); +XA END 'xid_i'; +XA PREPARE 'xid_i'; +LOAD INDEX INTO CACHE c KEY(PRIMARY); +Table Op Msg_type Msg_text +test.c preload_keys Error XAER_RMFAIL: The command cannot be executed when global transaction is in the PREPARED state +test.c preload_keys Error XAER_RMFAIL: The command cannot be executed when global transaction is in the PREPARED state +test.c preload_keys error Corrupt +XA COMMIT 'xid_i'; +SELECT * FROM ti; +c +include/show_binlog_events.inc +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; CREATE TABLE ta (c INT KEY) engine=Aria +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Annotate_rows # # INSERT INTO ta VALUES (1) +master-bin.000001 # Table_map # # table_id: # (test.ta) +master-bin.000001 # Write_rows_v1 # # table_id: # flags: STMT_END_F +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; CREATE TABLE ti (c INT KEY) engine=Innodb +master-bin.000001 # Gtid # # XA START X'7869645f69',X'',1 GTID #-#-# +master-bin.000001 # Annotate_rows # # INSERT INTO ti VALUES (1) +master-bin.000001 # Table_map # # table_id: # (test.ti) +master-bin.000001 # Write_rows_v1 # # table_id: # flags: STMT_END_F +master-bin.000001 # Query # # XA END X'7869645f69',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'7869645f69',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA ROLLBACK X'7869645f69',X'',1 +drop table ta,ti; diff --git a/mysql-test/suite/binlog/r/binlog_xa_prepared_disconnect.result b/mysql-test/suite/binlog/r/binlog_xa_prepared_disconnect.result index a84586111bcc7..f1083450df604 100644 --- a/mysql-test/suite/binlog/r/binlog_xa_prepared_disconnect.result +++ b/mysql-test/suite/binlog/r/binlog_xa_prepared_disconnect.result @@ -11,6 +11,8 @@ XA START 'trx1tmp'; INSERT INTO tmp1 SET a=1; XA END 'trx1tmp'; XA PREPARE 'trx1tmp'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; SET @@sql_log_bin = OFF; CREATE TEMPORARY TABLE tmp1 (a int) ENGINE=innodb; @@ -18,6 +20,8 @@ XA START 'trx2tmp'; INSERT INTO tmp1 SET a=1; XA END 'trx2tmp'; XA PREPARE 'trx2tmp'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; SET @@sql_log_bin = OFF; CREATE TEMPORARY TABLE tmp1 (a int) ENGINE=innodb; @@ -25,6 +29,8 @@ XA START 'trx3tmp'; INSERT INTO tmp1 SET a=1; XA END 'trx3tmp'; XA PREPARE 'trx3tmp'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back connection default; XA COMMIT 'trx1tmp'; ERROR XAE04: XAER_NOTA: Unknown XID @@ -36,9 +42,6 @@ connection default; *** 3 prepared transactions must be in the list *** XA RECOVER; formatID gtrid_length bqual_length data -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N connection conn1tmp; disconnect conn1tmp; connection default; @@ -52,6 +55,8 @@ a 100 XA END 'trx1ro'; XA PREPARE 'trx1ro'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx2ro'; SELECT * from t ORDER BY a; @@ -59,6 +64,8 @@ a 100 XA END 'trx2ro'; XA PREPARE 'trx2ro'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx3ro'; SELECT * from t ORDER BY a; @@ -66,14 +73,12 @@ a 100 XA END 'trx3ro'; XA PREPARE 'trx3ro'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back connection default; *** 4 prepared transactions must be in the list *** XA RECOVER; formatID gtrid_length bqual_length data -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N connection conn1ro; disconnect conn1ro; connection default; @@ -84,23 +89,24 @@ connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx1empty'; XA END 'trx1empty'; XA PREPARE 'trx1empty'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx2empty'; XA END 'trx2empty'; XA PREPARE 'trx2empty'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx3empty'; XA END 'trx3empty'; XA PREPARE 'trx3empty'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back connection default; *** 5 prepared transactions must be in the list *** XA RECOVER; formatID gtrid_length bqual_length data -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N connection conn1empty; disconnect conn1empty; connection default; @@ -480,6 +486,8 @@ XA START 'trx1tmp'; INSERT INTO tmp1 SET a=1; XA END 'trx1tmp'; XA PREPARE 'trx1tmp'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; SET @@sql_log_bin = OFF; CREATE TEMPORARY TABLE tmp1 (a int) ENGINE=innodb; @@ -487,6 +495,8 @@ XA START 'trx2tmp'; INSERT INTO tmp1 SET a=1; XA END 'trx2tmp'; XA PREPARE 'trx2tmp'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; SET @@sql_log_bin = OFF; CREATE TEMPORARY TABLE tmp1 (a int) ENGINE=innodb; @@ -494,6 +504,8 @@ XA START 'trx3tmp'; INSERT INTO tmp1 SET a=1; XA END 'trx3tmp'; XA PREPARE 'trx3tmp'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back connection default; XA COMMIT 'trx1tmp'; ERROR XAE04: XAER_NOTA: Unknown XID @@ -505,9 +517,6 @@ connection default; *** 3 prepared transactions must be in the list *** XA RECOVER; formatID gtrid_length bqual_length data -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N connection conn1tmp; disconnect conn1tmp; connection default; @@ -541,6 +550,8 @@ a 100 XA END 'trx1ro'; XA PREPARE 'trx1ro'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx2ro'; SELECT * from t ORDER BY a; @@ -568,6 +579,8 @@ a 100 XA END 'trx2ro'; XA PREPARE 'trx2ro'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx3ro'; SELECT * from t ORDER BY a; @@ -595,14 +608,12 @@ a 100 XA END 'trx3ro'; XA PREPARE 'trx3ro'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back connection default; *** 4 prepared transactions must be in the list *** XA RECOVER; formatID gtrid_length bqual_length data -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N connection conn1ro; disconnect conn1ro; connection default; @@ -613,23 +624,24 @@ connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx1empty'; XA END 'trx1empty'; XA PREPARE 'trx1empty'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx2empty'; XA END 'trx2empty'; XA PREPARE 'trx2empty'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx3empty'; XA END 'trx3empty'; XA PREPARE 'trx3empty'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back connection default; *** 5 prepared transactions must be in the list *** XA RECOVER; formatID gtrid_length bqual_length data -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N connection conn1empty; disconnect conn1empty; connection default; diff --git a/mysql-test/suite/binlog/t/binlog_empty_xa_prepared.test b/mysql-test/suite/binlog/t/binlog_empty_xa_prepared.test index a34b77195e7f8..e59784b51335d 100644 --- a/mysql-test/suite/binlog/t/binlog_empty_xa_prepared.test +++ b/mysql-test/suite/binlog/t/binlog_empty_xa_prepared.test @@ -142,31 +142,60 @@ XA PREPARE '1'; --source include/wait_until_count_sessions.inc XA RECOVER; ---error ER_XA_RBROLLBACK +# the read-only xa transaction completed by disconnect +--error ER_XAER_NOTA XA COMMIT '1'; + +--connect(con1_2,localhost,root,,) +# The same as above without diconnect +XA START '1'; + --error ER_DUP_ENTRY + INSERT INTO t1 VALUES (2),(1); + SELECT * FROM t1 WHERE a = 2; +XA END '1'; +XA PREPARE '1'; +# the read-only xa transaction completes seemlessly in the native connect +--error 0 +XA COMMIT '1'; +--disconnect con1_2 + +--connection default --echo Must be no XA PREPARE group nor XA completion one: --source include/show_binlog_events.inc DROP TABLE t1; ---source include/count_sessions.inc ---connect(con2,localhost,root,,) CREATE TABLE tm (a INT PRIMARY KEY) ENGINE=MyISAM; +--source include/count_sessions.inc +--connect(con2,localhost,root,,) XA START '1'; --error ER_DUP_ENTRY INSERT INTO tm VALUES (1),(1); SELECT * FROM tm WHERE a = 2; XA END '1'; XA PREPARE '1'; - --disconnect con2 --connection default --source include/wait_until_count_sessions.inc XA RECOVER; ---error ER_XA_RBROLLBACK +# ditto to the commit +--error ER_XAER_NOTA +XA ROLLBACK '1'; + +--connect(con2_2,localhost,root,,) +XA START '1'; + --error ER_DUP_ENTRY + INSERT INTO tm VALUES (1),(1); + SELECT * FROM tm WHERE a = 2; +XA END '1'; +XA PREPARE '1'; +--error 0 XA ROLLBACK '1'; +--disconnect con2_2 + +--connection default --echo Must be no XA PREPARE group nor XA completion one: --source include/show_binlog_events.inc DROP TABLE tm; @@ -180,6 +209,21 @@ SET pseudo_slave_mode=1; XA START 'a'; XA END 'a'; XA PREPARE 'a'; ---error ER_XA_RBROLLBACK +--error ER_XAER_NOTA XA ROLLBACK 'a'; + +--source include/show_binlog_events.inc + +# MDEV-31949/MDEV-32830/MDEV-32852 +# Prove exceptional empty transaction binlogging in the following case: +--connect(con_32852,localhost,root,,) + +--let $binlog_start = query_get_value(SHOW MASTER STATUS, Position, 1) +--let $binlog_file = query_get_value(SHOW MASTER STATUS, File, 1) +XA START 'a'; +CREATE TEMPORARY TABLE t1 (a INT) ENGINE=InnoDB; +SELECT * FROM t1; +--disconnect con_32852 + +--connection default --source include/show_binlog_events.inc diff --git a/mysql-test/suite/binlog/t/binlog_xa_checkpoint.test b/mysql-test/suite/binlog/t/binlog_xa_checkpoint.test index b208d02cf2a53..c57360d1b7cc1 100644 --- a/mysql-test/suite/binlog/t/binlog_xa_checkpoint.test +++ b/mysql-test/suite/binlog/t/binlog_xa_checkpoint.test @@ -1,57 +1,146 @@ +# +# MDEV-32830/MDEV-31949 +# The test proves Binlog-Checkpoint events are written upon binlog rotation +# caused by XA-PREPARE (the case A) or XA-COMMIT (the case B). +# --source include/have_innodb.inc ---source include/have_debug.inc ---source include/have_debug_sync.inc --source include/have_binlog_format_row.inc RESET MASTER; CREATE TABLE t1 (a INT PRIMARY KEY, b MEDIUMTEXT) ENGINE=Innodb; -# Test that -# 1. XA PREPARE is binlogged before the XA has been prepared in Engine -# 2. While XA PREPARE already binlogged in an old binlog file which has been rotated, -# Binlog checkpoint is not generated for the latest log until -# XA PREPARE returns, e.g OK to the client. +--echo # case A: Binlog CP after XAP +# Test that XA PREPARE is binlogged to cause binlog rotation (Proof 1) +# followed by Binlog checkpoint event (Proof 2) for the old log file, +# to comply with the normal transaction commit pattern. +# When the server crashes after that the prepared XA will be found +# at restart in the engine as well. - -# con1 will hang before doing commit checkpoint, blocking RESET MASTER. +--let $save_max_binlog_size= `SELECT @@global.max_binlog_size` +set @@global.max_binlog_size=4096; +--let $pk_ins= 1 +--let $binlog_0= query_get_value(SHOW MASTER STATUS, File, 1) +--let $pre_xa_pos= query_get_value(SHOW MASTER STATUS, Position, 1) connect(con1,localhost,root,,); -SET DEBUG_SYNC= "at_unlog_xa_prepare SIGNAL con1_ready WAIT_FOR con1_go"; +SET SESSION binlog_annotate_row_events=OFF; XA START '1'; -INSERT INTO t1 SET a=1; +--eval INSERT INTO t1 SET a=1,b=repeat('b',@@global.max_binlog_size); XA END '1'; ---send XA PREPARE '1'; +XA PREPARE '1'; +--echo ### Repeatable check block in the cases A,B. +# +# Proof 1: +# connection default; -SET DEBUG_SYNC= "now WAIT_FOR con1_ready"; -FLUSH LOGS; -FLUSH LOGS; -FLUSH LOGS; +# the Rotate event signature in the SBE Info field +--let $wait_binlog_event= ;pos=4 +--let $wait_binlog_file=$binlog_0 +--source include/wait_for_binlog_event.inc +# +# Proof 2: +# +# looping is very unlikely but treat it conservatively that is safe +--let $_max_wait_count= 300 +let $binlog_1= $binlog_0; +while (`select STRCMP("$binlog_0", "$binlog_1") = 0`) +{ + --dec $_max_wait_count + if (!$_max_wait_count) + { + --die Got $binlog_1 SHOW MASTER STATUS which is the old $binlog_0 + } + --real_sleep 0.1 + --let $binlog_1= query_get_value(SHOW MASTER STATUS, File, 1) +} +# new binlog reigns, prove/wait for its predessor's irrelevance for recovery +--source include/wait_for_binlog_checkpoint.inc +# .. so the server can crash-restart with the prepared XA: +# list of the logs +# events in the pre-ultimate and the current one follow. --source include/show_binary_logs.inc ---let $binlog_file= master-bin.000004 +--echo *** the old $binlog_file must show the prepared xa +--let $binlog_file= $binlog_0 +--let $binlog_start= $pre_xa_pos +--source include/show_binlog_events.inc +--echo *** the current $binlog_file must show the checkpoint event with its name *** --let $binlog_start= 4 +--let $binlog_file= $binlog_1 --source include/show_binlog_events.inc +--echo ### end of check block -SET DEBUG_SYNC= "now SIGNAL con1_go"; +--echo # case B: Binlog CP after XAC +# Similar proof to XA COMMIT. +# *estimate* the table map size and the row event header, +# fill the rest of the following Rows_log_event with data, approaching +# the end-of-binlog offset to its max, not overstepping though (!) (see -19). +# This test is (practially) future-prove if when the estimate will turn out +# too low later. +--let $curr_pos= query_get_value(SHOW MASTER STATUS, Position, 1) +--let $gtid_size=40 +--let $tm_size= 60 +--let $rows_hdr= 10 +--let $xid_size= 50 +--let $insert_length= `select @@max_binlog_size - ($curr_pos + $gtid_size + $tm_size + $rows_hdr + 0 + $xid_size) - 19` +connection default; +--inc $pk_ins +--eval SET STATEMENT binlog_annotate_row_events=OFF FOR INSERT INTO t1 SET a=$pk_ins,b=repeat('b', $insert_length) +--let $pre_xa_commit_file= query_get_value(SHOW MASTER STATUS, File, 1) +if (`select strcmp("$binlog_1", "$pre_xa_commit_file")`) +{ + --die unxpected (rotated?) $pre_xa_commit_file end-of-binlog while the correct must be $binlog_1 +} +--let $pre_xa_pos= query_get_value(SHOW MASTER STATUS, Position, 1) connection con1; -reap; ---echo *** master-bin.000004 checkpoint must show up now *** ---source include/wait_for_binlog_checkpoint.inc +XA COMMIT '1'; -# Todo: think about the error code returned, move to an appropriate test, or remove -# connection default; -#--error 1399 -# DROP TABLE t1; +--echo ### Repeatable check block in the cases A,B. +# +# Proof 1: +# +connection default; +--let $wait_binlog_event= ;pos=4 +--let $wait_binlog_file= $binlog_1 +--source include/wait_for_binlog_event.inc -connection con1; -XA ROLLBACK '1'; -SET debug_sync = 'reset'; +# +# Proof 2 (ditto looping) +# +--let $_max_wait_count= 300 +--let $binlog_2= $binlog_1 +while (`select STRCMP("$binlog_1", "$binlog_2") = 0`) +{ + --dec $_max_wait_count + if (!$_max_wait_count) + { + --die Got $binlog_2 SHOW MASTER STATUS which is the old $binlog_1 + } + --real_sleep 0.1 + --let $binlog_2= query_get_value(SHOW MASTER STATUS, File, 1) +} +# another new binlog reigns, prove/wait for its predessor's irrelevance for recovery.. +--source include/wait_for_binlog_checkpoint.inc + +# .. so the server can crash-restart with the committed XA +# events in the pre-ultimate log. Display that. +--source include/show_binary_logs.inc +--echo *** the old $binlog_file must show the committed xa +--let $binlog_file= $binlog_1 +--let $binlog_start= $pre_xa_pos +--source include/show_binlog_events.inc +--echo *** the current $binlog_file must show the checkpoint event with its name *** +--let $binlog_start= 4 +--let $binlog_file= $binlog_2 +--source include/show_binlog_events.inc +--echo ### end of check block # Clean up. +--disconnect con1 connection default; DROP TABLE t1; -SET debug_sync = 'reset'; +--eval set @@global.max_binlog_size=$save_max_binlog_size diff --git a/mysql-test/suite/binlog/t/binlog_xa_prepared_bugs.test b/mysql-test/suite/binlog/t/binlog_xa_prepared_bugs.test new file mode 100644 index 0000000000000..5d49e6ed77b2c --- /dev/null +++ b/mysql-test/suite/binlog/t/binlog_xa_prepared_bugs.test @@ -0,0 +1,36 @@ +--source include/have_binlog_format_row.inc +--source include/have_innodb.inc + +RESET MASTER; +--let $binlog_start = query_get_value(SHOW MASTER STATUS, Position, 1) +--let $binlog_file = query_get_value(SHOW MASTER STATUS, File, 1) + +CREATE TABLE ta (c INT KEY) engine=Aria; +XA START 'xid_a'; +INSERT INTO ta VALUES (1); +XA END 'xid_a'; +XA PREPARE 'xid_a'; + +# sort out the correct code by MDEV-32455 fixes +--error 0,ER_XAER_RMFAIL +LOAD INDEX INTO CACHE c KEY(PRIMARY); +--error 0 +XA ROLLBACK 'xid_a'; + +CREATE TABLE ti (c INT KEY) engine=Innodb; +XA START 'xid_i'; +INSERT INTO ti VALUES (1); +XA END 'xid_i'; +XA PREPARE 'xid_i'; + +--error 0,ER_XAER_RMFAIL +LOAD INDEX INTO CACHE c KEY(PRIMARY); + +--error 0 +XA COMMIT 'xid_i'; +SELECT * FROM ti; + +# +--source include/show_binlog_events.inc + +drop table ta,ti; diff --git a/mysql-test/suite/rpl/include/rpl_xa_concurrent_2pc.inc b/mysql-test/suite/rpl/include/rpl_xa_concurrent_2pc.inc new file mode 100644 index 0000000000000..1e7e9c0c6dfec --- /dev/null +++ b/mysql-test/suite/rpl/include/rpl_xa_concurrent_2pc.inc @@ -0,0 +1,334 @@ +# +# Helper file to run the 1-4(a,b) test cases for rpl_xa_concurrent_2pc, +# with either XA COMMIT or XA ROLLBACK used to complete XA transactions. +# +# Parameters +# $xa_complete_sym (string) : COMMIT or ROLLBACK, the action used to complete +# a prepared XA transaction +# + +if (!$xa_complete_sym) +{ + die MTR variable xa_complete_sym not specified, must be either COMMIT or ROLLBACK; +} + +--let $is_xac= 0 +--let $is_xar= 0 + +if (`SELECT strcmp("COMMIT", "$xa_complete_sym") = 0`) +{ + --let $is_xac= 1 +} + +if (`SELECT strcmp("ROLLBACK", "$xa_complete_sym") = 0`) +{ + --let $is_xar= 1 +} + +if (`SELECT !$is_xar && !$is_xac`) +{ + die MTR variable xa_complete_sym invalid, must be either COMMIT or ROLLBACK; +} + + +--echo # +--echo # Initialize test data +--connection slave +--source include/stop_slave.inc + +--connection master +create table t1 (a int primary key, b int) engine=innodb; + +# Slave locks this row before updates to pause transaction progress +--let $hold_row= -1 +--let $t1_ctr= 0 +--eval insert into t1 values ($hold_row, 0) +--source include/save_master_gtid.inc + +--connection slave +--source include/start_slave.inc +--source include/sync_with_master_gtid.inc +--source include/stop_slave.inc +set @save_debug= @@GLOBAL.debug_dbug; +set @save_par_thds= @@GLOBAL.slave_parallel_threads; +set @save_par_mode= @@GLOBAL.slave_parallel_mode; +set @@GLOBAL.slave_parallel_threads= 4; +set @@GLOBAL.slave_parallel_mode= optimistic; + +set statement sql_log_bin=0 for call mtr.add_suppression("Commit failed due to failure of an earlier commit on which this one depends"); + +--echo # +--echo # Test Case 1-$xa_complete_sym: +--echo # If two XA $xa_complete_sym transactions have different +--echo # XIDs, ensure both phases of both transactions all execute concurrently. +--echo # + +--connection slave +# Stop both XAP after their binlogging and before their engine changing +set @@global.debug_dbug= "+d,stop_before_binlog_prepare,stop_after_binlog_prepare"; + +--connection master +--eval set @@session.gtid_seq_no= 200 + ($is_xar * 1000) +XA START 'x1'; +--eval insert into t1 values ($t1_ctr, 0) +--inc $t1_ctr +XA END 'x1'; +XA PREPARE 'x1'; +--eval XA $xa_complete_sym 'x1' + +XA START 'x2'; +--eval insert into t1 values ($t1_ctr, 0) +--inc $t1_ctr +XA END 'x2'; +XA PREPARE 'x2'; +--eval XA $xa_complete_sym 'x2' + +--connection slave +--source include/start_slave.inc + +# Prove $count_wait number of the workers in W4PT2C. +# It's just 1 with MDEV-33668 that schedules XAC(xid) to a XAP(xid) worker +--let $count_wait= 1 +--let $wait_condition=SELECT count(*) = $count_wait FROM information_schema.processlist WHERE command = 'Slave_worker' AND state LIKE "Waiting for prior transaction to commit" +--source include/wait_condition.inc +# .. while the seq_no 200 XAP is dsync-delayed +--let $count_wait= 1 +--let $wait_condition=SELECT count(*) = $count_wait FROM information_schema.processlist WHERE command = 'Slave_worker' AND state LIKE "debug sync point: now" +--source include/wait_condition.inc +--echo must be \Empty +XA RECOVER; +set debug_sync= "now signal binlog_xap"; + +--echo # Make a stop at a point where XA PREPARE (both) have completed (binlogged): +set debug_sync= "now wait_for xa_prepare_binlogged"; + +--echo # prove XA-$xa_complete_sym 'x1' has completed too +--let $xid = query_get_value("XA RECOVER", data, 1) +--eval SELECT '$xid' = 'x1' + +--echo # cleanup: release the seq_no 200 XAP +--let $wait_condition=SELECT count(*) = 1 FROM information_schema.processlist WHERE command = 'Slave_worker' AND state LIKE "debug sync point: now" +--source include/wait_condition.inc +set debug_sync= "now signal continue_xap"; + +--echo # Proof of the full master-slave sync +--source include/sync_with_master_gtid.inc + +--let $diff_tables=master:test.t1, slave:test.t1 +--source include/diff_tables.inc + +--connection slave +--source include/stop_slave.inc +set @@global.debug_dbug= @save_debug; + + +--echo # +--echo # Test Case 2-$xa_complete_sym: +--echo # Two concurrent 2-phase XA transactions with matching XIDs +--echo # should run one after the other. +--echo # +--connection slave +set @@global.debug_dbug= "+d,stop_before_binlog_prepare,stop_after_binlog_prepare"; + +--connection master +--eval set @@session.gtid_seq_no= 300 + ($is_xar * 1000) +XA START 'x'; +--eval insert into t1 values ($t1_ctr, 1) +--inc $t1_ctr +XA END 'x'; +XA PREPARE 'x'; +--eval XA $xa_complete_sym 'x' + +XA START 'x'; +--eval insert into t1 values ($t1_ctr, 2) +--inc $t1_ctr +XA END 'x'; +XA PREPARE 'x'; +--eval XA $xa_complete_sym 'x' +--source include/save_master_gtid.inc + +--connection slave +# remember the slave's initial binlog offset +--let $binlog_pos=query_get_value(SHOW MASTER STATUS, Position, 1) +--let $binlog_file=query_get_value(SHOW MASTER STATUS, File, 1) +--source include/start_slave.inc + +--echo # states of workers at the first XAP near binlogging are expected to be as the following +--let $count_wait= 3 +--let $wait_condition=SELECT count(*) = $count_wait FROM information_schema.processlist WHERE command = 'Slave_worker' AND state LIKE "Waiting for prior transaction to commit" +set debug_sync= "now signal binlog_xap"; + +--echo # Once the first XA PREPARE has binlogged.. +set debug_sync= "now wait_for xa_prepare_binlogged"; + +# with MDEV-33668 the first of the two XAP:s is done now +--let $xid = query_get_value("XA RECOVER", data, 1) +--eval SELECT '$xid' = 'x' + +--echo # cleanup: signal XA PREPARE to leave the stage +set debug_sync= "now signal continue_xap"; + +--echo # Proof of the full master-slave sync +--source include/sync_with_master_gtid.inc +--let $diff_tables=master:test.t1, slave:test.t1 +--source include/diff_tables.inc + +--connection slave +--source include/stop_slave.inc +set @@global.debug_dbug= @save_debug; + + +--echo # +--echo # Test Case 3-$xa_complete_sym (Error Case): +--echo # If an XA PREPARE errors while its +--echo # XA $xa_complete_sym is waiting on it, both phases should rollback +--echo # successfully. Note this tests both: +--echo # a) XA $xa_complete_sym is waiting in group commit (first phase +--echo # times out in DMLs) +--echo # b) XA $xa_complete_sym is waiting in group commit, with another XAP +--echo # with a duplicate XID waiting on it. + +--echo # Case a) +--echo # Ensure slave is stopped +--connection slave +--source include/wait_for_slave_to_stop.inc +set @save_lock_wait_timeout= @@GLOBAL.innodb_lock_wait_timeout; +set @save_trans_retries= @@GLOBAL.slave_transaction_retries; +set @@global.innodb_lock_wait_timeout= 1; +set @@global.slave_transaction_retries= 0; + +--connection master +XA START 'x'; +--eval update t1 set b=b+1 where a=$hold_row +XA END 'x'; +XA PREPARE 'x'; +--eval XA $xa_complete_sym 'x' +--source include/save_master_gtid.inc + +--connection slave1 +BEGIN; +--eval select * from t1 where a=$hold_row for update; + +--connection slave +--source include/start_slave.inc + +--let $slave_sql_errno= 1205 +--source include/wait_for_slave_sql_error.inc + +--connection slave1 +ROLLBACK; + +--connection slave +# Stop the IO thread too +--source include/stop_slave_io.inc +set @@global.innodb_lock_wait_timeout= @save_lock_wait_timeout; +set @@global.slave_transaction_retries= @save_trans_retries; + +--echo # Ensure on slave restart, we can re-execute the XA transaction +--source include/start_slave.inc +--source include/save_master_gtid.inc +--source include/stop_slave.inc +set @@global.debug_dbug= @save_debug; + + +--echo # Case b) +--echo # Ensure slave is stopped +--connection slave +--source include/wait_for_slave_to_stop.inc +set @save_lock_wait_timeout= @@GLOBAL.innodb_lock_wait_timeout; +set @save_trans_retries= @@GLOBAL.slave_transaction_retries; +set @@global.innodb_lock_wait_timeout= 1; +set @@global.slave_transaction_retries= 0; + +--connection master +XA START 'x'; +--eval update t1 set b=b+1 where a=$hold_row +XA END 'x'; +XA PREPARE 'x'; +--eval XA $xa_complete_sym 'x' + +XA START 'x'; +--eval insert into t1 values ($t1_ctr, 0) +--let $new_row_idx= $t1_ctr +--inc $t1_ctr +XA END 'x'; +XA PREPARE 'x'; +--source include/save_master_gtid.inc +--eval XA $xa_complete_sym 'x' + +--connection slave1 +BEGIN; +--eval select * from t1 where a=$hold_row for update; + +--connection slave +--source include/start_slave.inc + +--let $slave_sql_errno= 1205 +--source include/wait_for_slave_sql_error.inc + +--connection slave1 +ROLLBACK; + +--echo # There should not be any prepared rows seen by XA RECOVER +XA RECOVER; + +--echo # Ensuring data from second XAP isn't visible.. +if (`select count(*) from t1 where a=$new_row_idx`) +{ + --die Failed, row exists +} +--echo # ..done + +--connection slave +--source include/stop_slave_io.inc +set @@global.innodb_lock_wait_timeout= @save_lock_wait_timeout; +set @@global.slave_transaction_retries= @save_trans_retries; + +--echo # Ensure on slave restart, we can re-execute the XA transaction +--source include/start_slave.inc +--source include/save_master_gtid.inc +--source include/stop_slave.inc +set @@global.debug_dbug= @save_debug; +--source include/start_slave.inc + +--echo # Ensuring data from second XAP is visible.. +if ($is_xac) +{ + --let $expected_row_count= 1 +} +if ($is_xar) +{ + --let $expected_row_count= 0 +} +if (`select count(*) != $expected_row_count from t1 where a=$new_row_idx`) +{ + --die Failed, XA $xa_complete_sym was not observed +} +--echo # ..done + +# +--echo # Cleanup +# + + +--connection slave +set debug_sync='RESET'; +set @@global.debug_dbug= @save_debug; +--source include/start_slave.inc + +--connection master +DROP TABLE t1; +--source include/save_master_gtid.inc +--let $binlog_file=query_get_value(SHOW MASTER STATUS, File, 1) +--source include/show_binlog_events.inc + +--connection slave +--source include/sync_with_master_gtid.inc +--let $binlog_file=query_get_value(SHOW MASTER STATUS, File, 1) +--let $filter_cid=1 +--source include/show_binlog_events2.inc + +--source include/stop_slave.inc +set @@GLOBAL.slave_parallel_threads= @save_par_thds; +set @@GLOBAL.slave_parallel_mode= @save_par_mode; +--source include/start_slave.inc diff --git a/mysql-test/suite/rpl/include/rpl_xa_empty_transaction_test_case.inc b/mysql-test/suite/rpl/include/rpl_xa_empty_transaction_test_case.inc index 6368336b8e36c..b718bbdaf345d 100644 --- a/mysql-test/suite/rpl/include/rpl_xa_empty_transaction_test_case.inc +++ b/mysql-test/suite/rpl/include/rpl_xa_empty_transaction_test_case.inc @@ -78,7 +78,7 @@ if ($use_disconnect) --source include/wait_until_count_sessions.inc XA RECOVER; - --error ER_XA_RBROLLBACK + --error ER_XAER_NOTA --eval XA $xa_completion_action 'x'; } if (!$use_disconnect) diff --git a/mysql-test/suite/rpl/include/rpl_xa_mixed_engines.inc b/mysql-test/suite/rpl/include/rpl_xa_mixed_engines.inc index 0707a04090acf..aae9324818d12 100644 --- a/mysql-test/suite/rpl/include/rpl_xa_mixed_engines.inc +++ b/mysql-test/suite/rpl/include/rpl_xa_mixed_engines.inc @@ -169,6 +169,7 @@ if ($command == run) --eval XA START '$xid' UPDATE t SET a = 99 where a = -1; + SELECT ROW_COUNT(); --eval XA END '$xid' if ($xa_prepare_opt) { diff --git a/mysql-test/suite/rpl/r/rpl_xa.result b/mysql-test/suite/rpl/r/rpl_xa.result index 5ed7ab5e27957..33bbaecbc693f 100644 --- a/mysql-test/suite/rpl/r/rpl_xa.result +++ b/mysql-test/suite/rpl/r/rpl_xa.result @@ -59,6 +59,8 @@ xa start 'ro_2'; select count(*) into @s2 from t1; xa end 'ro_2'; xa prepare 'ro_2';; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect master_ro_2; connection master; connect master_$xid, 127.0.0.1,root,,$db,$MASTER_MYPORT,; @@ -68,6 +70,8 @@ select 1; 1 xa end 'ro_1'; xa prepare 'ro_1';; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect master_ro_1; connection master; connect master_$xid, 127.0.0.1,root,,$db,$MASTER_MYPORT,; @@ -75,6 +79,8 @@ xa start 'ro_2'; select count(*) into @s2 from t1; xa end 'ro_2'; xa prepare 'ro_2';; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect master_ro_2; connection master; connect master_$xid, 127.0.0.1,root,,$db,$MASTER_MYPORT,; @@ -84,17 +90,17 @@ select 1; 1 xa end 'ro_1'; xa prepare 'ro_1';; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect master_ro_1; -*** 2 prepared xa:s must be in the list: +*** Zero prepared xa:s must be in the list: connection master; xa recover; formatID gtrid_length bqual_length data -1 4 0 ro_1 -1 4 0 ro_2 *** Zero prepared xa:s must be in the list: xa recover; formatID gtrid_length bqual_length data -*** At the end of read-only section gtid list has 0 more compare with previous check: +*** At the end of the above read-only section gtid list must not change from the previous check: flush logs; show binlog events in 'master-bin.000003' limit 1,1; Log_name Pos Event_type Server_id End_log_pos Info @@ -114,15 +120,18 @@ connection master; xa recover; formatID gtrid_length bqual_length data 1 12 0 rw_no_binlog +set @@session.sql_log_bin = 0; +xa $complete '$xid'; +set @@session.sql_log_bin = 1; *** Zero must be in the list: connection master; xa recover; formatID gtrid_length bqual_length data -*** At the end of --binlog-ignore-db section gtid list has 2 more: +*** At the end of --binlog-ignore-db section gtid list remains the same. flush logs; show binlog events in 'master-bin.000004' limit 1,1; Log_name Pos Event_type Server_id End_log_pos Info -master-bin.000004 # Gtid_list 1 # [0-1-13] +master-bin.000004 # Gtid_list 1 # [0-1-11] connection master; create table t3 (a int) engine=innodb; *** the disconnected prepare case @@ -162,7 +171,7 @@ formatID gtrid_length bqual_length data flush logs; show binlog events in 'master-bin.000005' limit 1,1; Log_name Pos Event_type Server_id End_log_pos Info -master-bin.000005 # Gtid_list 1 # [0-1-18] +master-bin.000005 # Gtid_list 1 # [0-1-16] create table tm (a int) engine=myisam; connection master; connect master_$xid, 127.0.0.1,root,,$db,$MASTER_MYPORT,; @@ -170,6 +179,8 @@ xa start 'rw_myisam'; insert into tm set a=1; xa end 'rw_myisam'; xa prepare 'rw_myisam';; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect master_rw_myisam; connection master; connect master_$xid, 127.0.0.1,root,,$db,$MASTER_MYPORT,; @@ -177,20 +188,30 @@ xa start 'rw_myisam'; insert into tm set a=1; xa end 'rw_myisam'; xa prepare 'rw_myisam';; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect master_rw_myisam; *** rw_myisam prepared must be in the list: connection master; xa recover; formatID gtrid_length bqual_length data -1 9 0 rw_myisam *** Zero prepared xa:s must be in the list: xa recover; formatID gtrid_length bqual_length data -*** At the end of MyISAM "xa" section gtid list has 7 more compare with previous check: +*** At the end of MyISAM "xa" section gtid list has 3 more compare with previous check: flush logs; show binlog events in 'master-bin.000006' limit 1,1; Log_name Pos Event_type Server_id End_log_pos Info -master-bin.000006 # Gtid_list 1 # [0-1-25] +master-bin.000006 # Gtid_list 1 # [0-1-19] +set @@session.sql_log_bin = OFF; +connect master_$xid, 127.0.0.1,root,,$db,$MASTER_MYPORT,; +xa start 'skip_binlog'; +insert into t2 values(1); +xa end 'skip_binlog'; +xa prepare 'skip_binlog';; +xa $complete '$xid'; +set @@session.sql_log_bin = ON; +disconnect master_skip_binlog; connect master_$xid, 127.0.0.1,root,,$db,$MASTER_MYPORT,; set @@session.sql_log_bin = OFF; xa start 'skip_binlog'; @@ -215,7 +236,7 @@ formatID gtrid_length bqual_length data flush logs; show binlog events in 'master-bin.000007' limit 1,1; Log_name Pos Event_type Server_id End_log_pos Info -master-bin.000007 # Gtid_list 1 # [0-1-25] +master-bin.000007 # Gtid_list 1 # [0-1-21] connection slave; include/sync_with_master_gtid.inc connection master; diff --git a/mysql-test/suite/rpl/r/rpl_xa_concurrent_2pc.result b/mysql-test/suite/rpl/r/rpl_xa_concurrent_2pc.result new file mode 100644 index 0000000000000..6e9c6b0564da9 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_xa_concurrent_2pc.result @@ -0,0 +1,780 @@ +include/master-slave.inc +[connection master] +# +# Initialize test data +connection slave; +include/stop_slave.inc +connection master; +create table t1 (a int primary key, b int) engine=innodb; +insert into t1 values (-1, 0); +include/save_master_gtid.inc +connection slave; +include/start_slave.inc +include/sync_with_master_gtid.inc +include/stop_slave.inc +set @save_debug= @@GLOBAL.debug_dbug; +set @save_par_thds= @@GLOBAL.slave_parallel_threads; +set @save_par_mode= @@GLOBAL.slave_parallel_mode; +set @@GLOBAL.slave_parallel_threads= 4; +set @@GLOBAL.slave_parallel_mode= optimistic; +set statement sql_log_bin=0 for call mtr.add_suppression("Commit failed due to failure of an earlier commit on which this one depends"); +# +# Test Case 1-COMMIT: +# If two XA COMMIT transactions have different +# XIDs, ensure both phases of both transactions all execute concurrently. +# +connection slave; +set @@global.debug_dbug= "+d,stop_before_binlog_prepare,stop_after_binlog_prepare"; +connection master; +set @@session.gtid_seq_no= 200 + (0 * 1000); +XA START 'x1'; +insert into t1 values (0, 0); +XA END 'x1'; +XA PREPARE 'x1'; +XA COMMIT 'x1'; +XA START 'x2'; +insert into t1 values (1, 0); +XA END 'x2'; +XA PREPARE 'x2'; +XA COMMIT 'x2'; +connection slave; +include/start_slave.inc +must be \Empty +XA RECOVER; +formatID gtrid_length bqual_length data +set debug_sync= "now signal binlog_xap"; +# Make a stop at a point where XA PREPARE (both) have completed (binlogged): +set debug_sync= "now wait_for xa_prepare_binlogged"; +# prove XA-COMMIT 'x1' has completed too +SELECT 'x1' = 'x1'; +'x1' = 'x1' +1 +# cleanup: release the seq_no 200 XAP +set debug_sync= "now signal continue_xap"; +# Proof of the full master-slave sync +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:test.t1, slave:test.t1] +connection slave; +include/stop_slave.inc +set @@global.debug_dbug= @save_debug; +# +# Test Case 2-COMMIT: +# Two concurrent 2-phase XA transactions with matching XIDs +# should run one after the other. +# +connection slave; +set @@global.debug_dbug= "+d,stop_before_binlog_prepare,stop_after_binlog_prepare"; +connection master; +set @@session.gtid_seq_no= 300 + (0 * 1000); +XA START 'x'; +insert into t1 values (2, 1); +XA END 'x'; +XA PREPARE 'x'; +XA COMMIT 'x'; +XA START 'x'; +insert into t1 values (3, 2); +XA END 'x'; +XA PREPARE 'x'; +XA COMMIT 'x'; +include/save_master_gtid.inc +connection slave; +include/start_slave.inc +# states of workers at the first XAP near binlogging are expected to be as the following +set debug_sync= "now signal binlog_xap"; +# Once the first XA PREPARE has binlogged.. +set debug_sync= "now wait_for xa_prepare_binlogged"; +SELECT 'x' = 'x'; +'x' = 'x' +1 +# cleanup: signal XA PREPARE to leave the stage +set debug_sync= "now signal continue_xap"; +# Proof of the full master-slave sync +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:test.t1, slave:test.t1] +connection slave; +include/stop_slave.inc +set @@global.debug_dbug= @save_debug; +# +# Test Case 3-COMMIT (Error Case): +# If an XA PREPARE errors while its +# XA COMMIT is waiting on it, both phases should rollback +# successfully. Note this tests both: +# a) XA COMMIT is waiting in group commit (first phase +# times out in DMLs) +# b) XA COMMIT is waiting in group commit, with another XAP +# with a duplicate XID waiting on it. +# Case a) +# Ensure slave is stopped +connection slave; +include/wait_for_slave_to_stop.inc +set @save_lock_wait_timeout= @@GLOBAL.innodb_lock_wait_timeout; +set @save_trans_retries= @@GLOBAL.slave_transaction_retries; +set @@global.innodb_lock_wait_timeout= 1; +set @@global.slave_transaction_retries= 0; +connection master; +XA START 'x'; +update t1 set b=b+1 where a=-1; +XA END 'x'; +XA PREPARE 'x'; +XA COMMIT 'x'; +include/save_master_gtid.inc +connection slave1; +BEGIN; +select * from t1 where a=-1 for update;; +a b +-1 0 +connection slave; +include/start_slave.inc +include/wait_for_slave_sql_error.inc [errno=1205] +connection slave1; +ROLLBACK; +connection slave; +include/stop_slave_io.inc +set @@global.innodb_lock_wait_timeout= @save_lock_wait_timeout; +set @@global.slave_transaction_retries= @save_trans_retries; +# Ensure on slave restart, we can re-execute the XA transaction +include/start_slave.inc +include/save_master_gtid.inc +include/stop_slave.inc +set @@global.debug_dbug= @save_debug; +# Case b) +# Ensure slave is stopped +connection slave; +include/wait_for_slave_to_stop.inc +set @save_lock_wait_timeout= @@GLOBAL.innodb_lock_wait_timeout; +set @save_trans_retries= @@GLOBAL.slave_transaction_retries; +set @@global.innodb_lock_wait_timeout= 1; +set @@global.slave_transaction_retries= 0; +connection master; +XA START 'x'; +update t1 set b=b+1 where a=-1; +XA END 'x'; +XA PREPARE 'x'; +XA COMMIT 'x'; +XA START 'x'; +insert into t1 values (4, 0); +XA END 'x'; +XA PREPARE 'x'; +include/save_master_gtid.inc +XA COMMIT 'x'; +connection slave1; +BEGIN; +select * from t1 where a=-1 for update;; +a b +-1 1 +connection slave; +include/start_slave.inc +include/wait_for_slave_sql_error.inc [errno=1205] +connection slave1; +ROLLBACK; +# There should not be any prepared rows seen by XA RECOVER +XA RECOVER; +formatID gtrid_length bqual_length data +# Ensuring data from second XAP isn't visible.. +# ..done +connection slave; +include/stop_slave_io.inc +set @@global.innodb_lock_wait_timeout= @save_lock_wait_timeout; +set @@global.slave_transaction_retries= @save_trans_retries; +# Ensure on slave restart, we can re-execute the XA transaction +include/start_slave.inc +include/save_master_gtid.inc +include/stop_slave.inc +set @@global.debug_dbug= @save_debug; +include/start_slave.inc +# Ensuring data from second XAP is visible.. +# ..done +# Cleanup +connection slave; +set debug_sync='RESET'; +set @@global.debug_dbug= @save_debug; +include/start_slave.inc +Warnings: +Note 1254 Slave is already running +connection master; +DROP TABLE t1; +include/save_master_gtid.inc +include/show_binlog_events.inc +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; create table t1 (a int primary key, b int) engine=innodb +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (-1, 0) +master-bin.000001 # Xid # # COMMIT /* XID */ +master-bin.000001 # Gtid # # XA START X'7831',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (0, 0) +master-bin.000001 # Query # # XA END X'7831',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'7831',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'7831',X'',1 +master-bin.000001 # Gtid # # XA START X'7832',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (1, 0) +master-bin.000001 # Query # # XA END X'7832',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'7832',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'7832',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (2, 1) +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'78',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (3, 2) +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'78',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; update t1 set b=b+1 where a=-1 +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'78',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; update t1 set b=b+1 where a=-1 +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'78',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (4, 0) +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; DROP TABLE `t1` /* generated by server */ +connection slave; +include/sync_with_master_gtid.inc +show binlog events in 'slave-bin.000001' from ; +Log_name Pos Event_type Server_id End_log_pos Info +slave-bin.000001 # Gtid_list 2 # [] +slave-bin.000001 # Binlog_checkpoint 2 # slave-bin.000001 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; create table t1 (a int primary key, b int) engine=innodb +slave-bin.000001 # Gtid 1 # BEGIN GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; insert into t1 values (-1, 0) +slave-bin.000001 # Xid 1 # COMMIT /* XID */ +slave-bin.000001 # Gtid 1 # XA START X'7831',X'',1 GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; insert into t1 values (0, 0) +slave-bin.000001 # Query 1 # XA END X'7831',X'',1 +slave-bin.000001 # XA_prepare 1 # XA PREPARE X'7831',X'',1 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # XA COMMIT X'7831',X'',1 +slave-bin.000001 # Gtid 1 # XA START X'7832',X'',1 GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; insert into t1 values (1, 0) +slave-bin.000001 # Query 1 # XA END X'7832',X'',1 +slave-bin.000001 # XA_prepare 1 # XA PREPARE X'7832',X'',1 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # XA COMMIT X'7832',X'',1 +slave-bin.000001 # Gtid 1 # XA START X'78',X'',1 GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; insert into t1 values (2, 1) +slave-bin.000001 # Query 1 # XA END X'78',X'',1 +slave-bin.000001 # XA_prepare 1 # XA PREPARE X'78',X'',1 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # XA COMMIT X'78',X'',1 +slave-bin.000001 # Gtid 1 # XA START X'78',X'',1 GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; insert into t1 values (3, 2) +slave-bin.000001 # Query 1 # XA END X'78',X'',1 +slave-bin.000001 # XA_prepare 1 # XA PREPARE X'78',X'',1 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # XA COMMIT X'78',X'',1 +slave-bin.000001 # Gtid 1 # XA START X'78',X'',1 GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; update t1 set b=b+1 where a=-1 +slave-bin.000001 # Query 1 # XA END X'78',X'',1 +slave-bin.000001 # XA_prepare 1 # XA PREPARE X'78',X'',1 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # XA COMMIT X'78',X'',1 +slave-bin.000001 # Gtid 1 # XA START X'78',X'',1 GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; update t1 set b=b+1 where a=-1 +slave-bin.000001 # Query 1 # XA END X'78',X'',1 +slave-bin.000001 # XA_prepare 1 # XA PREPARE X'78',X'',1 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # XA COMMIT X'78',X'',1 +slave-bin.000001 # Gtid 1 # XA START X'78',X'',1 GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; insert into t1 values (4, 0) +slave-bin.000001 # Query 1 # XA END X'78',X'',1 +slave-bin.000001 # XA_prepare 1 # XA PREPARE X'78',X'',1 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # XA COMMIT X'78',X'',1 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; DROP TABLE IF EXISTS `t1` /* generated by server */ +include/stop_slave.inc +set @@GLOBAL.slave_parallel_threads= @save_par_thds; +set @@GLOBAL.slave_parallel_mode= @save_par_mode; +include/start_slave.inc +# +# Initialize test data +connection slave; +include/stop_slave.inc +connection master; +create table t1 (a int primary key, b int) engine=innodb; +insert into t1 values (-1, 0); +include/save_master_gtid.inc +connection slave; +include/start_slave.inc +include/sync_with_master_gtid.inc +include/stop_slave.inc +set @save_debug= @@GLOBAL.debug_dbug; +set @save_par_thds= @@GLOBAL.slave_parallel_threads; +set @save_par_mode= @@GLOBAL.slave_parallel_mode; +set @@GLOBAL.slave_parallel_threads= 4; +set @@GLOBAL.slave_parallel_mode= optimistic; +set statement sql_log_bin=0 for call mtr.add_suppression("Commit failed due to failure of an earlier commit on which this one depends"); +# +# Test Case 1-ROLLBACK: +# If two XA ROLLBACK transactions have different +# XIDs, ensure both phases of both transactions all execute concurrently. +# +connection slave; +set @@global.debug_dbug= "+d,stop_before_binlog_prepare,stop_after_binlog_prepare"; +connection master; +set @@session.gtid_seq_no= 200 + (1 * 1000); +XA START 'x1'; +insert into t1 values (0, 0); +XA END 'x1'; +XA PREPARE 'x1'; +XA ROLLBACK 'x1'; +XA START 'x2'; +insert into t1 values (1, 0); +XA END 'x2'; +XA PREPARE 'x2'; +XA ROLLBACK 'x2'; +connection slave; +include/start_slave.inc +must be \Empty +XA RECOVER; +formatID gtrid_length bqual_length data +set debug_sync= "now signal binlog_xap"; +# Make a stop at a point where XA PREPARE (both) have completed (binlogged): +set debug_sync= "now wait_for xa_prepare_binlogged"; +# prove XA-ROLLBACK 'x1' has completed too +SELECT 'x1' = 'x1'; +'x1' = 'x1' +1 +# cleanup: release the seq_no 200 XAP +set debug_sync= "now signal continue_xap"; +# Proof of the full master-slave sync +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:test.t1, slave:test.t1] +connection slave; +include/stop_slave.inc +set @@global.debug_dbug= @save_debug; +# +# Test Case 2-ROLLBACK: +# Two concurrent 2-phase XA transactions with matching XIDs +# should run one after the other. +# +connection slave; +set @@global.debug_dbug= "+d,stop_before_binlog_prepare,stop_after_binlog_prepare"; +connection master; +set @@session.gtid_seq_no= 300 + (1 * 1000); +XA START 'x'; +insert into t1 values (2, 1); +XA END 'x'; +XA PREPARE 'x'; +XA ROLLBACK 'x'; +XA START 'x'; +insert into t1 values (3, 2); +XA END 'x'; +XA PREPARE 'x'; +XA ROLLBACK 'x'; +include/save_master_gtid.inc +connection slave; +include/start_slave.inc +# states of workers at the first XAP near binlogging are expected to be as the following +set debug_sync= "now signal binlog_xap"; +# Once the first XA PREPARE has binlogged.. +set debug_sync= "now wait_for xa_prepare_binlogged"; +SELECT 'x' = 'x'; +'x' = 'x' +1 +# cleanup: signal XA PREPARE to leave the stage +set debug_sync= "now signal continue_xap"; +# Proof of the full master-slave sync +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:test.t1, slave:test.t1] +connection slave; +include/stop_slave.inc +set @@global.debug_dbug= @save_debug; +# +# Test Case 3-ROLLBACK (Error Case): +# If an XA PREPARE errors while its +# XA ROLLBACK is waiting on it, both phases should rollback +# successfully. Note this tests both: +# a) XA ROLLBACK is waiting in group commit (first phase +# times out in DMLs) +# b) XA ROLLBACK is waiting in group commit, with another XAP +# with a duplicate XID waiting on it. +# Case a) +# Ensure slave is stopped +connection slave; +include/wait_for_slave_to_stop.inc +set @save_lock_wait_timeout= @@GLOBAL.innodb_lock_wait_timeout; +set @save_trans_retries= @@GLOBAL.slave_transaction_retries; +set @@global.innodb_lock_wait_timeout= 1; +set @@global.slave_transaction_retries= 0; +connection master; +XA START 'x'; +update t1 set b=b+1 where a=-1; +XA END 'x'; +XA PREPARE 'x'; +XA ROLLBACK 'x'; +include/save_master_gtid.inc +connection slave1; +BEGIN; +select * from t1 where a=-1 for update;; +a b +-1 0 +connection slave; +include/start_slave.inc +include/wait_for_slave_sql_error.inc [errno=1205] +connection slave1; +ROLLBACK; +connection slave; +include/stop_slave_io.inc +set @@global.innodb_lock_wait_timeout= @save_lock_wait_timeout; +set @@global.slave_transaction_retries= @save_trans_retries; +# Ensure on slave restart, we can re-execute the XA transaction +include/start_slave.inc +include/save_master_gtid.inc +include/stop_slave.inc +set @@global.debug_dbug= @save_debug; +# Case b) +# Ensure slave is stopped +connection slave; +include/wait_for_slave_to_stop.inc +set @save_lock_wait_timeout= @@GLOBAL.innodb_lock_wait_timeout; +set @save_trans_retries= @@GLOBAL.slave_transaction_retries; +set @@global.innodb_lock_wait_timeout= 1; +set @@global.slave_transaction_retries= 0; +connection master; +XA START 'x'; +update t1 set b=b+1 where a=-1; +XA END 'x'; +XA PREPARE 'x'; +XA ROLLBACK 'x'; +XA START 'x'; +insert into t1 values (4, 0); +XA END 'x'; +XA PREPARE 'x'; +include/save_master_gtid.inc +XA ROLLBACK 'x'; +connection slave1; +BEGIN; +select * from t1 where a=-1 for update;; +a b +-1 0 +connection slave; +include/start_slave.inc +include/wait_for_slave_sql_error.inc [errno=1205] +connection slave1; +ROLLBACK; +# There should not be any prepared rows seen by XA RECOVER +XA RECOVER; +formatID gtrid_length bqual_length data +# Ensuring data from second XAP isn't visible.. +# ..done +connection slave; +include/stop_slave_io.inc +set @@global.innodb_lock_wait_timeout= @save_lock_wait_timeout; +set @@global.slave_transaction_retries= @save_trans_retries; +# Ensure on slave restart, we can re-execute the XA transaction +include/start_slave.inc +include/save_master_gtid.inc +include/stop_slave.inc +set @@global.debug_dbug= @save_debug; +include/start_slave.inc +# Ensuring data from second XAP is visible.. +# ..done +# Cleanup +connection slave; +set debug_sync='RESET'; +set @@global.debug_dbug= @save_debug; +include/start_slave.inc +Warnings: +Note 1254 Slave is already running +connection master; +DROP TABLE t1; +include/save_master_gtid.inc +include/show_binlog_events.inc +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; create table t1 (a int primary key, b int) engine=innodb +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (-1, 0) +master-bin.000001 # Xid # # COMMIT /* XID */ +master-bin.000001 # Gtid # # XA START X'7831',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (0, 0) +master-bin.000001 # Query # # XA END X'7831',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'7831',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'7831',X'',1 +master-bin.000001 # Gtid # # XA START X'7832',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (1, 0) +master-bin.000001 # Query # # XA END X'7832',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'7832',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'7832',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (2, 1) +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'78',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (3, 2) +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'78',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; update t1 set b=b+1 where a=-1 +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'78',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; update t1 set b=b+1 where a=-1 +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'78',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (4, 0) +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; DROP TABLE `t1` /* generated by server */ +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; create table t1 (a int primary key, b int) engine=innodb +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (-1, 0) +master-bin.000001 # Xid # # COMMIT /* XID */ +master-bin.000001 # Gtid # # XA START X'7831',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (0, 0) +master-bin.000001 # Query # # XA END X'7831',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'7831',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA ROLLBACK X'7831',X'',1 +master-bin.000001 # Gtid # # XA START X'7832',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (1, 0) +master-bin.000001 # Query # # XA END X'7832',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'7832',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA ROLLBACK X'7832',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (2, 1) +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA ROLLBACK X'78',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (3, 2) +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA ROLLBACK X'78',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; update t1 set b=b+1 where a=-1 +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA ROLLBACK X'78',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; update t1 set b=b+1 where a=-1 +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA ROLLBACK X'78',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (4, 0) +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA ROLLBACK X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; DROP TABLE `t1` /* generated by server */ +connection slave; +include/sync_with_master_gtid.inc +show binlog events in 'slave-bin.000001' from ; +Log_name Pos Event_type Server_id End_log_pos Info +slave-bin.000001 # Gtid_list 2 # [] +slave-bin.000001 # Binlog_checkpoint 2 # slave-bin.000001 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; create table t1 (a int primary key, b int) engine=innodb +slave-bin.000001 # Gtid 1 # BEGIN GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; insert into t1 values (-1, 0) +slave-bin.000001 # Xid 1 # COMMIT /* XID */ +slave-bin.000001 # Gtid 1 # XA START X'7831',X'',1 GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; insert into t1 values (0, 0) +slave-bin.000001 # Query 1 # XA END X'7831',X'',1 +slave-bin.000001 # XA_prepare 1 # XA PREPARE X'7831',X'',1 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # XA COMMIT X'7831',X'',1 +slave-bin.000001 # Gtid 1 # XA START X'7832',X'',1 GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; insert into t1 values (1, 0) +slave-bin.000001 # Query 1 # XA END X'7832',X'',1 +slave-bin.000001 # XA_prepare 1 # XA PREPARE X'7832',X'',1 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # XA COMMIT X'7832',X'',1 +slave-bin.000001 # Gtid 1 # XA START X'78',X'',1 GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; insert into t1 values (2, 1) +slave-bin.000001 # Query 1 # XA END X'78',X'',1 +slave-bin.000001 # XA_prepare 1 # XA PREPARE X'78',X'',1 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # XA COMMIT X'78',X'',1 +slave-bin.000001 # Gtid 1 # XA START X'78',X'',1 GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; insert into t1 values (3, 2) +slave-bin.000001 # Query 1 # XA END X'78',X'',1 +slave-bin.000001 # XA_prepare 1 # XA PREPARE X'78',X'',1 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # XA COMMIT X'78',X'',1 +slave-bin.000001 # Gtid 1 # XA START X'78',X'',1 GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; update t1 set b=b+1 where a=-1 +slave-bin.000001 # Query 1 # XA END X'78',X'',1 +slave-bin.000001 # XA_prepare 1 # XA PREPARE X'78',X'',1 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # XA COMMIT X'78',X'',1 +slave-bin.000001 # Gtid 1 # XA START X'78',X'',1 GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; update t1 set b=b+1 where a=-1 +slave-bin.000001 # Query 1 # XA END X'78',X'',1 +slave-bin.000001 # XA_prepare 1 # XA PREPARE X'78',X'',1 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # XA COMMIT X'78',X'',1 +slave-bin.000001 # Gtid 1 # XA START X'78',X'',1 GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; insert into t1 values (4, 0) +slave-bin.000001 # Query 1 # XA END X'78',X'',1 +slave-bin.000001 # XA_prepare 1 # XA PREPARE X'78',X'',1 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # XA COMMIT X'78',X'',1 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; DROP TABLE IF EXISTS `t1` /* generated by server */ +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; create table t1 (a int primary key, b int) engine=innodb +slave-bin.000001 # Gtid 1 # BEGIN GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; insert into t1 values (-1, 0) +slave-bin.000001 # Xid 1 # COMMIT /* XID */ +slave-bin.000001 # Gtid 1 # XA START X'7831',X'',1 GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; insert into t1 values (0, 0) +slave-bin.000001 # Query 1 # XA END X'7831',X'',1 +slave-bin.000001 # XA_prepare 1 # XA PREPARE X'7831',X'',1 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # XA ROLLBACK X'7831',X'',1 +slave-bin.000001 # Gtid 1 # XA START X'7832',X'',1 GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; insert into t1 values (1, 0) +slave-bin.000001 # Query 1 # XA END X'7832',X'',1 +slave-bin.000001 # XA_prepare 1 # XA PREPARE X'7832',X'',1 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # XA ROLLBACK X'7832',X'',1 +slave-bin.000001 # Gtid 1 # XA START X'78',X'',1 GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; insert into t1 values (2, 1) +slave-bin.000001 # Query 1 # XA END X'78',X'',1 +slave-bin.000001 # XA_prepare 1 # XA PREPARE X'78',X'',1 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # XA ROLLBACK X'78',X'',1 +slave-bin.000001 # Gtid 1 # XA START X'78',X'',1 GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; insert into t1 values (3, 2) +slave-bin.000001 # Query 1 # XA END X'78',X'',1 +slave-bin.000001 # XA_prepare 1 # XA PREPARE X'78',X'',1 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # XA ROLLBACK X'78',X'',1 +slave-bin.000001 # Gtid 1 # XA START X'78',X'',1 GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; update t1 set b=b+1 where a=-1 +slave-bin.000001 # Query 1 # XA END X'78',X'',1 +slave-bin.000001 # XA_prepare 1 # XA PREPARE X'78',X'',1 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # XA ROLLBACK X'78',X'',1 +slave-bin.000001 # Gtid 1 # XA START X'78',X'',1 GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; update t1 set b=b+1 where a=-1 +slave-bin.000001 # Query 1 # XA END X'78',X'',1 +slave-bin.000001 # XA_prepare 1 # XA PREPARE X'78',X'',1 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # XA ROLLBACK X'78',X'',1 +slave-bin.000001 # Gtid 1 # XA START X'78',X'',1 GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; insert into t1 values (4, 0) +slave-bin.000001 # Query 1 # XA END X'78',X'',1 +slave-bin.000001 # XA_prepare 1 # XA PREPARE X'78',X'',1 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # XA ROLLBACK X'78',X'',1 +slave-bin.000001 # Gtid 1 # GTID #-#-# +slave-bin.000001 # Query 1 # use `test`; DROP TABLE IF EXISTS `t1` /* generated by server */ +include/stop_slave.inc +set @@GLOBAL.slave_parallel_threads= @save_par_thds; +set @@GLOBAL.slave_parallel_mode= @save_par_mode; +include/start_slave.inc +# +# Test Case 5: If an XAP is skipped by the replica (e.g. by incorrectly +# setting gtid_slave_pos), and only its XAC/XAR is tried to execute, the +# replica should report ER_XAER_NOTA. +connection master; +create table t1 (a int) engine=innodb; +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +call mtr.add_suppression("XAER_NOTA: Unknown XID"); +include/stop_slave.inc +change master to master_use_gtid = slave_pos; +connection master; +xa start '1'; +insert into t1 set a=1; +xa end '1'; +xa prepare '1'; +xa rollback '1'; +insert into t1 set a=2; +include/save_master_gtid.inc +connection slave; +set @save_gtid_slave_pos= @@global.gtid_slave_pos; +SELECT CONCAT(domain_id,"-",server_id,"-", seq_no + 1) +into @gtid_skip +FROM mysql.gtid_slave_pos +WHERE seq_no = (SELECT DISTINCT max(seq_no) FROM mysql.gtid_slave_pos) limit 1; +set @@global.gtid_slave_pos = @gtid_skip; +start slave; +include/wait_for_slave_sql_error.inc [errno=1397] +select count(*) = 2 % 2 as 'must be true' from t1;; +must be true +1 +include/stop_slave.inc +set @@global.gtid_slave_pos = @save_gtid_slave_pos; +show warnings; +Level Code Message +Warning 1947 Specified GTID conflicts with the binary log which contains a more recent GTID . If MASTER_GTID_POS=CURRENT_POS is used, the binlog position will override the new value of @@gtid_slave_pos +include/start_slave.inc +include/sync_with_master_gtid.inc +include/stop_slave.inc +change master to master_use_gtid = slave_pos; +connection master; +xa start '1'; +insert into t1 set a=1; +xa end '1'; +xa prepare '1'; +xa commit '1'; +insert into t1 set a=2; +include/save_master_gtid.inc +connection slave; +set @save_gtid_slave_pos= @@global.gtid_slave_pos; +SELECT CONCAT(domain_id,"-",server_id,"-", seq_no + 1) +into @gtid_skip +FROM mysql.gtid_slave_pos +WHERE seq_no = (SELECT DISTINCT max(seq_no) FROM mysql.gtid_slave_pos) limit 1; +set @@global.gtid_slave_pos = @gtid_skip; +start slave; +include/wait_for_slave_sql_error.inc [errno=1397] +select count(*) = 1 % 2 as 'must be true' from t1;; +must be true +1 +include/stop_slave.inc +set @@global.gtid_slave_pos = @save_gtid_slave_pos; +show warnings; +Level Code Message +include/start_slave.inc +include/sync_with_master_gtid.inc +connection master; +drop table t1; +connection slave; +include/rpl_end.inc +# End of rpl_xa_concurrent_2pc.test diff --git a/mysql-test/suite/rpl/r/rpl_xa_concurrent_2pc_lsu_off.result b/mysql-test/suite/rpl/r/rpl_xa_concurrent_2pc_lsu_off.result new file mode 100644 index 0000000000000..39b35136117a7 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_xa_concurrent_2pc_lsu_off.result @@ -0,0 +1,633 @@ +include/master-slave.inc +[connection master] +# +# Initialize test data +connection slave; +include/stop_slave.inc +connection master; +create table t1 (a int primary key, b int) engine=innodb; +insert into t1 values (-1, 0); +include/save_master_gtid.inc +connection slave; +include/start_slave.inc +include/sync_with_master_gtid.inc +include/stop_slave.inc +set @save_debug= @@GLOBAL.debug_dbug; +set @save_par_thds= @@GLOBAL.slave_parallel_threads; +set @save_par_mode= @@GLOBAL.slave_parallel_mode; +set @@GLOBAL.slave_parallel_threads= 4; +set @@GLOBAL.slave_parallel_mode= optimistic; +set statement sql_log_bin=0 for call mtr.add_suppression("Commit failed due to failure of an earlier commit on which this one depends"); +# +# Test Case 1-COMMIT: +# If two XA COMMIT transactions have different +# XIDs, ensure both phases of both transactions all execute concurrently. +# +connection slave; +set @@global.debug_dbug= "+d,stop_before_binlog_prepare,stop_after_binlog_prepare"; +connection master; +set @@session.gtid_seq_no= 200 + (0 * 1000); +XA START 'x1'; +insert into t1 values (0, 0); +XA END 'x1'; +XA PREPARE 'x1'; +XA COMMIT 'x1'; +XA START 'x2'; +insert into t1 values (1, 0); +XA END 'x2'; +XA PREPARE 'x2'; +XA COMMIT 'x2'; +connection slave; +include/start_slave.inc +must be \Empty +XA RECOVER; +formatID gtrid_length bqual_length data +set debug_sync= "now signal binlog_xap"; +# Make a stop at a point where XA PREPARE (both) have completed (binlogged): +set debug_sync= "now wait_for xa_prepare_binlogged"; +# prove XA-COMMIT 'x1' has completed too +SELECT 'x1' = 'x1'; +'x1' = 'x1' +1 +# cleanup: release the seq_no 200 XAP +set debug_sync= "now signal continue_xap"; +# Proof of the full master-slave sync +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:test.t1, slave:test.t1] +connection slave; +include/stop_slave.inc +set @@global.debug_dbug= @save_debug; +# +# Test Case 2-COMMIT: +# Two concurrent 2-phase XA transactions with matching XIDs +# should run one after the other. +# +connection slave; +set @@global.debug_dbug= "+d,stop_before_binlog_prepare,stop_after_binlog_prepare"; +connection master; +set @@session.gtid_seq_no= 300 + (0 * 1000); +XA START 'x'; +insert into t1 values (2, 1); +XA END 'x'; +XA PREPARE 'x'; +XA COMMIT 'x'; +XA START 'x'; +insert into t1 values (3, 2); +XA END 'x'; +XA PREPARE 'x'; +XA COMMIT 'x'; +include/save_master_gtid.inc +connection slave; +include/start_slave.inc +# states of workers at the first XAP near binlogging are expected to be as the following +set debug_sync= "now signal binlog_xap"; +# Once the first XA PREPARE has binlogged.. +set debug_sync= "now wait_for xa_prepare_binlogged"; +SELECT 'x' = 'x'; +'x' = 'x' +1 +# cleanup: signal XA PREPARE to leave the stage +set debug_sync= "now signal continue_xap"; +# Proof of the full master-slave sync +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:test.t1, slave:test.t1] +connection slave; +include/stop_slave.inc +set @@global.debug_dbug= @save_debug; +# +# Test Case 3-COMMIT (Error Case): +# If an XA PREPARE errors while its +# XA COMMIT is waiting on it, both phases should rollback +# successfully. Note this tests both: +# a) XA COMMIT is waiting in group commit (first phase +# times out in DMLs) +# b) XA COMMIT is waiting in group commit, with another XAP +# with a duplicate XID waiting on it. +# Case a) +# Ensure slave is stopped +connection slave; +include/wait_for_slave_to_stop.inc +set @save_lock_wait_timeout= @@GLOBAL.innodb_lock_wait_timeout; +set @save_trans_retries= @@GLOBAL.slave_transaction_retries; +set @@global.innodb_lock_wait_timeout= 1; +set @@global.slave_transaction_retries= 0; +connection master; +XA START 'x'; +update t1 set b=b+1 where a=-1; +XA END 'x'; +XA PREPARE 'x'; +XA COMMIT 'x'; +include/save_master_gtid.inc +connection slave1; +BEGIN; +select * from t1 where a=-1 for update;; +a b +-1 0 +connection slave; +include/start_slave.inc +include/wait_for_slave_sql_error.inc [errno=1205] +connection slave1; +ROLLBACK; +connection slave; +include/stop_slave_io.inc +set @@global.innodb_lock_wait_timeout= @save_lock_wait_timeout; +set @@global.slave_transaction_retries= @save_trans_retries; +# Ensure on slave restart, we can re-execute the XA transaction +include/start_slave.inc +include/save_master_gtid.inc +include/stop_slave.inc +set @@global.debug_dbug= @save_debug; +# Case b) +# Ensure slave is stopped +connection slave; +include/wait_for_slave_to_stop.inc +set @save_lock_wait_timeout= @@GLOBAL.innodb_lock_wait_timeout; +set @save_trans_retries= @@GLOBAL.slave_transaction_retries; +set @@global.innodb_lock_wait_timeout= 1; +set @@global.slave_transaction_retries= 0; +connection master; +XA START 'x'; +update t1 set b=b+1 where a=-1; +XA END 'x'; +XA PREPARE 'x'; +XA COMMIT 'x'; +XA START 'x'; +insert into t1 values (4, 0); +XA END 'x'; +XA PREPARE 'x'; +include/save_master_gtid.inc +XA COMMIT 'x'; +connection slave1; +BEGIN; +select * from t1 where a=-1 for update;; +a b +-1 1 +connection slave; +include/start_slave.inc +include/wait_for_slave_sql_error.inc [errno=1205] +connection slave1; +ROLLBACK; +# There should not be any prepared rows seen by XA RECOVER +XA RECOVER; +formatID gtrid_length bqual_length data +# Ensuring data from second XAP isn't visible.. +# ..done +connection slave; +include/stop_slave_io.inc +set @@global.innodb_lock_wait_timeout= @save_lock_wait_timeout; +set @@global.slave_transaction_retries= @save_trans_retries; +# Ensure on slave restart, we can re-execute the XA transaction +include/start_slave.inc +include/save_master_gtid.inc +include/stop_slave.inc +set @@global.debug_dbug= @save_debug; +include/start_slave.inc +# Ensuring data from second XAP is visible.. +# ..done +# Cleanup +connection slave; +set debug_sync='RESET'; +set @@global.debug_dbug= @save_debug; +include/start_slave.inc +Warnings: +Note 1254 Slave is already running +connection master; +DROP TABLE t1; +include/save_master_gtid.inc +include/show_binlog_events.inc +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; create table t1 (a int primary key, b int) engine=innodb +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (-1, 0) +master-bin.000001 # Xid # # COMMIT /* XID */ +master-bin.000001 # Gtid # # XA START X'7831',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (0, 0) +master-bin.000001 # Query # # XA END X'7831',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'7831',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'7831',X'',1 +master-bin.000001 # Gtid # # XA START X'7832',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (1, 0) +master-bin.000001 # Query # # XA END X'7832',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'7832',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'7832',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (2, 1) +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'78',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (3, 2) +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'78',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; update t1 set b=b+1 where a=-1 +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'78',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; update t1 set b=b+1 where a=-1 +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'78',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (4, 0) +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; DROP TABLE `t1` /* generated by server */ +connection slave; +include/sync_with_master_gtid.inc +show binlog events in 'slave-bin.000001' from ; +Log_name Pos Event_type Server_id End_log_pos Info +slave-bin.000001 # Gtid_list 2 # [] +slave-bin.000001 # Binlog_checkpoint 2 # slave-bin.000001 +include/stop_slave.inc +set @@GLOBAL.slave_parallel_threads= @save_par_thds; +set @@GLOBAL.slave_parallel_mode= @save_par_mode; +include/start_slave.inc +# +# Initialize test data +connection slave; +include/stop_slave.inc +connection master; +create table t1 (a int primary key, b int) engine=innodb; +insert into t1 values (-1, 0); +include/save_master_gtid.inc +connection slave; +include/start_slave.inc +include/sync_with_master_gtid.inc +include/stop_slave.inc +set @save_debug= @@GLOBAL.debug_dbug; +set @save_par_thds= @@GLOBAL.slave_parallel_threads; +set @save_par_mode= @@GLOBAL.slave_parallel_mode; +set @@GLOBAL.slave_parallel_threads= 4; +set @@GLOBAL.slave_parallel_mode= optimistic; +set statement sql_log_bin=0 for call mtr.add_suppression("Commit failed due to failure of an earlier commit on which this one depends"); +# +# Test Case 1-ROLLBACK: +# If two XA ROLLBACK transactions have different +# XIDs, ensure both phases of both transactions all execute concurrently. +# +connection slave; +set @@global.debug_dbug= "+d,stop_before_binlog_prepare,stop_after_binlog_prepare"; +connection master; +set @@session.gtid_seq_no= 200 + (1 * 1000); +XA START 'x1'; +insert into t1 values (0, 0); +XA END 'x1'; +XA PREPARE 'x1'; +XA ROLLBACK 'x1'; +XA START 'x2'; +insert into t1 values (1, 0); +XA END 'x2'; +XA PREPARE 'x2'; +XA ROLLBACK 'x2'; +connection slave; +include/start_slave.inc +must be \Empty +XA RECOVER; +formatID gtrid_length bqual_length data +set debug_sync= "now signal binlog_xap"; +# Make a stop at a point where XA PREPARE (both) have completed (binlogged): +set debug_sync= "now wait_for xa_prepare_binlogged"; +# prove XA-ROLLBACK 'x1' has completed too +SELECT 'x1' = 'x1'; +'x1' = 'x1' +1 +# cleanup: release the seq_no 200 XAP +set debug_sync= "now signal continue_xap"; +# Proof of the full master-slave sync +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:test.t1, slave:test.t1] +connection slave; +include/stop_slave.inc +set @@global.debug_dbug= @save_debug; +# +# Test Case 2-ROLLBACK: +# Two concurrent 2-phase XA transactions with matching XIDs +# should run one after the other. +# +connection slave; +set @@global.debug_dbug= "+d,stop_before_binlog_prepare,stop_after_binlog_prepare"; +connection master; +set @@session.gtid_seq_no= 300 + (1 * 1000); +XA START 'x'; +insert into t1 values (2, 1); +XA END 'x'; +XA PREPARE 'x'; +XA ROLLBACK 'x'; +XA START 'x'; +insert into t1 values (3, 2); +XA END 'x'; +XA PREPARE 'x'; +XA ROLLBACK 'x'; +include/save_master_gtid.inc +connection slave; +include/start_slave.inc +# states of workers at the first XAP near binlogging are expected to be as the following +set debug_sync= "now signal binlog_xap"; +# Once the first XA PREPARE has binlogged.. +set debug_sync= "now wait_for xa_prepare_binlogged"; +SELECT 'x' = 'x'; +'x' = 'x' +1 +# cleanup: signal XA PREPARE to leave the stage +set debug_sync= "now signal continue_xap"; +# Proof of the full master-slave sync +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:test.t1, slave:test.t1] +connection slave; +include/stop_slave.inc +set @@global.debug_dbug= @save_debug; +# +# Test Case 3-ROLLBACK (Error Case): +# If an XA PREPARE errors while its +# XA ROLLBACK is waiting on it, both phases should rollback +# successfully. Note this tests both: +# a) XA ROLLBACK is waiting in group commit (first phase +# times out in DMLs) +# b) XA ROLLBACK is waiting in group commit, with another XAP +# with a duplicate XID waiting on it. +# Case a) +# Ensure slave is stopped +connection slave; +include/wait_for_slave_to_stop.inc +set @save_lock_wait_timeout= @@GLOBAL.innodb_lock_wait_timeout; +set @save_trans_retries= @@GLOBAL.slave_transaction_retries; +set @@global.innodb_lock_wait_timeout= 1; +set @@global.slave_transaction_retries= 0; +connection master; +XA START 'x'; +update t1 set b=b+1 where a=-1; +XA END 'x'; +XA PREPARE 'x'; +XA ROLLBACK 'x'; +include/save_master_gtid.inc +connection slave1; +BEGIN; +select * from t1 where a=-1 for update;; +a b +-1 0 +connection slave; +include/start_slave.inc +include/wait_for_slave_sql_error.inc [errno=1205] +connection slave1; +ROLLBACK; +connection slave; +include/stop_slave_io.inc +set @@global.innodb_lock_wait_timeout= @save_lock_wait_timeout; +set @@global.slave_transaction_retries= @save_trans_retries; +# Ensure on slave restart, we can re-execute the XA transaction +include/start_slave.inc +include/save_master_gtid.inc +include/stop_slave.inc +set @@global.debug_dbug= @save_debug; +# Case b) +# Ensure slave is stopped +connection slave; +include/wait_for_slave_to_stop.inc +set @save_lock_wait_timeout= @@GLOBAL.innodb_lock_wait_timeout; +set @save_trans_retries= @@GLOBAL.slave_transaction_retries; +set @@global.innodb_lock_wait_timeout= 1; +set @@global.slave_transaction_retries= 0; +connection master; +XA START 'x'; +update t1 set b=b+1 where a=-1; +XA END 'x'; +XA PREPARE 'x'; +XA ROLLBACK 'x'; +XA START 'x'; +insert into t1 values (4, 0); +XA END 'x'; +XA PREPARE 'x'; +include/save_master_gtid.inc +XA ROLLBACK 'x'; +connection slave1; +BEGIN; +select * from t1 where a=-1 for update;; +a b +-1 0 +connection slave; +include/start_slave.inc +include/wait_for_slave_sql_error.inc [errno=1205] +connection slave1; +ROLLBACK; +# There should not be any prepared rows seen by XA RECOVER +XA RECOVER; +formatID gtrid_length bqual_length data +# Ensuring data from second XAP isn't visible.. +# ..done +connection slave; +include/stop_slave_io.inc +set @@global.innodb_lock_wait_timeout= @save_lock_wait_timeout; +set @@global.slave_transaction_retries= @save_trans_retries; +# Ensure on slave restart, we can re-execute the XA transaction +include/start_slave.inc +include/save_master_gtid.inc +include/stop_slave.inc +set @@global.debug_dbug= @save_debug; +include/start_slave.inc +# Ensuring data from second XAP is visible.. +# ..done +# Cleanup +connection slave; +set debug_sync='RESET'; +set @@global.debug_dbug= @save_debug; +include/start_slave.inc +Warnings: +Note 1254 Slave is already running +connection master; +DROP TABLE t1; +include/save_master_gtid.inc +include/show_binlog_events.inc +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; create table t1 (a int primary key, b int) engine=innodb +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (-1, 0) +master-bin.000001 # Xid # # COMMIT /* XID */ +master-bin.000001 # Gtid # # XA START X'7831',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (0, 0) +master-bin.000001 # Query # # XA END X'7831',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'7831',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'7831',X'',1 +master-bin.000001 # Gtid # # XA START X'7832',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (1, 0) +master-bin.000001 # Query # # XA END X'7832',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'7832',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'7832',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (2, 1) +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'78',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (3, 2) +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'78',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; update t1 set b=b+1 where a=-1 +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'78',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; update t1 set b=b+1 where a=-1 +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'78',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (4, 0) +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA COMMIT X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; DROP TABLE `t1` /* generated by server */ +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; create table t1 (a int primary key, b int) engine=innodb +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (-1, 0) +master-bin.000001 # Xid # # COMMIT /* XID */ +master-bin.000001 # Gtid # # XA START X'7831',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (0, 0) +master-bin.000001 # Query # # XA END X'7831',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'7831',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA ROLLBACK X'7831',X'',1 +master-bin.000001 # Gtid # # XA START X'7832',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (1, 0) +master-bin.000001 # Query # # XA END X'7832',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'7832',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA ROLLBACK X'7832',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (2, 1) +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA ROLLBACK X'78',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (3, 2) +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA ROLLBACK X'78',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; update t1 set b=b+1 where a=-1 +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA ROLLBACK X'78',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; update t1 set b=b+1 where a=-1 +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA ROLLBACK X'78',X'',1 +master-bin.000001 # Gtid # # XA START X'78',X'',1 GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (4, 0) +master-bin.000001 # Query # # XA END X'78',X'',1 +master-bin.000001 # XA_prepare # # XA PREPARE X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # XA ROLLBACK X'78',X'',1 +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; DROP TABLE `t1` /* generated by server */ +connection slave; +include/sync_with_master_gtid.inc +show binlog events in 'slave-bin.000001' from ; +Log_name Pos Event_type Server_id End_log_pos Info +slave-bin.000001 # Gtid_list 2 # [] +slave-bin.000001 # Binlog_checkpoint 2 # slave-bin.000001 +include/stop_slave.inc +set @@GLOBAL.slave_parallel_threads= @save_par_thds; +set @@GLOBAL.slave_parallel_mode= @save_par_mode; +include/start_slave.inc +# +# Test Case 5: If an XAP is skipped by the replica (e.g. by incorrectly +# setting gtid_slave_pos), and only its XAC/XAR is tried to execute, the +# replica should report ER_XAER_NOTA. +connection master; +create table t1 (a int) engine=innodb; +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +call mtr.add_suppression("XAER_NOTA: Unknown XID"); +include/stop_slave.inc +change master to master_use_gtid = slave_pos; +connection master; +xa start '1'; +insert into t1 set a=1; +xa end '1'; +xa prepare '1'; +xa rollback '1'; +insert into t1 set a=2; +include/save_master_gtid.inc +connection slave; +set @save_gtid_slave_pos= @@global.gtid_slave_pos; +SELECT CONCAT(domain_id,"-",server_id,"-", seq_no + 1) +into @gtid_skip +FROM mysql.gtid_slave_pos +WHERE seq_no = (SELECT DISTINCT max(seq_no) FROM mysql.gtid_slave_pos) limit 1; +set @@global.gtid_slave_pos = @gtid_skip; +start slave; +include/wait_for_slave_sql_error.inc [errno=1397] +select count(*) = 2 % 2 as 'must be true' from t1;; +must be true +1 +include/stop_slave.inc +set @@global.gtid_slave_pos = @save_gtid_slave_pos; +show warnings; +Level Code Message +Warning 1947 Specified GTID conflicts with the binary log which contains a more recent GTID . If MASTER_GTID_POS=CURRENT_POS is used, the binlog position will override the new value of @@gtid_slave_pos +include/start_slave.inc +include/sync_with_master_gtid.inc +include/stop_slave.inc +change master to master_use_gtid = slave_pos; +connection master; +xa start '1'; +insert into t1 set a=1; +xa end '1'; +xa prepare '1'; +xa commit '1'; +insert into t1 set a=2; +include/save_master_gtid.inc +connection slave; +set @save_gtid_slave_pos= @@global.gtid_slave_pos; +SELECT CONCAT(domain_id,"-",server_id,"-", seq_no + 1) +into @gtid_skip +FROM mysql.gtid_slave_pos +WHERE seq_no = (SELECT DISTINCT max(seq_no) FROM mysql.gtid_slave_pos) limit 1; +set @@global.gtid_slave_pos = @gtid_skip; +start slave; +include/wait_for_slave_sql_error.inc [errno=1397] +select count(*) = 1 % 2 as 'must be true' from t1;; +must be true +1 +include/stop_slave.inc +set @@global.gtid_slave_pos = @save_gtid_slave_pos; +show warnings; +Level Code Message +include/start_slave.inc +include/sync_with_master_gtid.inc +connection master; +drop table t1; +connection slave; +include/rpl_end.inc +# End of rpl_xa_concurrent_2pc.test diff --git a/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result b/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result index 917041c148d23..862e2981bac70 100644 --- a/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result +++ b/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result @@ -19,6 +19,8 @@ connection server_1; XA START 'x'; XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back XA COMMIT 'x';; include/save_master_gtid.inc FLUSH LOGS; @@ -53,13 +55,14 @@ connect con1,localhost,root,,; XA START 'x'; XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; formatID gtrid_length bqual_length data -1 1 0 x XA COMMIT 'x';; -ERROR XA100: XA_RBROLLBACK: Transaction branch was rolled back +ERROR XAE04: XAER_NOTA: Unknown XID include/save_master_gtid.inc FLUSH LOGS; # MYSQL_BINLOG server_1_datadir/binlog_filename --result-file=assert_file @@ -92,6 +95,8 @@ connection server_1; XA START 'x'; XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back XA ROLLBACK 'x';; include/save_master_gtid.inc FLUSH LOGS; @@ -126,13 +131,14 @@ connect con1,localhost,root,,; XA START 'x'; XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; formatID gtrid_length bqual_length data -1 1 0 x XA ROLLBACK 'x';; -ERROR XA100: XA_RBROLLBACK: Transaction branch was rolled back +ERROR XAE04: XAER_NOTA: Unknown XID include/save_master_gtid.inc FLUSH LOGS; # MYSQL_BINLOG server_1_datadir/binlog_filename --result-file=assert_file @@ -170,6 +176,8 @@ INSERT INTO ti VALUES (1),(1);; ERROR 23000: Duplicate entry '1' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back XA COMMIT 'x';; include/save_master_gtid.inc FLUSH LOGS; @@ -206,13 +214,14 @@ INSERT INTO ti VALUES (1),(1);; ERROR 23000: Duplicate entry '1' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; formatID gtrid_length bqual_length data -1 1 0 x XA COMMIT 'x';; -ERROR XA100: XA_RBROLLBACK: Transaction branch was rolled back +ERROR XAE04: XAER_NOTA: Unknown XID include/save_master_gtid.inc FLUSH LOGS; # MYSQL_BINLOG server_1_datadir/binlog_filename --result-file=assert_file @@ -247,6 +256,8 @@ INSERT INTO ti VALUES (1),(1);; ERROR 23000: Duplicate entry '1' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back XA ROLLBACK 'x';; include/save_master_gtid.inc FLUSH LOGS; @@ -283,13 +294,14 @@ INSERT INTO ti VALUES (1),(1);; ERROR 23000: Duplicate entry '1' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; formatID gtrid_length bqual_length data -1 1 0 x XA ROLLBACK 'x';; -ERROR XA100: XA_RBROLLBACK: Transaction branch was rolled back +ERROR XAE04: XAER_NOTA: Unknown XID include/save_master_gtid.inc FLUSH LOGS; # MYSQL_BINLOG server_1_datadir/binlog_filename --result-file=assert_file @@ -331,7 +343,11 @@ INSERT INTO tm VALUES (1),(1);; ERROR 23000: Duplicate entry '1' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back XA COMMIT 'x';; +Warnings: +Warning 1196 Some non-transactional changed tables couldn't be rolled back include/save_master_gtid.inc FLUSH LOGS; # MYSQL_BINLOG server_1_datadir/binlog_filename --result-file=assert_file @@ -367,13 +383,14 @@ INSERT INTO tm VALUES (1),(1);; ERROR 23000: Duplicate entry '1' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; formatID gtrid_length bqual_length data -1 1 0 x XA COMMIT 'x';; -ERROR XA100: XA_RBROLLBACK: Transaction branch was rolled back +ERROR XAE04: XAER_NOTA: Unknown XID include/save_master_gtid.inc FLUSH LOGS; # MYSQL_BINLOG server_1_datadir/binlog_filename --result-file=assert_file @@ -408,6 +425,8 @@ INSERT INTO tm VALUES (1),(1);; ERROR 23000: Duplicate entry '1' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back XA ROLLBACK 'x';; Warnings: Warning 1196 Some non-transactional changed tables couldn't be rolled back @@ -446,13 +465,14 @@ INSERT INTO tm VALUES (1),(1);; ERROR 23000: Duplicate entry '1' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; formatID gtrid_length bqual_length data -1 1 0 x XA ROLLBACK 'x';; -ERROR XA100: XA_RBROLLBACK: Transaction branch was rolled back +ERROR XAE04: XAER_NOTA: Unknown XID include/save_master_gtid.inc FLUSH LOGS; # MYSQL_BINLOG server_1_datadir/binlog_filename --result-file=assert_file @@ -495,6 +515,8 @@ INSERT INTO ti VALUES (2),(2);; ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back XA COMMIT 'x';; include/save_master_gtid.inc FLUSH LOGS; @@ -533,13 +555,14 @@ INSERT INTO ti VALUES (2),(2);; ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; formatID gtrid_length bqual_length data -1 1 0 x XA COMMIT 'x';; -ERROR XA100: XA_RBROLLBACK: Transaction branch was rolled back +ERROR XAE04: XAER_NOTA: Unknown XID include/save_master_gtid.inc FLUSH LOGS; # MYSQL_BINLOG server_1_datadir/binlog_filename --result-file=assert_file @@ -576,6 +599,8 @@ INSERT INTO ti VALUES (2),(2);; ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back XA ROLLBACK 'x';; include/save_master_gtid.inc FLUSH LOGS; @@ -614,13 +639,14 @@ INSERT INTO ti VALUES (2),(2);; ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; formatID gtrid_length bqual_length data -1 1 0 x XA ROLLBACK 'x';; -ERROR XA100: XA_RBROLLBACK: Transaction branch was rolled back +ERROR XAE04: XAER_NOTA: Unknown XID include/save_master_gtid.inc FLUSH LOGS; # MYSQL_BINLOG server_1_datadir/binlog_filename --result-file=assert_file @@ -643,7 +669,8 @@ include/sync_with_master_gtid.inc # # Test Case 5: A mixed XA transaction consisting of one statement that # can successfully be rolled back (first statement), and another that -# can not (second statement) should be binlogged +# can not (second statement) should be binlogged similarly to the normal +# transaction to reflect the 2nd statement. connection server_1; set @sav_binlog_format = @@binlog_format; set @@binlog_format = row; @@ -665,7 +692,11 @@ INSERT INTO tm VALUES (2),(2);; ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back XA COMMIT 'x';; +Warnings: +Warning 1196 Some non-transactional changed tables couldn't be rolled back include/save_master_gtid.inc FLUSH LOGS; # MYSQL_BINLOG server_1_datadir/binlog_filename --result-file=assert_file @@ -703,13 +734,14 @@ INSERT INTO tm VALUES (2),(2);; ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; formatID gtrid_length bqual_length data -1 1 0 x XA COMMIT 'x';; -ERROR XA100: XA_RBROLLBACK: Transaction branch was rolled back +ERROR XAE04: XAER_NOTA: Unknown XID include/save_master_gtid.inc FLUSH LOGS; # MYSQL_BINLOG server_1_datadir/binlog_filename --result-file=assert_file @@ -746,6 +778,8 @@ INSERT INTO tm VALUES (2),(2);; ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back XA ROLLBACK 'x';; Warnings: Warning 1196 Some non-transactional changed tables couldn't be rolled back @@ -786,13 +820,14 @@ INSERT INTO tm VALUES (2),(2);; ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; formatID gtrid_length bqual_length data -1 1 0 x XA ROLLBACK 'x';; -ERROR XA100: XA_RBROLLBACK: Transaction branch was rolled back +ERROR XAE04: XAER_NOTA: Unknown XID include/save_master_gtid.inc FLUSH LOGS; # MYSQL_BINLOG server_1_datadir/binlog_filename --result-file=assert_file @@ -818,7 +853,8 @@ set @@global.binlog_format = @sav_binlog_format; # # Test Case 6: A mixed XA transaction consisting of one statement that # cannot successfully be rolled back (first statement), and another that -# can (second statement) should be binlogged +# can (second statement) should be binlogged similarly to the normal +# transaction to reflect the 1st statement. connection server_1; set @sav_binlog_format = @@binlog_format; set @@binlog_format = row; @@ -840,7 +876,11 @@ INSERT INTO ti VALUES (2),(2);; ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back XA COMMIT 'x';; +Warnings: +Warning 1196 Some non-transactional changed tables couldn't be rolled back include/save_master_gtid.inc FLUSH LOGS; # MYSQL_BINLOG server_1_datadir/binlog_filename --result-file=assert_file @@ -878,13 +918,14 @@ INSERT INTO ti VALUES (2),(2);; ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; formatID gtrid_length bqual_length data -1 1 0 x XA COMMIT 'x';; -ERROR XA100: XA_RBROLLBACK: Transaction branch was rolled back +ERROR XAE04: XAER_NOTA: Unknown XID include/save_master_gtid.inc FLUSH LOGS; # MYSQL_BINLOG server_1_datadir/binlog_filename --result-file=assert_file @@ -921,6 +962,8 @@ INSERT INTO ti VALUES (2),(2);; ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back XA ROLLBACK 'x';; Warnings: Warning 1196 Some non-transactional changed tables couldn't be rolled back @@ -961,13 +1004,14 @@ INSERT INTO ti VALUES (2),(2);; ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; formatID gtrid_length bqual_length data -1 1 0 x XA ROLLBACK 'x';; -ERROR XA100: XA_RBROLLBACK: Transaction branch was rolled back +ERROR XAE04: XAER_NOTA: Unknown XID include/save_master_gtid.inc FLUSH LOGS; # MYSQL_BINLOG server_1_datadir/binlog_filename --result-file=assert_file @@ -992,7 +1036,7 @@ set @@binlog_format = @sav_binlog_format; set @@global.binlog_format = @sav_binlog_format; # # Test Case 7: An XA transaction consisting of two failed -# non-transactional statements should be binlogged +# non-transactional statements should be binlogged akin to 5,6. connection server_1; set @sav_binlog_format = @@binlog_format; set @@binlog_format = row; @@ -1014,7 +1058,11 @@ INSERT INTO tm VALUES (2),(2);; ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back XA COMMIT 'x';; +Warnings: +Warning 1196 Some non-transactional changed tables couldn't be rolled back include/save_master_gtid.inc FLUSH LOGS; # MYSQL_BINLOG server_1_datadir/binlog_filename --result-file=assert_file @@ -1052,13 +1100,14 @@ INSERT INTO tm VALUES (2),(2);; ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; formatID gtrid_length bqual_length data -1 1 0 x XA COMMIT 'x';; -ERROR XA100: XA_RBROLLBACK: Transaction branch was rolled back +ERROR XAE04: XAER_NOTA: Unknown XID include/save_master_gtid.inc FLUSH LOGS; # MYSQL_BINLOG server_1_datadir/binlog_filename --result-file=assert_file @@ -1095,6 +1144,8 @@ INSERT INTO tm VALUES (2),(2);; ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back XA ROLLBACK 'x';; Warnings: Warning 1196 Some non-transactional changed tables couldn't be rolled back @@ -1135,13 +1186,14 @@ INSERT INTO tm VALUES (2),(2);; ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; formatID gtrid_length bqual_length data -1 1 0 x XA ROLLBACK 'x';; -ERROR XA100: XA_RBROLLBACK: Transaction branch was rolled back +ERROR XAE04: XAER_NOTA: Unknown XID include/save_master_gtid.inc FLUSH LOGS; # MYSQL_BINLOG server_1_datadir/binlog_filename --result-file=assert_file @@ -1246,6 +1298,30 @@ include/start_slave.inc connection server_1; drop database db1; drop database db2; +use test; +connection server_1; +create table t_not_in_binlog (a int) engine=innodb; +flush logs; +include/save_master_gtid.inc +connect con1,localhost,root,,; +call mtr.add_suppression("XAER_NOTA: Unknown XID"); +SET sql_log_bin=0; +XA START 'a'; +insert into t_not_in_binlog set a=1; +XA END 'a'; +XA PREPARE 'a'; +disconnect con1; +connection server_1; +xa recover; +formatID gtrid_length bqual_length data +1 1 0 a +XA ROLLBACK 'a'; +drop table t_not_in_binlog; +include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +connection server_3; +include/sync_with_master_gtid.inc connection server_1; include/rpl_end.inc # End of rpl_xa_empty_transaction.test diff --git a/mysql-test/suite/rpl/r/rpl_xa_gtid_pos_auto_engine.result b/mysql-test/suite/rpl/r/rpl_xa_gtid_pos_auto_engine.result index f10e3e578bdb1..87a4d98712088 100644 --- a/mysql-test/suite/rpl/r/rpl_xa_gtid_pos_auto_engine.result +++ b/mysql-test/suite/rpl/r/rpl_xa_gtid_pos_auto_engine.result @@ -68,6 +68,8 @@ xa start 'ro_2'; select count(*) into @s2 from t1; xa end 'ro_2'; xa prepare 'ro_2';; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect master_ro_2; connection master; connect master_$xid, 127.0.0.1,root,,$db,$MASTER_MYPORT,; @@ -77,6 +79,8 @@ select 1; 1 xa end 'ro_1'; xa prepare 'ro_1';; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect master_ro_1; connection master; connect master_$xid, 127.0.0.1,root,,$db,$MASTER_MYPORT,; @@ -84,6 +88,8 @@ xa start 'ro_2'; select count(*) into @s2 from t1; xa end 'ro_2'; xa prepare 'ro_2';; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect master_ro_2; connection master; connect master_$xid, 127.0.0.1,root,,$db,$MASTER_MYPORT,; @@ -93,17 +99,17 @@ select 1; 1 xa end 'ro_1'; xa prepare 'ro_1';; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect master_ro_1; -*** 2 prepared xa:s must be in the list: +*** Zero prepared xa:s must be in the list: connection master; xa recover; formatID gtrid_length bqual_length data -1 4 0 ro_1 -1 4 0 ro_2 *** Zero prepared xa:s must be in the list: xa recover; formatID gtrid_length bqual_length data -*** At the end of read-only section gtid list has 0 more compare with previous check: +*** At the end of the above read-only section gtid list must not change from the previous check: flush logs; show binlog events in 'master-bin.000003' limit 1,1; Log_name Pos Event_type Server_id End_log_pos Info @@ -123,15 +129,18 @@ connection master; xa recover; formatID gtrid_length bqual_length data 1 12 0 rw_no_binlog +set @@session.sql_log_bin = 0; +xa $complete '$xid'; +set @@session.sql_log_bin = 1; *** Zero must be in the list: connection master; xa recover; formatID gtrid_length bqual_length data -*** At the end of --binlog-ignore-db section gtid list has 2 more: +*** At the end of --binlog-ignore-db section gtid list remains the same. flush logs; show binlog events in 'master-bin.000004' limit 1,1; Log_name Pos Event_type Server_id End_log_pos Info -master-bin.000004 # Gtid_list 1 # [0-1-13] +master-bin.000004 # Gtid_list 1 # [0-1-11] connection master; create table t3 (a int) engine=innodb; *** the disconnected prepare case @@ -171,7 +180,7 @@ formatID gtrid_length bqual_length data flush logs; show binlog events in 'master-bin.000005' limit 1,1; Log_name Pos Event_type Server_id End_log_pos Info -master-bin.000005 # Gtid_list 1 # [0-1-18] +master-bin.000005 # Gtid_list 1 # [0-1-16] create table tm (a int) engine=myisam; connection master; connect master_$xid, 127.0.0.1,root,,$db,$MASTER_MYPORT,; @@ -179,6 +188,8 @@ xa start 'rw_myisam'; insert into tm set a=1; xa end 'rw_myisam'; xa prepare 'rw_myisam';; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect master_rw_myisam; connection master; connect master_$xid, 127.0.0.1,root,,$db,$MASTER_MYPORT,; @@ -186,20 +197,30 @@ xa start 'rw_myisam'; insert into tm set a=1; xa end 'rw_myisam'; xa prepare 'rw_myisam';; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect master_rw_myisam; *** rw_myisam prepared must be in the list: connection master; xa recover; formatID gtrid_length bqual_length data -1 9 0 rw_myisam *** Zero prepared xa:s must be in the list: xa recover; formatID gtrid_length bqual_length data -*** At the end of MyISAM "xa" section gtid list has 7 more compare with previous check: +*** At the end of MyISAM "xa" section gtid list has 3 more compare with previous check: flush logs; show binlog events in 'master-bin.000006' limit 1,1; Log_name Pos Event_type Server_id End_log_pos Info -master-bin.000006 # Gtid_list 1 # [0-1-25] +master-bin.000006 # Gtid_list 1 # [0-1-19] +set @@session.sql_log_bin = OFF; +connect master_$xid, 127.0.0.1,root,,$db,$MASTER_MYPORT,; +xa start 'skip_binlog'; +insert into t2 values(1); +xa end 'skip_binlog'; +xa prepare 'skip_binlog';; +xa $complete '$xid'; +set @@session.sql_log_bin = ON; +disconnect master_skip_binlog; connect master_$xid, 127.0.0.1,root,,$db,$MASTER_MYPORT,; set @@session.sql_log_bin = OFF; xa start 'skip_binlog'; @@ -224,7 +245,7 @@ formatID gtrid_length bqual_length data flush logs; show binlog events in 'master-bin.000007' limit 1,1; Log_name Pos Event_type Server_id End_log_pos Info -master-bin.000007 # Gtid_list 1 # [0-1-25] +master-bin.000007 # Gtid_list 1 # [0-1-21] connection slave; include/sync_with_master_gtid.inc connection master; diff --git a/mysql-test/suite/rpl/r/rpl_xa_survive_disconnect.result b/mysql-test/suite/rpl/r/rpl_xa_survive_disconnect.result index a295a5d4dc2d9..79c8b60fc12e1 100644 --- a/mysql-test/suite/rpl/r/rpl_xa_survive_disconnect.result +++ b/mysql-test/suite/rpl/r/rpl_xa_survive_disconnect.result @@ -99,6 +99,8 @@ a 2 XA END '4'; XA PREPARE '4'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect master2; connect master_bulk_conn$i, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'bulk_trx_10'; diff --git a/mysql-test/suite/rpl/r/rpl_xa_survive_disconnect_lsu_off.result b/mysql-test/suite/rpl/r/rpl_xa_survive_disconnect_lsu_off.result index a295a5d4dc2d9..79c8b60fc12e1 100644 --- a/mysql-test/suite/rpl/r/rpl_xa_survive_disconnect_lsu_off.result +++ b/mysql-test/suite/rpl/r/rpl_xa_survive_disconnect_lsu_off.result @@ -99,6 +99,8 @@ a 2 XA END '4'; XA PREPARE '4'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back disconnect master2; connect master_bulk_conn$i, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'bulk_trx_10'; diff --git a/mysql-test/suite/rpl/r/rpl_xa_survive_disconnect_mixed_engines.result b/mysql-test/suite/rpl/r/rpl_xa_survive_disconnect_mixed_engines.result index 09bfffc0da452..e0c9317d3d6e9 100644 --- a/mysql-test/suite/rpl/r/rpl_xa_survive_disconnect_mixed_engines.result +++ b/mysql-test/suite/rpl/r/rpl_xa_survive_disconnect_mixed_engines.result @@ -22,7 +22,11 @@ XA START 'xa_trx'; INSERT INTO tm VALUES (3); XA END 'xa_trx'; XA PREPARE 'xa_trx'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back XA COMMIT 'xa_trx' ; +Warnings: +Warning 1196 Some non-transactional changed tables couldn't be rolled back CREATE TEMPORARY TABLE tmp_i LIKE t; CREATE TEMPORARY TABLE tmp_m LIKE tm; XA START 'xa_trx'; @@ -87,8 +91,13 @@ XA PREPARE 'xa_trx'; XA COMMIT 'xa_trx' ; XA START 'xa_trx'; UPDATE t SET a = 99 where a = -1; +SELECT ROW_COUNT(); +ROW_COUNT() +0 XA END 'xa_trx'; XA PREPARE 'xa_trx'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back XA COMMIT 'xa_trx' ; include/sync_slave_sql_with_master.inc connection master; @@ -166,6 +175,9 @@ XA END 'xa_trx'; XA COMMIT 'xa_trx' ONE PHASE; XA START 'xa_trx'; UPDATE t SET a = 99 where a = -1; +SELECT ROW_COUNT(); +ROW_COUNT() +0 XA END 'xa_trx'; XA COMMIT 'xa_trx' ONE PHASE; include/sync_slave_sql_with_master.inc @@ -191,6 +203,8 @@ XA START 'xa_trx'; INSERT INTO tm VALUES (3); XA END 'xa_trx'; XA PREPARE 'xa_trx'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back xa rollback 'xa_trx' ; Warnings: Warning 1196 Some non-transactional changed tables couldn't be rolled back @@ -268,8 +282,13 @@ Warnings: Warning 1196 Some non-transactional changed tables couldn't be rolled back XA START 'xa_trx'; UPDATE t SET a = 99 where a = -1; +SELECT ROW_COUNT(); +ROW_COUNT() +0 XA END 'xa_trx'; XA PREPARE 'xa_trx'; +Warnings: +Note 4183 Found to be read-only XA transaction is rolled back xa rollback 'xa_trx' ; include/sync_slave_sql_with_master.inc connection master; @@ -363,6 +382,9 @@ Warnings: Warning 1196 Some non-transactional changed tables couldn't be rolled back XA START 'xa_trx'; UPDATE t SET a = 99 where a = -1; +SELECT ROW_COUNT(); +ROW_COUNT() +0 XA END 'xa_trx'; xa rollback 'xa_trx' ; include/sync_slave_sql_with_master.inc diff --git a/mysql-test/suite/rpl/t/rpl_create_xa_prepared.inc b/mysql-test/suite/rpl/t/rpl_create_xa_prepared.inc index b823ebf62ee9a..05adc1682b9ac 100644 --- a/mysql-test/suite/rpl/t/rpl_create_xa_prepared.inc +++ b/mysql-test/suite/rpl/t/rpl_create_xa_prepared.inc @@ -1,6 +1,6 @@ # param $xid to name xa and take part in the connection name # param $query to execute as the xa body -# param $db_ign the default database +# param $db the default database --connect (master_$xid, 127.0.0.1,root,,$db,$MASTER_MYPORT,) --eval xa start '$xid' diff --git a/mysql-test/suite/rpl/t/rpl_parallel_multi_domain_xa.test b/mysql-test/suite/rpl/t/rpl_parallel_multi_domain_xa.test index f17634d4df816..b1c0b38eb6f9d 100644 --- a/mysql-test/suite/rpl/t/rpl_parallel_multi_domain_xa.test +++ b/mysql-test/suite/rpl/t/rpl_parallel_multi_domain_xa.test @@ -69,7 +69,9 @@ while ($mode) --eval XA START '$conn$trx$i' --eval UPDATE t1 SET b = 1 - 2 * $decision WHERE a = 1 --eval XA END '$conn$trx$i' + set @@session.note_verbosity=""; --eval XA PREPARE '$conn$trx$i' + set @@session.note_verbosity=default; --let $term = COMMIT if ($decision) { diff --git a/mysql-test/suite/rpl/t/rpl_parallel_optimistic_xa.test b/mysql-test/suite/rpl/t/rpl_parallel_optimistic_xa.test index 6e5cf4367a725..2ce20e1cc7c27 100644 --- a/mysql-test/suite/rpl/t/rpl_parallel_optimistic_xa.test +++ b/mysql-test/suite/rpl/t/rpl_parallel_optimistic_xa.test @@ -56,13 +56,26 @@ while($i > 0) { # 'decision' to commit 0, or rollback 1 --let $decision = `SELECT $i % 2` + --let $b = `SELECT 1 - 2 * $decision` --eval XA START '$conn$trx$i' - --eval UPDATE t1 SET b = 1 - 2 * $decision WHERE a = 1 + --let $ignore_note = `select @@session.binlog_format = 'ROW' AND b = $b from t1 where a = 1` + --eval UPDATE t1 SET b = $b WHERE a = 1 --eval XA END '$conn$trx$i' - --let $one_phase = `SELECT IF(floor(rand()*10)%2, "ONE PHASE", 0)` + --let $one_phase = `SELECT IF($i%2, "ONE PHASE", 0)` if (!$one_phase) { + # Only ROW format can produce empty XA-prepare so generates notes. + # STATEMENT (MIXED) format case features actual binlogging yet + # of ineffective in Engine updates (same record updated). + if ($ignore_note) + { + set @@session.note_verbosity=""; + } --eval XA PREPARE '$conn$trx$i' + if ($ignore_note) + { + set @@session.note_verbosity=default; + } --let $one_phase = } @@ -85,7 +98,7 @@ while($i > 0) --source include/stop_slave.inc -# II. Logging XS:s from multiple connections in random interweaving manner: +# II. Logging XS:s from multiple connections in interweaving manner: # # in a loop ($i) per connection # arrange an inner ($k) loop where @@ -126,7 +139,7 @@ while($i > 0) # the row id of the last connection that committed its XA --let $c_max = 1 --let $k = 0 - while ($decision < 3) + while ($decision < 4) { --inc $k --eval XA START '$conn_i$trx$k' @@ -147,7 +160,6 @@ while($i > 0) --eval XA END '$conn_i$trx$k' --let $term = COMMIT - --let $decision = `SELECT (floor(rand()*10 % 10) + ($i+$k)) % 4` if ($decision == 1) { --let $term = ROLLBACK @@ -162,6 +174,8 @@ while($i > 0) { --eval XA COMMIT '$conn_i$trx$k' ONE PHASE } + + --inc $decision } # $decision = 3 diff --git a/mysql-test/suite/rpl/t/rpl_xa.inc b/mysql-test/suite/rpl/t/rpl_xa.inc index 7fb42d711af58..361fbdc35a716 100644 --- a/mysql-test/suite/rpl/t/rpl_xa.inc +++ b/mysql-test/suite/rpl/t/rpl_xa.inc @@ -71,8 +71,7 @@ let $diff_tables= master:t2, slave:t2; source include/diff_tables.inc; # -# Read-only XA remains prepared after disconnect and must rollback at XA-complete -# after recoonect. To the read-only also belongs non-transactional engine XA. +# Read-only XA is not binlogged and is rolled back by disconnect. # --connection master @@ -128,7 +127,7 @@ while ($p_trx) --dec $p_trx } ---echo *** $ro_cases prepared xa:s must be in the list: +--echo *** Zero prepared xa:s must be in the list: --connection master sorted_result; xa recover; @@ -140,7 +139,7 @@ while ($p_trx) --let $complete=`select if(floor(rand()*10)%2,'COMMIT','ROLLBACK')` --disable_query_log --disable_result_log - --error ER_XA_RBROLLBACK + --error ER_XAER_NOTA --eval xa $complete '$xid' --enable_result_log --enable_query_log @@ -150,7 +149,7 @@ while ($p_trx) --echo *** Zero prepared xa:s must be in the list: xa recover; ---echo *** At the end of read-only section gtid list has 0 more compare with previous check: +--echo *** At the end of the above read-only section gtid list must not change from the previous check: flush logs; --let $binlog_file= query_get_value(SHOW MASTER STATUS, File, 1) --source include/show_gtid_list.inc @@ -160,8 +159,9 @@ flush logs; # XA logging cases while some of XA resources are read-only # # A1. Binlog filter - - +# The empty XAP is created. It reaches slave though where it is read-only +# optimized. This affects the following XA-complete which causes slave error out. +# --let $db=test_ign --eval create database $db set @@sql_log_bin = 0; @@ -179,12 +179,13 @@ set @@sql_log_bin = 1; xa recover; --let $complete=`select if(floor(rand()*10)%2,'COMMIT','ROLLBACK')` ---error 0 ---disable_query_log ---disable_result_log ---eval xa $complete '$xid' ---enable_result_log ---enable_query_log +# ignore-db filtered out XA-PREPARE is empty-binlogged and ... +# as of now unnecessarily replicated. +# todo: implement SKIP_REPLICATION flag for either of XA -PREPARE and -COMPLETE +# As a work-around skip binlogging of its completion. +set @@session.sql_log_bin = 0; +--evalp xa $complete '$xid' +set @@session.sql_log_bin = 1; --echo *** Zero must be in the list: --connection master @@ -192,15 +193,15 @@ xa recover; # restore for the following tests --let $db=test ---echo *** At the end of --binlog-ignore-db section gtid list has 2 more: +--echo *** At the end of --binlog-ignore-db section gtid list remains the same. flush logs; --let $binlog_file= query_get_value(SHOW MASTER STATUS, File, 1) --source include/show_gtid_list.inc # # A2. Opposite to A1, ineffective execution in Engine may create a -# binlog transaction -# +# binlog transaction. +# This case is non-crash-recoverable by design of "maybe-all" record DELETE. connection master; create table t3 (a int) engine=innodb; @@ -265,8 +266,8 @@ flush logs; --source include/show_gtid_list.inc # -# A3 MyISAM "xa" logs empty XA-prepare group, followed by -# an XA-complete event +# A3 MyISAM "xa" does not log even empty XA-prepare group. +# Such transaction is considered to be read-only so optimized away. create table tm (a int) engine=myisam; # No disconnect @@ -298,6 +299,8 @@ xa recover; --let $complete=`select if(floor(rand()*10)%2,'COMMIT','ROLLBACK')` --disable_query_log --disable_result_log + # the result of read-only optimization + --error ER_XAER_NOTA --eval xa $complete '$xid' --enable_result_log --enable_query_log @@ -305,18 +308,27 @@ xa recover; --echo *** Zero prepared xa:s must be in the list: xa recover; ---echo *** At the end of MyISAM "xa" section gtid list has 7 more compare with previous check: +--echo *** At the end of MyISAM "xa" section gtid list has 3 more compare with previous check: flush logs; --let $binlog_file= query_get_value(SHOW MASTER STATUS, File, 1) --source include/show_gtid_list.inc -# B. Session binlog disable does not log even empty XA-prepare -# Therefore XA-complete should be also run in sql_log_bin-OFF environment. +# B. Session binlog disable does log even empty XA-prepare. +# As a workaround XA-complete should be also run in sql_log_bin=OFF. --let $db=test --let $xid=skip_binlog --let $query=insert into t2 values(1) +set @@session.sql_log_bin = OFF; + --source rpl_create_xa_prepared.inc + --let $complete=`select if(floor(rand()*10)%2,'COMMIT','ROLLBACK')` + --evalp xa $complete '$xid' +set @@session.sql_log_bin = ON; + --disconnect master_$xid + --source include/wait_until_disconnected.inc + +# with disconnect --connect (master_$xid, 127.0.0.1,root,,$db,$MASTER_MYPORT,) set @@session.sql_log_bin = OFF; --eval xa start '$xid' diff --git a/mysql-test/suite/rpl/t/rpl_xa_concurrent_2pc.test b/mysql-test/suite/rpl/t/rpl_xa_concurrent_2pc.test new file mode 100644 index 0000000000000..12d07cf327c0b --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_xa_concurrent_2pc.test @@ -0,0 +1,111 @@ +# +# This test ensures that two-phase XA transactions have their first and +# second phases parallelized for both XA COMMIT and XA ROLLBACK. It ensures the +# following behaviors: +# +# Test Case 1: Ensure that a 2-phase XA transaction has its XA PREPARE and +# XA COMMIT/ROLLBACK run concurrently. That is, the XA COMMIT/ROLLBACK will +# wait at group commit until the XA PREPARE binlogs, and then it will wait +# again until the XA PREPARE finishes preparing in all engines. At this point, +# the XA COMMIT/ROLLBACK will run to completion. +# +# Test Case 2: If two XA transactions have different XIDs, if XA COMMIT ends +# a transaction, ensure both phases of both transactions can all execute +# concurrently. +# +# Test Case 3: Two current 2-phase XA transactions with matching XIDs should +# run one after the other, while each transaction still allows both phases of +# its own transaction to run concurrently. +# +# Test Case 4: Error Case. If an XAP errors while its XAC/R is waiting on it, +# both the XAP and XAC/R should rollback successfully. Note this tests both: +# a) XAC/R is waiting in group commit (first phase times out in DMLs) +# b) XAC/R is waiting in group commit, with another XAP with a duplicate XID +# waiting on it. +# +# Test Case 5: If an XAP is skipped by the replica (e.g. by incorrectly +# setting gtid_slave_pos), and only its XAC/XAR is tried to execute, the +# replica should report ER_XAER_NOTA. +# +# +# References: +# MDEV-31949: slow parallel replication of user xa +# +--source include/have_debug.inc +--source include/have_innodb.inc +--source include/have_binlog_format_mixed.inc +--source include/master-slave.inc + +--let $xa_complete_sym= COMMIT +--source include/rpl_xa_concurrent_2pc.inc + +--let $xa_complete_sym= ROLLBACK +--source include/rpl_xa_concurrent_2pc.inc + +--echo # +--echo # Test Case 5: If an XAP is skipped by the replica (e.g. by incorrectly +--echo # setting gtid_slave_pos), and only its XAC/XAR is tried to execute, the +--echo # replica should report ER_XAER_NOTA. + +--connection master +create table t1 (a int) engine=innodb; +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc +call mtr.add_suppression("XAER_NOTA: Unknown XID"); + +--let $i=2 +while ($i) +{ + --source include/stop_slave.inc + --replace_regex /[0-9]*-[0-9]*-[0-9]*// + change master to master_use_gtid = slave_pos; + + --connection master + --let $complete=rollback + if ($i == 1) + { + --let $complete=commit + } + xa start '1'; insert into t1 set a=1; xa end '1'; xa prepare '1'; + --eval xa $complete '1' + insert into t1 set a=2; + --source include/save_master_gtid.inc + + --connection slave + + # reposition the slave to skip one transaction from master + set @save_gtid_slave_pos= @@global.gtid_slave_pos; + SELECT CONCAT(domain_id,"-",server_id,"-", seq_no + 1) + into @gtid_skip + FROM mysql.gtid_slave_pos + WHERE seq_no = (SELECT DISTINCT max(seq_no) FROM mysql.gtid_slave_pos) limit 1; + set @@global.gtid_slave_pos = @gtid_skip; + + start slave; + let $slave_sql_errno= 1397; # ER_XAER_NOTA + source include/wait_for_slave_sql_error.inc; + --eval select count(*) = $i % 2 as 'must be true' from t1; + --let $rpl_allow_error = 1 + --source include/stop_slave.inc + + --disable_warnings + set @@global.gtid_slave_pos = @save_gtid_slave_pos; + --enable_warnings + --replace_regex /[0-9]*-[0-9]*-[0-9]*// + show warnings; + --source include/start_slave.inc + --source include/sync_with_master_gtid.inc + + --dec $i +} + +# MDEV-31949 cleanup +--connection master +drop table t1; + +--sync_slave_with_master +--source include/rpl_end.inc +--echo # End of rpl_xa_concurrent_2pc.test + diff --git a/mysql-test/suite/rpl/t/rpl_xa_concurrent_2pc_lsu_off-slave.opt b/mysql-test/suite/rpl/t/rpl_xa_concurrent_2pc_lsu_off-slave.opt new file mode 100644 index 0000000000000..acd68493e0aeb --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_xa_concurrent_2pc_lsu_off-slave.opt @@ -0,0 +1 @@ +--log-slave-updates=0 diff --git a/mysql-test/suite/rpl/t/rpl_xa_concurrent_2pc_lsu_off.test b/mysql-test/suite/rpl/t/rpl_xa_concurrent_2pc_lsu_off.test new file mode 100644 index 0000000000000..547d04990ec59 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_xa_concurrent_2pc_lsu_off.test @@ -0,0 +1 @@ +--source rpl_xa_concurrent_2pc.test diff --git a/mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test b/mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test index 85b6bcdb87397..a422616f7ada2 100644 --- a/mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test +++ b/mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test @@ -111,7 +111,8 @@ set @@global.binlog_format = @sav_binlog_format; --echo # --echo # Test Case 5: A mixed XA transaction consisting of one statement that --echo # can successfully be rolled back (first statement), and another that ---echo # can not (second statement) should be binlogged +--echo # can not (second statement) should be binlogged similarly to the normal +--echo # transaction to reflect the 2nd statement. --connection server_1 set @sav_binlog_format = @@binlog_format; @@ -133,7 +134,8 @@ set @@global.binlog_format = @sav_binlog_format; --echo # --echo # Test Case 6: A mixed XA transaction consisting of one statement that --echo # cannot successfully be rolled back (first statement), and another that ---echo # can (second statement) should be binlogged +--echo # can (second statement) should be binlogged similarly to the normal +--echo # transaction to reflect the 1st statement. --connection server_1 set @sav_binlog_format = @@binlog_format; @@ -153,7 +155,7 @@ set @@global.binlog_format = @sav_binlog_format; --echo # --echo # Test Case 7: An XA transaction consisting of two failed ---echo # non-transactional statements should be binlogged +--echo # non-transactional statements should be binlogged akin to 5,6. --connection server_1 set @sav_binlog_format = @@binlog_format; @@ -296,7 +298,53 @@ SET @@GLOBAL.replicate_do_db=""; --connection server_1 drop database db1; drop database db2; +use test; +# MDEV-32257 dangling XA-rollback in binlog from emtpy XA +# create a case of XA ROLLBACK gets to binlog while its XAP was not and +# try replicate it. +# Expected result is both slaves error out. +--connection server_1 +--let $binlog_start = query_get_value(SHOW MASTER STATUS, Position, 1) +--let $binlog_file = query_get_value(SHOW MASTER STATUS, File, 1) +create table t_not_in_binlog (a int) engine=innodb; +flush logs; +--source include/save_master_gtid.inc +--let $binlog_file=query_get_value(SHOW MASTER STATUS, File, 1) + +# External connection XID access after disconnect is subject to race. +# "(" open parenthesis to remember # of connection before ... +--source include/count_sessions.inc + +--connect(con1,localhost,root,,) +call mtr.add_suppression("XAER_NOTA: Unknown XID"); + +SET sql_log_bin=0; +XA START 'a'; +insert into t_not_in_binlog set a=1; +XA END 'a'; +XA PREPARE 'a'; +--disconnect con1 + +--connection server_1 +# .. ")" close parenthesis, to wait until con1 fully releases access to xid. +--source include/wait_until_count_sessions.inc +xa recover; +# +# replicate orphan XAR to server 2,3 and expect the error first +# after that compensate it. + +--error 0 +XA ROLLBACK 'a'; +# cleanup at once +drop table t_not_in_binlog; +--source include/save_master_gtid.inc + +--connection server_2 +--source include/sync_with_master_gtid.inc + +--connection server_3 +--source include/sync_with_master_gtid.inc # # Cleanup diff --git a/mysql-test/suite/rpl/t/rpl_xa_prepare_gtid_fail.test b/mysql-test/suite/rpl/t/rpl_xa_prepare_gtid_fail.test index aa1b088ed232c..1fb7677e087ee 100644 --- a/mysql-test/suite/rpl/t/rpl_xa_prepare_gtid_fail.test +++ b/mysql-test/suite/rpl/t/rpl_xa_prepare_gtid_fail.test @@ -16,9 +16,9 @@ # MDEV-31038: Parallel Replication Breaks if XA PREPARE Fails Updating Slave # GTID State # -source include/master-slave.inc; source include/have_binlog_format_row.inc; source include/have_innodb.inc; +source include/master-slave.inc; --connection slave --source include/stop_slave.inc diff --git a/sql/handler.cc b/sql/handler.cc index 13c6473746dc7..7e1b1188f76d4 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -1534,9 +1534,22 @@ int ha_prepare(THD *thd) if (ha_info) { - for (; ha_info; ha_info= ha_info->next()) + int err; + bool is_rw, has_binlog; + + for (is_rw= false, has_binlog= false; ha_info; ha_info= ha_info->next()) { transaction_participant *ht= ha_info->ht(); + + if (ht == &binlog_tp) + { + /* + The marking may be optimistic when the transaction cache is empty. + However it's safe as binlog_prepare et al would return early. + */ + has_binlog= true; + continue; + } if (ht->prepare) { if (unlikely(prepare_or_error(ht, thd, all))) @@ -1546,6 +1559,7 @@ int ha_prepare(THD *thd) error=1; break; } + is_rw= is_rw || ha_info->is_trx_read_write(); } else { @@ -1556,17 +1570,27 @@ int ha_prepare(THD *thd) } } - - DEBUG_SYNC(thd, "at_unlog_xa_prepare"); - - if (tc_log->unlog_xa_prepare(thd, all)) + // engine read-only must proceed into binlogging by slave applier + if (!is_rw && !thd->rgi_slave) + { + goto ro_xa; + } + else if (has_binlog && (err= prepare_or_error(&binlog_tp, thd, all))) { ha_rollback_trans(thd, all); - error=1; + my_error(ER_ERROR_DURING_COMMIT, MYF(0), err); + error= 1; + goto err; } } - else if (thd->rgi_slave) + else { +ro_xa: + if (!thd->rgi_slave) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_XA_RDONLY, ER_THD(thd, ER_XA_RDONLY), ""); + } /* Slave threads will always process XA COMMITs in the binlog handler (see MDEV-25616 and MDEV-30423), so if this is a slave thread preparing a @@ -1578,10 +1602,14 @@ int ha_prepare(THD *thd) If the xid_cache is cleared before the completion event comes, before issuing ER_XAER_NOTA, first check if the event targets an ignored database, and ignore the error if so. + + The above policy is generalized over any user read-only xa, de-facto + implementing the XA specs' read-only optimization. */ thd->transaction->xid_state.set_rollback_only(); } +err: DBUG_RETURN(error); } @@ -2170,7 +2198,7 @@ static bool is_ro_1pc_trans(THD *thd, Ha_trx_info *ha_info, bool all, return !rw_trans; } -static bool has_binlog_hton(Ha_trx_info *ha_info) +bool has_binlog_hton(Ha_trx_info *ha_info) { bool rc; for (rc= false; ha_info && !rc; ha_info= ha_info->next()) @@ -2336,11 +2364,22 @@ int ha_rollback_trans(THD *thd, bool all) if (is_real_trans) /* not a statement commit */ thd->stmt_map.close_transient_cursors(); + int err; + bool has_binlog= has_binlog_hton(ha_info); + + DBUG_ASSERT(thd->lex->sql_command != SQLCOM_XA_ROLLBACK || + thd->transaction->xid_state.is_explicit_XA()); + + if (has_binlog && (err= binlog_tp.rollback(thd, all))) + { + my_error(ER_ERROR_DURING_COMMIT, MYF(0), err); + error= 1; + } for (; ha_info; ha_info= ha_info_next) { - int err; transaction_participant *ht= ha_info->ht(); - if ((err= ht->rollback(thd, all))) + if ((ht != &binlog_tp) && + (err= ht->rollback(thd, all))) { // cannot happen my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err); @@ -2426,15 +2465,15 @@ int ha_rollback_trans(THD *thd, bool all) DBUG_RETURN(error); } - struct xahton_st { XID *xid; int result; }; +/* Commit an engine branch of XA */ static bool xacommit_handlerton(THD *, transaction_participant *hton, void *arg) { - if (hton->recover) + if (hton->recover && hton != &binlog_tp) { hton->commit_by_xid(((struct xahton_st *)arg)->xid); ((struct xahton_st *)arg)->result= 0; @@ -2442,9 +2481,10 @@ static bool xacommit_handlerton(THD *, transaction_participant *hton, void *arg) return FALSE; } -static bool xarollback_handlerton(THD *, transaction_participant *hton, void *arg) +/* Rollback an engine branch of XA */ +bool xarollback_handlerton(THD *, transaction_participant *hton, void *arg) { - if (hton->recover) + if (hton->recover && hton != &binlog_tp) { hton->rollback_by_xid(((struct xahton_st *)arg)->xid); ((struct xahton_st *)arg)->result= 0; @@ -2452,25 +2492,49 @@ static bool xarollback_handlerton(THD *, transaction_participant *hton, void *ar return FALSE; } +/* completes xa transaction in engine */ +int commit_or_rollback_xa_engine(XID *xid, bool is_commit) +{ + struct xahton_st xaop= { xid, 1 }; + tp_foreach(NULL, is_commit ? xacommit_handlerton : xarollback_handlerton, + &xaop); + + return xaop.result; +} -int ha_commit_or_rollback_by_xid(XID *xid, bool commit) +/* + The function completes a prepared XA transaction. + It consists of two disjoint branches. + When the transaction has a binlog branch the completion must be logged + before the Engine action. The binlog-enabled path executes the engine + actions as part of binlog-group-ordered commit. + When binlog is disabled on slave the completion has to wait for its + turn to commit/rollback. +*/ +int ha_commit_or_rollback_by_xid(XID *xid, bool is_commit, THD *thd) { - struct xahton_st xaop; - xaop.xid= xid; - xaop.result= 1; + int rc= 0; + bool skip_binlog= + thd->is_current_stmt_binlog_disabled() || !is_xap_binlogged(thd); - /* - When the binlogging service is enabled complete the transaction - by it first. - */ - if (commit) - binlog_commit_by_xid(xid); + if (!skip_binlog) + { + // when binlog is ON start from its transaction branch + if (is_commit) + binlog_commit_by_xid(xid); + else + binlog_rollback_by_xid(xid); + } else - binlog_rollback_by_xid(xid); - - tp_foreach(NULL, commit ? xacommit_handlerton : xarollback_handlerton, &xaop); + { + int rc= !thd->rgi_slave ? 0 : thd->wait_for_prior_commit(); + if (!rc) + { + rc= commit_or_rollback_xa_engine(xid, is_commit); + } + } - return xaop.result; + return rc; } @@ -2834,7 +2898,12 @@ static bool xarecover_handlerton(THD *, transaction_participant *hton, void *arg char buf[XIDDATASIZE*4+6]; _db_doprnt_("ignore xid %s", xid_to_str(buf, info->list[i])); }); - xid_cache_insert(info->list + i); + /* + TODO: the xa crash recovery is not ready yet. The passed `true` + parameter value assumes the xid is found in binlog + which is of course "optimistic". + */ + xid_cache_insert(info->list + i, true); info->found_foreign_xids++; continue; } diff --git a/sql/handler.h b/sql/handler.h index 9081e8350b04f..cb52a584f563c 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -5715,7 +5715,8 @@ int ha_change_key_cache(KEY_CACHE *old_key_cache, KEY_CACHE *new_key_cache); /* transactions: interface to handlerton functions */ int ha_start_consistent_snapshot(THD *thd); -int ha_commit_or_rollback_by_xid(XID *xid, bool commit); +int ha_commit_or_rollback_by_xid(XID *xid, bool commit, THD *thd); +int commit_or_rollback_xa_engine(XID *xid, bool is_commit); int ha_commit_one_phase(THD *thd, bool all); int ha_commit_trans(THD *thd, bool all); int ha_rollback_trans(THD *thd, bool all); diff --git a/sql/log.cc b/sql/log.cc index 37139d239fb43..cf8863ed1abb1 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -1996,9 +1996,26 @@ inline size_t serialize_with_xid(XID *xid, char *buf, q_len + strlen(static_cast(xid)->serialize(buf + q_len)); } +static inline int write_xa_end(THD *thd, XID *xid, binlog_cache_mngr *cache_mngr) +{ + const char query[]= "XA END "; + const size_t q_len= sizeof(query) - 1; + char buf[q_len + ser_buf_size]; + size_t buflen; + binlog_cache_data *cache_data= cache_mngr->get_binlog_cache_data(true); + IO_CACHE *file= &cache_data->cache_log; + + memcpy(buf, query, q_len); + buflen= + q_len + strlen(static_cast(xid)->serialize(buf + q_len)); + cache_data= cache_mngr->get_binlog_cache_data(true); + Query_log_event xa_end(thd, buf, buflen, true, false, true, 0); + + return mysql_bin_log.write_event(&xa_end, cache_data, file); +} /** - This function flushes the trx-cache upon commit. + This function flushes the trx-cache at commit or XA-PREPARE. @param thd The thread whose transaction should be flushed @param cache_mngr Pointer to the cache manager @@ -2012,24 +2029,49 @@ binlog_commit_flush_trx_cache(THD *thd, bool all, binlog_cache_mngr *cache_mngr, { DBUG_ENTER("binlog_commit_flush_trx_cache"); - const char query[]= "XA COMMIT "; - const size_t q_len= sizeof(query) - 1; // do not count trailing 0 - char buf[q_len + ser_buf_size]= "COMMIT"; - size_t buflen= sizeof("COMMIT") - 1; - - if (thd->lex->sql_command == SQLCOM_XA_COMMIT && - thd->lex->xa_opt != XA_ONE_PHASE) + if (thd->lex->sql_command == SQLCOM_XA_PREPARE) { - DBUG_ASSERT(thd->transaction->xid_state.is_explicit_XA()); - DBUG_ASSERT(thd->transaction->xid_state.get_state_code() == - XA_PREPARED); + XID *xid= thd->transaction->xid_state.get_xid(); + /* + Log the XA END event first. + We don't do that in trans_xa_end() as XA COMMIT ONE PHASE + is logged as simple BEGIN/COMMIT so the XA END should + not get to the log. + */ + if (write_xa_end(thd, xid, cache_mngr)) + DBUG_RETURN(1); - buflen= serialize_with_xid(thd->transaction->xid_state.get_xid(), - buf, query, q_len); + cache_mngr->using_xa= FALSE; // need not engine commit ordered + XA_prepare_log_event end_evt(thd, xid, FALSE); + + DBUG_RETURN(binlog_flush_cache(thd, cache_mngr, &end_evt, all, + FALSE, TRUE, ro_1pc)); } - Query_log_event end_evt(thd, buf, buflen, TRUE, TRUE, TRUE, 0); + else + { + const char query[]= "XA COMMIT "; + const size_t q_len= sizeof(query) - 1; // do not count trailing 0 + char buf[q_len + ser_buf_size]= "COMMIT"; + size_t buflen= sizeof("COMMIT") - 1; + bool using_stmt_cache= true; - DBUG_RETURN(binlog_flush_cache(thd, cache_mngr, &end_evt, all, FALSE, TRUE, ro_1pc)); + if (thd->lex->sql_command == SQLCOM_XA_COMMIT && + thd->lex->xa_opt != XA_ONE_PHASE) + { + DBUG_ASSERT(thd->transaction->xid_state.is_explicit_XA()); + DBUG_ASSERT(thd->transaction->xid_state.get_state_code() == + XA_PREPARED); + + buflen= serialize_with_xid(thd->transaction->xid_state.get_xid(), + buf, query, q_len); + cache_mngr->using_xa= TRUE; // to take part in group commit + using_stmt_cache= false; + } + Query_log_event end_evt(thd, buf, buflen, TRUE, TRUE, TRUE, 0); + + DBUG_RETURN(binlog_flush_cache(thd, cache_mngr, &end_evt, all, + using_stmt_cache, TRUE, ro_1pc)); + } } @@ -2057,6 +2099,7 @@ binlog_rollback_flush_trx_cache(THD *thd, bool all, if (thd->transaction->xid_state.get_state_code() == XA_PREPARED) buflen= serialize_with_xid(thd->transaction->xid_state.get_xid(), buf, query, q_len); + cache_mngr->using_xa= TRUE; } Query_log_event end_evt(thd, buf, buflen, TRUE, TRUE, TRUE, 0); @@ -2158,26 +2201,38 @@ inline bool is_preparing_xa(THD *thd) } +/** + Does nothing in the normal transaction case. + In the user XA the image of the prepared transaction is binlogged. + @retval + 0 as normal + non-zero as an error +*/ static int binlog_prepare(THD *thd, bool all) { /* Do nothing unless the transaction is a user XA. */ return is_preparing_xa(thd) ? binlog_commit(thd, all, FALSE) : 0; } +/* + Log an xa completion event from an exteral connection. + For that an associated with the XA-"complete" "subtransaction" binlog branch + needs registering. It's empty but binlog_"complete"() honor its presence + to produce the aimed binlog event. -int binlog_commit_by_xid(XID *xid) + The function is invoked only when its prepared state has been logged. +*/ +inline int binlog_complete_by_xid(XID *xid, bool is_commit) { int rc= 0; THD *thd= current_thd; - if (thd->is_current_stmt_binlog_disabled()) - { - return thd->wait_for_prior_commit(); - } + DBUG_ASSERT(is_xap_binlogged(thd)); /* the asserted state can't be reachable with xa commit */ - DBUG_ASSERT(!thd->get_stmt_da()->is_error() || - thd->get_stmt_da()->sql_errno() != ER_XA_RBROLLBACK); + DBUG_ASSERT(!is_commit || + (!thd->get_stmt_da()->is_error() || + thd->get_stmt_da()->sql_errno() != ER_XA_RBROLLBACK)); /* This is a recovered user xa transaction commit. Create a "temporary" binlog transaction to write the commit record @@ -2188,46 +2243,43 @@ int binlog_commit_by_xid(XID *xid) thd->ha_data[binlog_tp.slot].ha_info[1].register_ha(&trans, &binlog_tp); thd->ha_data[binlog_tp.slot].ha_info[1].set_trx_read_write(); - (void) thd->binlog_setup_trx_data(); - - DBUG_ASSERT(thd->lex->sql_command == SQLCOM_XA_COMMIT); + binlog_cache_mngr *cache_mngr= thd->binlog_setup_trx_data(); + cache_mngr->using_xa= TRUE; // just as normal trx take part in group commit - rc= binlog_commit(thd, TRUE, FALSE); + rc= is_commit ? + binlog_commit(thd, TRUE, FALSE) : binlog_rollback(thd, TRUE); thd->ha_data[binlog_tp.slot].ha_info[1].reset(); return rc; } - -int binlog_rollback_by_xid(XID *xid) +int binlog_commit_by_xid(XID *xid) { - int rc= 0; THD *thd= current_thd; + DBUG_ASSERT(thd->lex->sql_command == SQLCOM_XA_COMMIT); + if (thd->is_current_stmt_binlog_disabled()) { return thd->wait_for_prior_commit(); } + return binlog_complete_by_xid(xid, true); +} - if (thd->get_stmt_da()->is_error() && - thd->get_stmt_da()->sql_errno() == ER_XA_RBROLLBACK) - return rc; - - THD_TRANS trans; - trans.ha_list= NULL; - - thd->ha_data[binlog_tp.slot].ha_info[1].register_ha(&trans, &binlog_tp); - thd->ha_data[binlog_tp.slot].ha_info[1].set_trx_read_write(); - (void) thd->binlog_setup_trx_data(); +int binlog_rollback_by_xid(XID *xid) +{ + int rc= 0; + THD *thd= current_thd; DBUG_ASSERT(thd->lex->sql_command == SQLCOM_XA_ROLLBACK || (thd->transaction->xid_state.get_state_code() == XA_ROLLBACK_ONLY)); - rc= binlog_rollback(thd, TRUE); - thd->ha_data[binlog_tp.slot].ha_info[1].reset(); - return rc; -} + if (thd->get_stmt_da()->is_error() && + thd->get_stmt_da()->sql_errno() == ER_XA_RBROLLBACK) + return rc; + return binlog_complete_by_xid(xid, false); +} inline bool is_prepared_xa(THD *thd) { @@ -2235,6 +2287,11 @@ inline bool is_prepared_xa(THD *thd) thd->transaction->xid_state.get_state_code() == XA_PREPARED; } +inline bool is_prepared_logged_xa(THD *thd) +{ + return is_prepared_xa(thd) && is_xap_binlogged(thd); +} + /* We flush the cache wrapped in a beginning/rollback if: @@ -2262,57 +2319,23 @@ static bool trans_cannot_safely_rollback(THD *thd, bool all) is_prepared_xa(thd)); } - -/** - Specific log flusher invoked through log_xa_prepare(). -*/ -static int binlog_commit_flush_xa_prepare(THD *thd, bool all, - binlog_cache_mngr *cache_mngr) +#ifndef DBUG_OFF +inline bool is_binlog_read_write(THD *thd) { - XID *xid= thd->transaction->xid_state.get_xid(); - { - // todo assert wsrep_simulate || is_open() - - /* - Log the XA END event first. - We don't do that in trans_xa_end() as XA COMMIT ONE PHASE - is logged as simple BEGIN/COMMIT so the XA END should - not get to the log. - */ - const char query[]= "XA END "; - const size_t q_len= sizeof(query) - 1; // do not count trailing 0 - char buf[q_len + ser_buf_size]; - size_t buflen; - binlog_cache_data *cache_data; - IO_CACHE *file; - - memcpy(buf, query, q_len); - buflen= q_len + - strlen(static_cast(xid)->serialize(buf + q_len)); - cache_data= cache_mngr->get_binlog_cache_data(true); - file= &cache_data->cache_log; - thd->lex->sql_command= SQLCOM_XA_END; - Query_log_event xa_end(thd, buf, buflen, true, false, true, 0); - if (mysql_bin_log.write_event(&xa_end, cache_data, file)) - return 1; - thd->lex->sql_command= SQLCOM_XA_PREPARE; - } - - cache_mngr->using_xa= FALSE; - XA_prepare_log_event end_evt(thd, xid, FALSE); - - return (binlog_flush_cache(thd, cache_mngr, &end_evt, all, TRUE, TRUE)); + return (thd->ha_data[binlog_tp.slot].ha_info[1].is_started() && + thd->ha_data[binlog_tp.slot].ha_info[1].is_trx_read_write()); } - +#endif /** This function is called once after each statement. - It has the responsibility to flush the caches to the binary log on commits. + It has the responsibility to flush the caches to the binary log on commits + of normal and XA transactions or XA-prepare. @param thd The client thread that executes the transaction. - @param all This is @c true if this is a real transaction commit, and - @false otherwise. + @param all This is @c true if this is a real transaction commit or + XA-prepare, and @false otherwise. @param ro_1pc read-only one-phase commit transaction */ int binlog_commit(THD *thd, bool all, bool ro_1pc) @@ -2368,44 +2391,40 @@ int binlog_commit(THD *thd, bool all, bool ro_1pc) #endif /* WITH_WSREP */ error= binlog_commit_flush_stmt_cache(thd, all, cache_mngr); } - if (cache_mngr->trx_cache.empty() && - (thd->transaction->xid_state.get_state_code() != XA_PREPARED || - !(thd->ha_data[binlog_tp.slot].ha_info[1].is_started() && - thd->ha_data[binlog_tp.slot].ha_info[1].is_trx_read_write()))) + if (cache_mngr->trx_cache.empty() && !is_prepared_logged_xa(thd)) { /* - This is an empty transaction commit (both the regular and xa), - or such transaction xa-prepare or - either one's statement having no effect on the transactional cache - as any prior to it. - The empty xa-prepare sinks in only when binlog is read-only. - */ + This is an empty transaction/statement commit (both the regular and xa), + or the xa transaction is running XA-COMPLETE in which case + its binlog branch must be read-write. + */ cache_mngr->reset(false, true); THD_STAGE_INFO(thd, org_stage); DBUG_RETURN(error); } + else + { + // XA-commmit time empty cache is marked read-write for further logging + DBUG_ASSERT(!is_prepared_xa(thd) || is_binlog_read_write(thd)); + } /* - We commit the transaction if: + We commit the transaction and log its changes if: - We are not in a transaction and committing a statement, or - - We are in a transaction and a full transaction is committed. + - We are in a transaction and a full transaction is committed; + We also log the trx cache of a being prepared XA transaction. Otherwise, we accumulate the changes. */ if (likely(!error) && is_ending_transaction) { - bool is_xa_prepare= is_preparing_xa(thd); - - error= is_xa_prepare ? - binlog_commit_flush_xa_prepare(thd, all, cache_mngr) : - binlog_commit_flush_trx_cache (thd, all, cache_mngr, ro_1pc); - // the user xa is unlogged on common exec path with the "empty" xa case - if (cache_mngr->need_unlog && !is_xa_prepare) - { - error= - mysql_bin_log.unlog(BINLOG_COOKIE_MAKE(cache_mngr->binlog_id, - cache_mngr->delayed_error), 1); - cache_mngr->need_unlog= false; - } + error= binlog_commit_flush_trx_cache(thd, all, cache_mngr, ro_1pc); + if (cache_mngr->need_unlog) + { + error= + mysql_bin_log.unlog(BINLOG_COOKIE_MAKE(cache_mngr->binlog_id, + cache_mngr->delayed_error), 1); + cache_mngr->need_unlog= false; + } } else if (thd->lock) { @@ -2468,17 +2487,17 @@ static int binlog_rollback(THD *thd, bool all) } if (!cache_mngr->trx_cache.has_incident() && cache_mngr->trx_cache.empty() && - (thd->transaction->xid_state.get_state_code() != XA_PREPARED || - !(thd->ha_data[binlog_tp.slot].ha_info[1].is_started() && - thd->ha_data[binlog_tp.slot].ha_info[1].is_trx_read_write()))) + !is_prepared_logged_xa(thd)) { - /* - The same comments apply as in the binlog commit method's branch. - */ + // see a similar brach in the binlog commit cache_mngr->reset(false, true); thd->reset_binlog_for_next_statement(); DBUG_RETURN(error); } + else + { + DBUG_ASSERT(!is_prepared_xa(thd) || is_binlog_read_write(thd)); + } if (!wsrep_emulate_bin_log && Event_log::check_write_error(thd)) { /* @@ -2486,8 +2505,9 @@ static int binlog_rollback(THD *thd, bool all) this function was called. However, this must not happen as a rollback is written directly to the binary log. And in auto-commit mode, a single statement that is rolled back has the flag all == false. + An errored "all == true" XA-PREPARE can turn in this block though. */ - DBUG_ASSERT(!all); + DBUG_ASSERT(!all || is_preparing_xa(thd)); /* We reach this point if the effect of a statement did not properly get into a cache and need to be rolled back. @@ -8512,7 +8532,7 @@ MYSQL_BIN_LOG::write_transaction_to_binlog(THD *thd, entry.all= all; entry.using_stmt_cache= using_stmt_cache; entry.using_trx_cache= using_trx_cache; - entry.need_unlog= is_preparing_xa(thd); + entry.need_unlog= 0; ha_info= all ? thd->transaction->all.ha_list : thd->transaction->stmt.ha_list; entry.ro_1pc= is_ro_1pc; entry.end_event= end_ev; @@ -9265,6 +9285,15 @@ void MYSQL_BIN_LOG::trx_group_commit_with_engines(group_commit_entry *leader, any_error= true; } #endif + + // mark the user xid to indicate XA- prepare and "complete" logged + XID_STATE *xid_state= ¤t->thd->transaction->xid_state; + if (is_preparing_xa(current->thd) || is_prepared_xa(current->thd)) + { + xid_cache_update_xa_binlog_state( + current->thd, xid_state, + (current->thd->lex->sql_command == SQLCOM_XA_PREPARE)); + } } /* @@ -10251,15 +10280,81 @@ TC_LOG::run_prepare_ordered(THD *thd, bool all) } } +/* + The user XA completion in group commit. + The completion - XA-COMMIT or XA-ROLLBACK goes through one of two braches, + depending on which of native or external connection the completion is issued. +*/ +inline void run_xa_complete_ordered(THD *thd) +{ + Ha_trx_info *ha_info= thd->transaction->all.ha_list; + + if (!ha_info) + { + // slave or master's xa-"complete" external + DBUG_ASSERT(thd->lex->sql_command == SQLCOM_XA_COMMIT || + thd->lex->sql_command == SQLCOM_XA_ROLLBACK); + // the prepare time flag is dropped by the completion's binlog write + DBUG_ASSERT(!is_xap_binlogged(thd)); + + XID *xid= thd->transaction->xid_state.get_xid(); + bool is_commit= thd->lex->sql_command == SQLCOM_XA_COMMIT; + commit_or_rollback_xa_engine(xid, is_commit); + + return; + } + for (; ha_info; ha_info= ha_info->next()) + { + DBUG_ASSERT(thd->lex->sql_command == SQLCOM_XA_COMMIT || + (thd->lex->sql_command == SQLCOM_XA_ROLLBACK || + (thd->lex->sql_command == SQLCOM_PRELOAD_KEYS && + thd->get_stmt_da()->get_sql_errno() /* MDEV-32455 */))); + + transaction_participant *ht= ha_info->ht(); + if (ht == &binlog_tp || !ht->commit_ordered) + continue; + if (thd->lex->sql_command == SQLCOM_XA_COMMIT) + { + ht->commit_ordered(thd, true); + } + else + { + DBUG_ASSERT((thd->lex->sql_command == SQLCOM_XA_ROLLBACK || + (thd->lex->sql_command == SQLCOM_PRELOAD_KEYS && thd->get_stmt_da()->get_sql_errno() /* MDEV-32455 */)) || + thd->transaction->xid_state.get_error() == + ER_XA_RBROLLBACK); + + ht->rollback(thd, true); + } + } +} +/* + The function executes the fast part of ordered commit in engine branches + for normal and XA transactions, as well as the entirety of XA rollback. +*/ void TC_LOG::run_commit_ordered(THD *thd, bool all) { - Ha_trx_info *ha_info= - all ? thd->transaction->all.ha_list : thd->transaction->stmt.ha_list; - mysql_mutex_assert_owner(&LOCK_commit_ordered); - for (; ha_info; ha_info= ha_info->next()) + + if (thd->transaction->xid_state.is_explicit_XA() && + likely(thd->transaction->xid_state.get_state_code() != XA_ACTIVE)) + { + DBUG_ASSERT(all); + run_xa_complete_ordered(thd); + + return; + } + else + { + DBUG_ASSERT(!thd->transaction->xid_state.is_explicit_XA() || + thd->transaction->xid_state.get_error() > 0); + } + + for (Ha_trx_info *ha_info= all ? + thd->transaction->all.ha_list : thd->transaction->stmt.ha_list; + ha_info; ha_info= ha_info->next()) { transaction_participant *ht= ha_info->ht(); if (!ht->commit_ordered) @@ -10274,7 +10369,6 @@ TC_LOG::run_commit_ordered(THD *thd, bool all) } } - int TC_LOG_MMAP::log_and_order(THD *thd, my_xid xid, bool all, bool need_prepare_ordered, bool need_commit_ordered) @@ -11390,49 +11484,6 @@ int TC_LOG_BINLOG::unlog(ulong cookie, my_xid xid) DBUG_RETURN(BINLOG_COOKIE_GET_ERROR_FLAG(cookie)); } -static bool write_empty_xa_prepare(THD *thd, binlog_cache_mngr *cache_mngr) -{ - return binlog_commit_flush_xa_prepare(thd, true, cache_mngr); -} - -int TC_LOG_BINLOG::unlog_xa_prepare(THD *thd, bool all) -{ - DBUG_ASSERT(is_preparing_xa(thd)); - - binlog_cache_mngr *cache_mngr= thd->binlog_setup_trx_data(); - int cookie= 0; - - if (!cache_mngr->need_unlog) - { - Ha_trx_info *ha_info; - uint rw_count= ha_count_rw_all(thd, &ha_info); - bool rc= false; - - /* - This transaction has not been binlogged as indicated by need_unlog. - Such exceptional cases include transactions with no effect to engines, - e.g REPLACE that does not change the dat but still the Engine - transaction branch claims to be rw, and few more. - In all such cases an empty XA-prepare group of events is bin-logged. - */ - if (rw_count > 0) - { - /* an empty XA-prepare event group is logged */ - rc= write_empty_xa_prepare(thd, cache_mngr); // normally gains need_unlog - trans_register_ha(thd, true, &binlog_tp, 0); // do it for future commmit - thd->ha_data[binlog_tp.slot].ha_info[1].set_trx_read_write(); - } - if (rw_count == 0 || !cache_mngr->need_unlog) - return rc; - } - - cookie= BINLOG_COOKIE_MAKE(cache_mngr->binlog_id, cache_mngr->delayed_error); - cache_mngr->need_unlog= false; - - return unlog(cookie, 1); -} - - void TC_LOG_BINLOG::commit_checkpoint_notify(void *cookie) { diff --git a/sql/log.h b/sql/log.h index dc95f33de63cd..849bac4351c50 100644 --- a/sql/log.h +++ b/sql/log.h @@ -62,7 +62,6 @@ class TC_LOG bool need_prepare_ordered, bool need_commit_ordered) = 0; virtual int unlog(ulong cookie, my_xid xid)=0; - virtual int unlog_xa_prepare(THD *thd, bool all)= 0; virtual void commit_checkpoint_notify(void *cookie)= 0; protected: @@ -117,10 +116,6 @@ class TC_LOG_DUMMY: public TC_LOG // use it to disable the logging return 1; } int unlog(ulong cookie, my_xid xid) override { return 0; } - int unlog_xa_prepare(THD *thd, bool all) override - { - return 0; - } void commit_checkpoint_notify(void *cookie) override { DBUG_ASSERT(0); }; }; @@ -204,10 +199,6 @@ class TC_LOG_MMAP: public TC_LOG int log_and_order(THD *thd, my_xid xid, bool all, bool need_prepare_ordered, bool need_commit_ordered) override; int unlog(ulong cookie, my_xid xid) override; - int unlog_xa_prepare(THD *thd, bool all) override - { - return 0; - } void commit_checkpoint_notify(void *cookie) override; int recover(); @@ -921,7 +912,6 @@ class MYSQL_BIN_LOG: public TC_LOG, private Event_log int log_and_order(THD *thd, my_xid xid, bool all, bool need_prepare_ordered, bool need_commit_ordered) override; int unlog(ulong cookie, my_xid xid) override; - int unlog_xa_prepare(THD *thd, bool all) override; void commit_checkpoint_notify(void *cookie) override; int recover(LOG_INFO *linfo, const char *last_log_name, IO_CACHE *first_log, Format_description_log_event *fdle, bool do_xa); diff --git a/sql/log_event_server.cc b/sql/log_event_server.cc index e7e3ece27170b..eccc84c3b44f4 100644 --- a/sql/log_event_server.cc +++ b/sql/log_event_server.cc @@ -1467,7 +1467,7 @@ Query_log_event::Query_log_event(THD* thd_arg, const char* query_arg, case SQLCOM_RELEASE_SAVEPOINT: case SQLCOM_ROLLBACK_TO_SAVEPOINT: case SQLCOM_SAVEPOINT: - case SQLCOM_XA_END: + case SQLCOM_XA_PREPARE: use_cache= trx_cache= TRUE; break; default: diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index ee13a1c501ae3..82f8179d4d534 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -12328,3 +12328,6 @@ ER_WARN_MALFORMED_HINT eng "Hint %s is ignored as malformed" ER_WARN_HINTS_ON_INSERT_PART_OF_INSERT_SELECT eng "Optimizer hints at the INSERT part of a INSERT..SELECT statement are not supported" +ER_XA_RDONLY + eng "Found to be read-only XA transaction is rolled back" + diff --git a/sql/xa.cc b/sql/xa.cc index 951232c646c1d..7b7d3e1d04b49 100644 --- a/sql/xa.cc +++ b/sql/xa.cc @@ -22,6 +22,10 @@ #include "my_cpu.h" #include #include +#ifndef DBUG_OFF +#include "rpl_rli.h" // rpl_group_info +#endif +#include "debug_sync.h" // DEBUG_SYNC static bool slave_applier_reset_xa_trans(THD *thd); @@ -79,6 +83,13 @@ class XID_cache_element uint rm_error; enum xa_states xa_state; XA_data xid; + /* + The flag memorizes the fact of the prepared XA has been binlogged. + It gets dropped with binlogging of the transaction's XA-COMPLETE. + FIXME: alternatively, could we use bit 32 of m_state or, + TODO: better, XID::formatID highest bit (also required by MDEV-21777). + */ + bool xap_binlogged_awaiting_xac; bool is_set(int32_t flag) { return m_state.load(std::memory_order_relaxed) & flag; } void set(int32_t flag) @@ -106,6 +117,7 @@ class XID_cache_element old&= ACQUIRED | RECOVERED; (void) LF_BACKOFF(); } + xap_binlogged_awaiting_xac= FALSE; } void acquired_to_recovered() { @@ -126,6 +138,16 @@ class XID_cache_element } return true; } + void notify_xap_binlogged() + { + xap_binlogged_awaiting_xac= TRUE; + } + void notify_xac_binlogged() + { + DBUG_ASSERT(opt_bin_log); + + xap_binlogged_awaiting_xac= FALSE; + } static void lf_hash_initializer(LF_HASH *, void *el, const void *ie) { XID_cache_element *element= static_cast(el); @@ -135,6 +157,7 @@ class XID_cache_element element->rm_error= 0; element->xa_state= new_element->xa_state; element->xid.set(new_element->xid); + element->xap_binlogged_awaiting_xac= FALSE; new_element->xid_cache_element= element; } static void lf_alloc_constructor(uchar *ptr) @@ -194,6 +217,12 @@ void XID_STATE::set_rollback_only() MYSQL_SET_TRANSACTION_XA_STATE(current_thd->m_transaction_psi, XA_ROLLBACK_ONLY); } +#ifndef DBUG_OFF +uint XID_STATE::get_error() +{ + return is_explicit_XA() ? xid_cache_element->rm_error : 0; +} +#endif void XID_STATE::er_xaer_rmfail() const { @@ -278,8 +307,7 @@ static XID_cache_element *xid_cache_search(THD *thd, XID *xid) return element; } - -bool xid_cache_insert(XID *xid) +bool xid_cache_insert(XID *xid, bool is_binlogged) { XID_cache_insert_element new_element(XA_PREPARED, xid); LF_PINS *pins; @@ -292,6 +320,8 @@ bool xid_cache_insert(XID *xid) { case 0: new_element.xid_cache_element->set(XID_cache_element::RECOVERED); + if (is_binlogged) + new_element.xid_cache_element->notify_xap_binlogged(); break; case 1: res= 0; @@ -334,10 +364,45 @@ static void xid_cache_delete(THD *thd, XID_cache_element *&element) void xid_cache_delete(THD *thd, XID_STATE *xid_state) { DBUG_ASSERT(xid_state->is_explicit_XA()); + xid_cache_delete(thd, xid_state->xid_cache_element); xid_state->xid_cache_element= 0; } +void xid_cache_delete(THD *thd) +{ + xid_cache_delete(thd, &thd->transaction->xid_state); +} + + +void xid_cache_update_xa_binlog_state(THD *thd, XID_STATE *xid_state, + bool is_xap) +{ + DBUG_ASSERT(xid_state->is_explicit_XA()); + if (is_xap) + { + DBUG_ASSERT(!xid_state->xid_cache_element->xap_binlogged_awaiting_xac); + + xid_state->xid_cache_element->notify_xap_binlogged(); + } + else + { + /* + The flag may not be set by ineffective XA-PREPARE groups + that logged on master in few exceptional cases. + */ + DBUG_ASSERT(xid_state->xid_cache_element->xap_binlogged_awaiting_xac || + thd->transaction->all.ha_list == 0); + + xid_state->xid_cache_element->notify_xac_binlogged(); + } +} + +bool is_xap_binlogged(THD *thd) +{ + return + thd->transaction->xid_state.xid_cache_element->xap_binlogged_awaiting_xac; +} struct xid_cache_iterate_arg { @@ -570,6 +635,15 @@ bool trans_xa_prepare(THD *thd) my_error(ER_XAER_NOTA, MYF(0)); else { +#ifdef ENABLED_DEBUG_SYNC + DBUG_EXECUTE_IF( + "stop_before_binlog_prepare", + if (thd->rgi_slave->current_gtid.seq_no % 100 == 0) + { + DBUG_ASSERT(!debug_sync_set_action( + thd, STRING_WITH_LEN("now WAIT_FOR binlog_xap"))); + };); +#endif MDL_request mdl_request; if (trans_xa_get_backup_lock(thd, &mdl_request) || ha_prepare(thd)) @@ -586,6 +660,8 @@ bool trans_xa_prepare(THD *thd) } else { + DBUG_ASSERT(thd->transaction->xid_state.xid_cache_element); + if (thd->transaction->xid_state.xid_cache_element->xa_state != XA_ROLLBACK_ONLY) { @@ -595,14 +671,37 @@ bool trans_xa_prepare(THD *thd) else { /* - In the non-err case, XA_ROLLBACK_ONLY should only be set by a slave - thread which prepared an empty transaction, to prevent binlogging a - standalone XA COMMIT. + In the non-err case, XA_ROLLBACK_ONLY should be set for a + read-only transaction, has nothing to binlog. */ - DBUG_ASSERT(thd->rgi_slave && !(thd->transaction->all.ha_list)); +#ifndef DBUG_OFF + bool is_rw; + Ha_trx_info *ha_info= thd->transaction->all.ha_list; + for (is_rw= false; ha_info; ha_info= ha_info->next()) + { + transaction_participant *ht= ha_info->ht(); + if (ht == &binlog_tp || !ht->prepare) + continue; + is_rw= is_rw || ha_info->is_trx_read_write(); + } + DBUG_ASSERT((thd->rgi_slave && !(thd->transaction->all.ha_list)) || + !is_rw); +#endif } res= thd->variables.pseudo_slave_mode || thd->slave_thread ? slave_applier_reset_xa_trans(thd) : 0; +#ifdef ENABLED_DEBUG_SYNC + DBUG_EXECUTE_IF( + "stop_after_binlog_prepare", + if (thd->rgi_slave->current_gtid.seq_no % 100 == 0) + { + DBUG_ASSERT(!debug_sync_set_action( + thd, + STRING_WITH_LEN( + "now SIGNAL xa_prepare_binlogged WAIT_FOR continue_xap"))); + };); +#endif + } trans_xa_release_backup_lock(thd); } @@ -612,99 +711,118 @@ bool trans_xa_prepare(THD *thd) /** - Commit and terminate the a XA transaction. - Transactional locks are released if transaction ended + Commit/Rollback a prepared XA transaction through "external" connection. - @param thd Current thread + @param thd Current "external" connection thread + @bool do_commit true for Commit, false for Rollback @retval FALSE Success @retval TRUE Failure - */ -bool trans_xa_commit(THD *thd) +static bool xa_complete(THD *thd, bool do_commit) { - bool res= true; XID_STATE &xid_state= thd->transaction->xid_state; - DBUG_ENTER("trans_xa_commit"); + DBUG_ENTER("xa_complete"); - if (!xid_state.is_explicit_XA() || - !xid_state.xid_cache_element->xid.eq(thd->lex->xid)) + if (thd->in_multi_stmt_transaction_mode()) + { + /* + Not allow to commit from inside an not-"native" to xid + ongoing transaction: the commit effect can't be reversed. + */ + my_error(ER_XAER_OUTSIDE, MYF(0)); + DBUG_RETURN(TRUE); + } + if (do_commit && thd->lex->xa_opt != XA_NONE) + { + /* + Not allow to commit with one phase a prepared xa out of compatibility + with the native commit branch's error out. + */ + my_error(ER_XAER_INVAL, MYF(0)); + DBUG_RETURN(TRUE); + } + if (thd->fix_xid_hash_pins()) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + DBUG_RETURN(TRUE); + } + + if (auto xs= xid_cache_search(thd, thd->lex->xid)) { - if (thd->in_multi_stmt_transaction_mode()) + bool res; + MDL_request mdl_request; + bool rw_trans= (xs->rm_error != ER_XA_RBROLLBACK); + + if (rw_trans && thd->check_read_only_with_error()) { - /* - Not allow to commit from inside an not-"native" to xid - ongoing transaction: the commit effect can't be reversed. - */ - my_error(ER_XAER_OUTSIDE, MYF(0)); - DBUG_RETURN(TRUE); + DBUG_ASSERT(thd->is_error()); + + goto _end_external_xid; } - if (thd->lex->xa_opt != XA_NONE) + + res= xa_trans_rolled_back(xs); + if (trans_xa_get_backup_lock(thd, &mdl_request)) { /* - Not allow to commit with one phase a prepared xa out of compatibility - with the native commit branch's error out. + We can't rollback an XA transaction on lock failure due to + Innodb redo log and bin log update is involved in rollback. + Return error to user for a retry. */ - my_error(ER_XAER_INVAL, MYF(0)); - DBUG_RETURN(TRUE); + DBUG_ASSERT(thd->is_error()); + + goto _end_external_xid; } - if (thd->fix_xid_hash_pins()) + + DBUG_ASSERT(!xid_state.xid_cache_element); + + xid_state.xid_cache_element= xs; + ha_commit_or_rollback_by_xid(&xs->xid, do_commit ? !res : 0, thd); + + if (!res && thd->is_error()) { - my_error(ER_OUT_OF_RESOURCES, MYF(0)); - DBUG_RETURN(TRUE); + // hton completion error retains xs/xid in the cache, + // unless there had been already one as reflected by `res`. + goto _end_external_xid; } + xid_cache_delete(thd, &xid_state); - if (auto xs= xid_cache_search(thd, thd->lex->xid)) - { - bool xid_deleted= false; - MDL_request mdl_request; - bool rw_trans= (xs->rm_error != ER_XA_RBROLLBACK); + xs= NULL; - if (rw_trans && thd->check_read_only_with_error()) - { - res= 1; - goto _end_external_xid; - } - res= xa_trans_rolled_back(xs); - if (trans_xa_get_backup_lock(thd, &mdl_request)) - { - /* - We can't rollback an XA transaction on lock failure due to - Innodb redo log and bin log update is involved in rollback. - Return error to user for a retry. - */ - DBUG_ASSERT(thd->is_error()); +_end_external_xid: + if (xs) + xs->acquired_to_recovered(); + xid_state.xid_cache_element= 0; + trans_xa_release_backup_lock(thd); + } + else + my_error(ER_XAER_NOTA, MYF(0)); + DBUG_RETURN(thd->get_stmt_da()->is_error()); +} - res= true; - goto _end_external_xid; - } - DBUG_ASSERT(!xid_state.xid_cache_element); +/** + Commit and terminate the a XA transaction. + Transactional locks are released if transaction ended - xid_state.xid_cache_element= xs; - ha_commit_or_rollback_by_xid(&xs->xid, !res); - if (!res && thd->is_error()) - { - // hton completion error retains xs/xid in the cache, - // unless there had been already one as reflected by `res`. - res= true; - goto _end_external_xid; - } - xid_cache_delete(thd, xs); - xid_deleted= true; - - _end_external_xid: - xid_state.xid_cache_element= 0; - res= res || thd->is_error(); - if (!xid_deleted) - xs->acquired_to_recovered(); - trans_xa_release_backup_lock(thd); - } - else - my_error(ER_XAER_NOTA, MYF(0)); - DBUG_RETURN(res); - } + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure + +*/ + +bool trans_xa_commit(THD *thd) +{ + XID_STATE &xid_state= thd->transaction->xid_state; + bool res; + + DBUG_ENTER("trans_xa_commit"); + + if (!xid_state.is_explicit_XA() || + !xid_state.xid_cache_element->xid.eq(thd->lex->xid)) + DBUG_RETURN(xa_complete(thd, true)); if (thd->transaction->all.is_trx_read_write() && thd->check_read_only_with_error()) DBUG_RETURN(TRUE); @@ -802,63 +920,7 @@ bool trans_xa_rollback(THD *thd) if (!xid_state.is_explicit_XA() || !xid_state.xid_cache_element->xid.eq(thd->lex->xid)) - { - if (thd->in_multi_stmt_transaction_mode()) - { - my_error(ER_XAER_OUTSIDE, MYF(0)); - DBUG_RETURN(TRUE); - } - if (thd->fix_xid_hash_pins()) - { - my_error(ER_OUT_OF_RESOURCES, MYF(0)); - DBUG_RETURN(TRUE); - } - - if (auto xs= xid_cache_search(thd, thd->lex->xid)) - { - bool res; - bool xid_deleted= false; - bool rw_trans= (xs->rm_error != ER_XA_RBROLLBACK); - - if (rw_trans && thd->check_read_only_with_error()) - { - res= 1; - goto _end_external_xid; - } - - if (trans_xa_get_backup_lock(thd, &mdl_request)) - { - /* - We can't rollback an XA transaction on lock failure due to - Innodb redo log and bin log update is involved in rollback. - Return error to user for a retry. - */ - DBUG_ASSERT(thd->is_error()); - - goto _end_external_xid; - } - res= xa_trans_rolled_back(xs); - DBUG_ASSERT(!xid_state.xid_cache_element); - - xid_state.xid_cache_element= xs; - ha_commit_or_rollback_by_xid(&xs->xid, 0); - if (!res && thd->is_error()) - { - goto _end_external_xid; - } - xid_cache_delete(thd, xs); - xid_deleted= true; - - _end_external_xid: - xid_state.xid_cache_element= 0; - if (!xid_deleted) - xs->acquired_to_recovered(); - trans_xa_release_backup_lock(thd); - } - else - my_error(ER_XAER_NOTA, MYF(0)); - DBUG_RETURN(thd->get_stmt_da()->is_error()); - } + DBUG_RETURN(xa_complete(thd, false)); if (thd->transaction->all.is_trx_read_write() && thd->check_read_only_with_error()) DBUG_RETURN(TRUE); @@ -891,7 +953,12 @@ bool trans_xa_detach(THD *thd) DBUG_ASSERT(thd->transaction->xid_state.is_explicit_XA()); if (thd->transaction->xid_state.xid_cache_element->xa_state != XA_PREPARED) + { +#ifndef DBUG_OFF + thd->transaction->xid_state.set_error(ER_XA_RBROLLBACK); +#endif return xa_trans_force_rollback(thd); + } else if (!thd->transaction->all.is_trx_read_write()) { thd->transaction->xid_state.set_error(ER_XA_RBROLLBACK); @@ -1151,13 +1218,17 @@ static bool slave_applier_reset_xa_trans(THD *thd) ~(SERVER_STATUS_IN_TRANS | SERVER_STATUS_IN_TRANS_READONLY); DBUG_PRINT("info", ("clearing SERVER_STATUS_IN_TRANS")); - if (thd->variables.pseudo_slave_mode && - !thd->transaction->all.is_trx_read_write()) + if (thd->transaction->xid_state.xid_cache_element->xa_state != XA_PREPARED) { - thd->transaction->xid_state.set_error(ER_XA_RBROLLBACK); + DBUG_ASSERT(thd->transaction->xid_state.xid_cache_element->xa_state == + XA_ROLLBACK_ONLY); + xa_trans_force_rollback(thd); + } + else + { + thd->transaction->xid_state.xid_cache_element->acquired_to_recovered(); + thd->transaction->xid_state.xid_cache_element= 0; } - thd->transaction->xid_state.xid_cache_element->acquired_to_recovered(); - thd->transaction->xid_state.xid_cache_element= 0; for (Ha_trx_info *ha_info= thd->transaction->all.ha_list, *ha_info_next; ha_info; ha_info= ha_info_next) diff --git a/sql/xa.h b/sql/xa.h index a6732039140d7..87d302eedcae6 100644 --- a/sql/xa.h +++ b/sql/xa.h @@ -39,13 +39,18 @@ struct XID_STATE { void er_xaer_rmfail() const; XID *get_xid() const; enum xa_states get_state_code() const; +#ifndef DBUG_OFF + uint get_error(); +#endif }; void xid_cache_init(void); void xid_cache_free(void); -bool xid_cache_insert(XID *xid); +bool xid_cache_insert(XID *xid, bool is_binlogged= false); bool xid_cache_insert(THD *thd, XID_STATE *xid_state, XID *xid); void xid_cache_delete(THD *thd, XID_STATE *xid_state); +void xid_cache_update_xa_binlog_state(THD *thd, XID_STATE *xid_state, bool is_xap); +bool is_xap_binlogged(THD *thd); bool trans_xa_start(THD *thd); bool trans_xa_end(THD *thd); From dd6f8d29c276ec86b32a1833e9fd1f20751d16f8 Mon Sep 17 00:00:00 2001 From: Andrei Elkin Date: Mon, 5 May 2025 16:43:10 +0300 Subject: [PATCH 2/8] Rebase of Part I affected test/resut:s. Affected binlog,main,rpl suites tests/results due to the empty XA's rollback optimization. --- .../main/alter_table_online_debug.result | 4 + mysql-test/main/binary_to_hex.result | 2 +- mysql-test/main/binary_to_hex.test | 3 +- mysql-test/main/commit.result | 2 + mysql-test/main/flush_read_lock.result | 4 + mysql-test/main/xa.result | 34 +++- mysql-test/main/xa.test | 8 +- mysql-test/main/xa_binlog.result | 14 +- mysql-test/main/xa_prepared_binlog_off.result | 154 ++++++++++++++---- mysql-test/main/xa_sync.result | 12 +- mysql-test/main/xa_sync.test | 6 +- .../include/binlog_xa_prepare_disconnect.inc | 7 +- .../binlog_xa_prepared_do_and_restart.inc | 84 +++++----- .../binlog/r/binlog_empty_xa_prepared.result | 32 ++-- .../r/binlog_truncate_active_log.result | 115 +++---------- .../binlog/r/binlog_xa_prepared_bugs.result | 2 +- .../r/binlog_xa_prepared_disconnect.result | 130 ++++++++++++--- .../binlog/t/binlog_truncate_active_log.inc | 4 + .../binlog/t/binlog_truncate_active_log.test | 25 +-- .../rpl/include/rpl_xa_concurrent_2pc.inc | 23 ++- mysql-test/suite/rpl/r/rpl_xa.result | 12 +- .../suite/rpl/r/rpl_xa_concurrent_2pc.result | 59 +++++-- .../r/rpl_xa_concurrent_2pc_lsu_off.result | 59 +++++-- .../rpl/r/rpl_xa_empty_transaction.result | 56 +++---- .../rpl/r/rpl_xa_gtid_pos_auto_engine.result | 12 +- .../rpl/r/rpl_xa_survive_disconnect.result | 2 +- .../rpl_xa_survive_disconnect_lsu_off.result | 2 +- ...xa_survive_disconnect_mixed_engines.result | 8 +- 28 files changed, 529 insertions(+), 346 deletions(-) diff --git a/mysql-test/main/alter_table_online_debug.result b/mysql-test/main/alter_table_online_debug.result index 1148e178428df..c64ba93c7ff4a 100644 --- a/mysql-test/main/alter_table_online_debug.result +++ b/mysql-test/main/alter_table_online_debug.result @@ -1690,6 +1690,8 @@ update t set a = 0; ERROR 23000: Duplicate entry '0' for key 'PRIMARY' xa end 'xid'; xa prepare 'xid'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back xa commit 'xid'; set debug_sync= 'now signal go'; connection default; @@ -1707,6 +1709,8 @@ update t set a = 0; ERROR 23000: Duplicate entry '0' for key 'PRIMARY' xa end 'xid'; xa prepare 'xid'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back xa rollback 'xid'; set debug_sync= 'now signal go'; connection default; diff --git a/mysql-test/main/binary_to_hex.result b/mysql-test/main/binary_to_hex.result index 3918021f3e65a..697fb4c0b11d1 100644 --- a/mysql-test/main/binary_to_hex.result +++ b/mysql-test/main/binary_to_hex.result @@ -115,7 +115,7 @@ c9: 0x000000000101000000000000000000F03F000000000000F03F #Print the table contents in html format
c1c2c3c4c5c6c7c8c9
0x74696E79626C6F622D74657874207265616461626C650x626C6F622D74657874207265616461626C650x6D656469756D626C6F622D74657874207265616461626C650x6C6F6E67626C6F622D74657874207265616461626C650x74657874207265616461626C650x010x630x7661726961626C650x000000000101000000000000000000F03F000000000000F03F
idcol1col2
10xAB1234000000000000000x123ABC
20xDE1234000000000000000x123DEF
DROP TABLE t1, t2; -create table t1 (a int); +create table t1 (a int) engine=innodb; formatID gtrid_length bqual_length data 1 3 2 0x7472316271 DROP TABLE t1; diff --git a/mysql-test/main/binary_to_hex.test b/mysql-test/main/binary_to_hex.test index be4fb301e4033..98ec88cea3502 100644 --- a/mysql-test/main/binary_to_hex.test +++ b/mysql-test/main/binary_to_hex.test @@ -9,6 +9,7 @@ # # Save the initial number of concurrent sessions +--source include/have_innodb.inc --source include/count_sessions.inc --source include/not_embedded.inc @@ -74,7 +75,7 @@ DROP TABLE t1, t2; # MDEV-14593 human-readable XA RECOVER -create table t1 (a int); +create table t1 (a int) engine=innodb; --write_file $MYSQLTEST_VARDIR/tmp/mdev-14593.sql DELIMITER / diff --git a/mysql-test/main/commit.result b/mysql-test/main/commit.result index 40524829667fa..8da40e259481d 100644 --- a/mysql-test/main/commit.result +++ b/mysql-test/main/commit.result @@ -524,6 +524,8 @@ DELETE FROM t1; ERROR 25006: Cannot execute statement in a READ ONLY transaction XA END 'test1'; XA PREPARE 'test1'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back XA COMMIT 'test1'; DROP TABLE t1; # diff --git a/mysql-test/main/flush_read_lock.result b/mysql-test/main/flush_read_lock.result index a8f8431e1253b..9b506754a0c34 100644 --- a/mysql-test/main/flush_read_lock.result +++ b/mysql-test/main/flush_read_lock.result @@ -1308,10 +1308,14 @@ flush tables with read lock; xa start 'test1'; xa end 'test1'; xa prepare 'test1'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back xa rollback 'test1'; xa start 'test1'; xa end 'test1'; xa prepare 'test1'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back xa commit 'test1'; xa recover; unlock tables; diff --git a/mysql-test/main/xa.result b/mysql-test/main/xa.result index a692e5ec311b9..0c58b8e87f6bc 100644 --- a/mysql-test/main/xa.result +++ b/mysql-test/main/xa.result @@ -175,10 +175,14 @@ xa rollback 'a'; xa start 'a'; xa end 'a'; xa prepare 'a'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back xa commit 'a'; xa start 'a'; xa end 'a'; xa prepare 'a'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back xa commit 'a'; CREATE TABLE t1(a INT, KEY(a)) ENGINE=InnoDB; INSERT INTO t1 VALUES(1),(2); @@ -222,8 +226,10 @@ XA END 'x'; XA END 'x'; ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the IDLE state XA PREPARE 'x'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back XA PREPARE 'x'; -ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the PREPARED state +ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the ROLLBACK ONLY state XA ROLLBACK 'x'; # # Bug#59986 Assert in Diagnostics_area::set_ok_status() for XA COMMIT @@ -324,6 +330,8 @@ connection con2; XA START 'xid1'; XA END 'xid1'; XA PREPARE 'xid1'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back XA ROLLBACK 'xid1'; connection default; DROP TABLE t1, t2; @@ -396,6 +404,8 @@ ERROR 42000: You have an error in your SQL syntax; check the manual that corresp XA BEGIN 'Я_упaлa_c_сеновала_тормозила_головой'; XA END 'Я_упaлa_c_сеновала_тормозила_головой'; XA PREPARE 'Я_упaлa_c_сеновала_тормозила_головой'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back XA ROLLBACK 'Я_упaлa_c_сеновала_тормозила_головой'; SET NAMES default; DROP TABLE t1; @@ -422,9 +432,11 @@ XA ROLLBACK 'unknown'; ERROR XAE09: XAER_OUTSIDE: Some work is done outside global transaction XA END 'xid1'; XA PREPARE 'xid1'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back XA COMMIT 'xid1' ONE PHASE; -ERROR XAE05: XAER_INVAL: Invalid arguments (or unsupported command) XA ROLLBACK 'xid1'; +ERROR XAE04: XAER_NOTA: Unknown XID # # MDEV-21856 - xid_t::formatID has to be constrained to 4 byte size # @@ -463,8 +475,10 @@ SELECT * FROM t; a XA END '0'; XA PREPARE '0'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back HANDLER t READ NEXT; -ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the PREPARED state +ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the ROLLBACK ONLY state # Cleanup XA COMMIT '0'; DROP TABLE t; @@ -555,6 +569,8 @@ select 0; 0 xa end '1'; xa prepare '1'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back xa commit '1'; xa start '2'; select 0; @@ -562,6 +578,8 @@ select 0; 0 xa end '2'; xa prepare '2'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back xa rollback '2'; # Read-only disconnect case connect con1_ro,localhost,root,,; @@ -571,13 +589,14 @@ select 0; 0 xa end '3'; xa prepare '3'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back disconnect con1_ro; connection default; xa recover; formatID gtrid_length bqual_length data -1 1 0 3 xa commit '3'; -ERROR XA100: XA_RBROLLBACK: Transaction branch was rolled back +ERROR XAE04: XAER_NOTA: Unknown XID connect con2_ro,localhost,root,,; xa start '4'; select 0; @@ -585,13 +604,14 @@ select 0; 0 xa end '4'; xa prepare '4'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back disconnect con2_ro; connection default; xa recover; formatID gtrid_length bqual_length data -1 1 0 4 xa rollback '4'; -ERROR XA100: XA_RBROLLBACK: Transaction branch was rolled back +ERROR XAE04: XAER_NOTA: Unknown XID set @@global.read_only=@sav_read_only; # # End of 10.5 tests diff --git a/mysql-test/main/xa.test b/mysql-test/main/xa.test index a4c60ef316d2d..9f8c3bd7a231f 100644 --- a/mysql-test/main/xa.test +++ b/mysql-test/main/xa.test @@ -573,8 +573,10 @@ XA COMMIT 'unknown' ONE PHASE; XA ROLLBACK 'unknown'; XA END 'xid1'; XA PREPARE 'xid1'; ---error ER_XAER_INVAL +# marked to rollback at prepare does not warn (maybe it should) +--error 0 XA COMMIT 'xid1' ONE PHASE; +--error ER_XAER_NOTA XA ROLLBACK 'xid1'; @@ -731,7 +733,7 @@ disconnect con1_ro; connection default; --source include/wait_until_count_sessions.inc xa recover; ---error ER_XA_RBROLLBACK +--error ER_XAER_NOTA xa commit '3'; --source include/count_sessions.inc @@ -745,7 +747,7 @@ disconnect con2_ro; connection default; --source include/wait_until_count_sessions.inc xa recover; ---error ER_XA_RBROLLBACK +--error ER_XAER_NOTA xa rollback '4'; set @@global.read_only=@sav_read_only; diff --git a/mysql-test/main/xa_binlog.result b/mysql-test/main/xa_binlog.result index a272570aac16a..c2334cd8c4599 100644 --- a/mysql-test/main/xa_binlog.result +++ b/mysql-test/main/xa_binlog.result @@ -27,7 +27,10 @@ XA END 'x'; XA PREPARE 'x'; Warnings: Warning 1030 Got error 131 "Command not supported by the engine" from storage engine Aria +Note 4226 Found to be read-only XA transaction is rolled back XA COMMIT 'x'; +Warnings: +Warning 1196 Some non-transactional changed tables couldn't be rolled back SELECT * from t1; a XA BEGIN 'x'; @@ -36,6 +39,7 @@ XA END 'x'; XA PREPARE 'x'; Warnings: Warning 1030 Got error 131 "Command not supported by the engine" from storage engine Aria +Note 4226 Found to be read-only XA transaction is rolled back XA ROLLBACK 'x'; Warnings: Warning 1196 Some non-transactional changed tables couldn't be rolled back @@ -68,18 +72,8 @@ master-bin.000001 # Query 1 # COMMIT master-bin.000001 # Gtid 1 # BEGIN GTID #-#-# master-bin.000001 # Query 1 # use `test`; DELETE FROM t1 master-bin.000001 # Query 1 # COMMIT -master-bin.000001 # Gtid 1 # XA START X'78',X'',1 GTID #-#-# -master-bin.000001 # Query 1 # XA END X'78',X'',1 -master-bin.000001 # XA_prepare 1 # XA PREPARE X'78',X'',1 -master-bin.000001 # Gtid 1 # GTID #-#-# -master-bin.000001 # Query 1 # XA COMMIT X'78',X'',1 master-bin.000001 # Gtid 1 # BEGIN GTID #-#-# master-bin.000001 # Query 1 # use `test`; INSERT INTO t1 VALUES (3),(4) master-bin.000001 # Query 1 # COMMIT -master-bin.000001 # Gtid 1 # XA START X'78',X'',1 GTID #-#-# -master-bin.000001 # Query 1 # XA END X'78',X'',1 -master-bin.000001 # XA_prepare 1 # XA PREPARE X'78',X'',1 -master-bin.000001 # Gtid 1 # GTID #-#-# -master-bin.000001 # Query 1 # XA ROLLBACK X'78',X'',1 master-bin.000001 # Gtid 1 # GTID #-#-# master-bin.000001 # Query 1 # use `test`; DROP TABLE `t1` /* generated by server */ diff --git a/mysql-test/main/xa_prepared_binlog_off.result b/mysql-test/main/xa_prepared_binlog_off.result index ebcb24d3af302..59e033e9c4d9b 100644 --- a/mysql-test/main/xa_prepared_binlog_off.result +++ b/mysql-test/main/xa_prepared_binlog_off.result @@ -4,6 +4,9 @@ CREATE VIEW v_processlist as SELECT * FROM performance_schema.threads where typ call mtr.add_suppression("Found 10 prepared XA transactions"); CREATE TABLE t (a INT) ENGINE=innodb; INSERT INTO t VALUES(100); +# +# A. The temp table only prepared XA recovers only formally to +# let post recovery XA COMMIT or XA ROLLBACK with no effect. connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; SET @@sql_log_bin = OFF; CREATE TEMPORARY TABLE tmp1 (a int) ENGINE=innodb; @@ -11,6 +14,8 @@ XA START 'trx1tmp'; INSERT INTO tmp1 SET a=1; XA END 'trx1tmp'; XA PREPARE 'trx1tmp'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; SET @@sql_log_bin = OFF; CREATE TEMPORARY TABLE tmp1 (a int) ENGINE=innodb; @@ -18,6 +23,8 @@ XA START 'trx2tmp'; INSERT INTO tmp1 SET a=1; XA END 'trx2tmp'; XA PREPARE 'trx2tmp'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; SET @@sql_log_bin = OFF; CREATE TEMPORARY TABLE tmp1 (a int) ENGINE=innodb; @@ -25,6 +32,11 @@ XA START 'trx3tmp'; INSERT INTO tmp1 SET a=1; XA END 'trx3tmp'; XA PREPARE 'trx3tmp'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back +# +# Various prohibited XA state changes to test here: +# connection default; XA COMMIT 'trx1tmp'; ERROR XAE04: XAER_NOTA: Unknown XID @@ -33,18 +45,18 @@ ERROR XAE04: XAER_NOTA: Unknown XID XA START 'trx1tmp'; ERROR XAE08: XAER_DUPID: The XID already exists connection default; -*** 3 prepared transactions must be in the list *** +*** 0 transactions must be in the list *** XA RECOVER; formatID gtrid_length bqual_length data -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N connection conn1tmp; disconnect conn1tmp; connection default; XA COMMIT 'trx1tmp'; KILL connection CONN_ID; XA COMMIT 'trx3tmp'; +# +# B. "Read-only" (select) prepared XA recovers only formally to +# let post recovery XA COMMIT or XA ROLLBACK with no effect. connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx1ro'; SELECT * from t ORDER BY a; @@ -52,6 +64,8 @@ a 100 XA END 'trx1ro'; XA PREPARE 'trx1ro'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx2ro'; SELECT * from t ORDER BY a; @@ -59,6 +73,8 @@ a 100 XA END 'trx2ro'; XA PREPARE 'trx2ro'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx3ro'; SELECT * from t ORDER BY a; @@ -66,47 +82,55 @@ a 100 XA END 'trx3ro'; XA PREPARE 'trx3ro'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back connection default; -*** 4 prepared transactions must be in the list *** +*** 0 transactions must be in the list *** XA RECOVER; formatID gtrid_length bqual_length data -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N connection conn1ro; disconnect conn1ro; connection default; XA ROLLBACK 'trx1ro'; KILL connection CONN_ID; XA ROLLBACK 'trx3ro'; +# +# C. Empty prepared XA recovers only formally to +# let post recovery XA COMMIT or XA ROLLBACK with no effect. +# connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx1empty'; XA END 'trx1empty'; XA PREPARE 'trx1empty'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx2empty'; XA END 'trx2empty'; XA PREPARE 'trx2empty'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx3empty'; XA END 'trx3empty'; XA PREPARE 'trx3empty'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back connection default; -*** 5 prepared transactions must be in the list *** +*** 0 transactions must be in the list *** XA RECOVER; formatID gtrid_length bqual_length data -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N connection conn1empty; disconnect conn1empty; connection default; XA COMMIT 'trx1empty'; KILL connection CONN_ID; XA COMMIT 'trx3empty'; +# +# D. Not prepared XA disconnects to be cleared out, +# no effect on data left as well. +# Few more prohibited XA state transactions is checked out. +# connect conn1$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx1unprepared'; INSERT INTO t set a=0; @@ -121,6 +145,12 @@ disconnect conn1unprepared; connection default; XA COMMIT 'trx1unprepared'; ERROR XAE04: XAER_NOTA: Unknown XID +# +# II. Regular case. +# +# Prepared transactions get disconnected in three ways: +# actively, being killed and by the server shutdown. +# connect conn$i, 127.0.0.1,root,,test,$MASTER_MYPORT,; SET @@binlog_format = STATEMENT; XA START 'trx_0'; @@ -281,18 +311,27 @@ XA END 'trx_19'; XA PREPARE 'trx_19'; connection default; KILL CONNECTION CONN_ID; +# [0, 5 - 1] are rolled back now connection default; XA ROLLBACK 'trx_0'; XA ROLLBACK 'trx_1'; XA ROLLBACK 'trx_2'; XA ROLLBACK 'trx_3'; XA ROLLBACK 'trx_4'; +# [5, 5 + 5 - 1] get committed XA COMMIT 'trx_5'; XA COMMIT 'trx_6'; XA COMMIT 'trx_7'; XA COMMIT 'trx_8'; XA COMMIT 'trx_9'; # restart +# +# III. Post server-restart verification. +# It concludes survived XA:s with a number of commits and rollbacks +# as configured in the 1st part to check expected results in the end. +# Cleanup section consists of explicit disconnect (for killed, or +# not disconnected before shutdown). +# connect conn_restart_$k, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'new_trx_0'; INSERT INTO t SET a=0; @@ -374,6 +413,11 @@ XA COMMIT 'new_trx_6'; XA COMMIT 'new_trx_7'; XA COMMIT 'new_trx_8'; XA COMMIT 'new_trx_9'; +# +# Symmetrically to the pre-restart, the resurrected trx:s are committed +# [10, 10 + 5 - 1] +# and the rest is rolled back. +# XA START 'trx_10'; ERROR XAE08: XAER_DUPID: The XID already exists XA COMMIT 'trx_10'; @@ -404,6 +448,9 @@ XA ROLLBACK 'trx_18'; XA START 'trx_19'; ERROR XAE08: XAER_DUPID: The XID already exists XA ROLLBACK 'trx_19'; +# +# Verification of correct results of recovered XA transaction handling: +# SELECT * FROM t; a 100 @@ -473,6 +520,9 @@ connection default; XA ROLLBACK 'trx_11'; ERROR XAE04: XAER_NOTA: Unknown XID disconnect conn10; +# +# A. The temp table only prepared XA recovers only formally to +# let post recovery XA COMMIT or XA ROLLBACK with no effect. connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; SET @@sql_log_bin = OFF; CREATE TEMPORARY TABLE tmp1 (a int) ENGINE=innodb; @@ -480,6 +530,8 @@ XA START 'trx1tmp'; INSERT INTO tmp1 SET a=1; XA END 'trx1tmp'; XA PREPARE 'trx1tmp'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; SET @@sql_log_bin = OFF; CREATE TEMPORARY TABLE tmp1 (a int) ENGINE=innodb; @@ -487,6 +539,8 @@ XA START 'trx2tmp'; INSERT INTO tmp1 SET a=1; XA END 'trx2tmp'; XA PREPARE 'trx2tmp'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; SET @@sql_log_bin = OFF; CREATE TEMPORARY TABLE tmp1 (a int) ENGINE=innodb; @@ -494,6 +548,11 @@ XA START 'trx3tmp'; INSERT INTO tmp1 SET a=1; XA END 'trx3tmp'; XA PREPARE 'trx3tmp'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back +# +# Various prohibited XA state changes to test here: +# connection default; XA COMMIT 'trx1tmp'; ERROR XAE04: XAER_NOTA: Unknown XID @@ -502,18 +561,18 @@ ERROR XAE04: XAER_NOTA: Unknown XID XA START 'trx1tmp'; ERROR XAE08: XAER_DUPID: The XID already exists connection default; -*** 3 prepared transactions must be in the list *** +*** 0 transactions must be in the list *** XA RECOVER; formatID gtrid_length bqual_length data -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N connection conn1tmp; disconnect conn1tmp; connection default; XA COMMIT 'trx1tmp'; KILL connection CONN_ID; XA COMMIT 'trx3tmp'; +# +# B. "Read-only" (select) prepared XA recovers only formally to +# let post recovery XA COMMIT or XA ROLLBACK with no effect. connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx1ro'; SELECT * from t ORDER BY a; @@ -541,6 +600,8 @@ a 100 XA END 'trx1ro'; XA PREPARE 'trx1ro'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx2ro'; SELECT * from t ORDER BY a; @@ -568,6 +629,8 @@ a 100 XA END 'trx2ro'; XA PREPARE 'trx2ro'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx3ro'; SELECT * from t ORDER BY a; @@ -595,47 +658,55 @@ a 100 XA END 'trx3ro'; XA PREPARE 'trx3ro'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back connection default; -*** 4 prepared transactions must be in the list *** +*** 0 transactions must be in the list *** XA RECOVER; formatID gtrid_length bqual_length data -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N connection conn1ro; disconnect conn1ro; connection default; XA ROLLBACK 'trx1ro'; KILL connection CONN_ID; XA ROLLBACK 'trx3ro'; +# +# C. Empty prepared XA recovers only formally to +# let post recovery XA COMMIT or XA ROLLBACK with no effect. +# connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx1empty'; XA END 'trx1empty'; XA PREPARE 'trx1empty'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx2empty'; XA END 'trx2empty'; XA PREPARE 'trx2empty'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx3empty'; XA END 'trx3empty'; XA PREPARE 'trx3empty'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back connection default; -*** 5 prepared transactions must be in the list *** +*** 0 transactions must be in the list *** XA RECOVER; formatID gtrid_length bqual_length data -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N -1 LEN1 LEN2 TRX_N connection conn1empty; disconnect conn1empty; connection default; XA COMMIT 'trx1empty'; KILL connection CONN_ID; XA COMMIT 'trx3empty'; +# +# D. Not prepared XA disconnects to be cleared out, +# no effect on data left as well. +# Few more prohibited XA state transactions is checked out. +# connect conn1$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx1unprepared'; INSERT INTO t set a=0; @@ -650,6 +721,12 @@ disconnect conn1unprepared; connection default; XA COMMIT 'trx1unprepared'; ERROR XAE04: XAER_NOTA: Unknown XID +# +# II. Regular case. +# +# Prepared transactions get disconnected in three ways: +# actively, being killed and by the server shutdown. +# connect conn$i, 127.0.0.1,root,,test,$MASTER_MYPORT,; SET @@binlog_format = STATEMENT; XA START 'trx_0'; @@ -810,18 +887,27 @@ XA END 'trx_19'; XA PREPARE 'trx_19'; connection default; KILL CONNECTION CONN_ID; +# [0, 5 - 1] are rolled back now connection default; XA ROLLBACK 'trx_0'; XA ROLLBACK 'trx_1'; XA ROLLBACK 'trx_2'; XA ROLLBACK 'trx_3'; XA ROLLBACK 'trx_4'; +# [5, 5 + 5 - 1] get committed XA COMMIT 'trx_5'; XA COMMIT 'trx_6'; XA COMMIT 'trx_7'; XA COMMIT 'trx_8'; XA COMMIT 'trx_9'; # Kill and restart +# +# III. Post server-restart verification. +# It concludes survived XA:s with a number of commits and rollbacks +# as configured in the 1st part to check expected results in the end. +# Cleanup section consists of explicit disconnect (for killed, or +# not disconnected before shutdown). +# connect conn_restart_$k, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'new_trx_0'; INSERT INTO t SET a=0; @@ -903,6 +989,11 @@ XA COMMIT 'new_trx_6'; XA COMMIT 'new_trx_7'; XA COMMIT 'new_trx_8'; XA COMMIT 'new_trx_9'; +# +# Symmetrically to the pre-restart, the resurrected trx:s are committed +# [10, 10 + 5 - 1] +# and the rest is rolled back. +# XA START 'trx_10'; ERROR XAE08: XAER_DUPID: The XID already exists XA COMMIT 'trx_10'; @@ -933,6 +1024,9 @@ XA ROLLBACK 'trx_18'; XA START 'trx_19'; ERROR XAE08: XAER_DUPID: The XID already exists XA ROLLBACK 'trx_19'; +# +# Verification of correct results of recovered XA transaction handling: +# SELECT * FROM t; a 100 diff --git a/mysql-test/main/xa_sync.result b/mysql-test/main/xa_sync.result index e7dd9b028470c..4baced5108f20 100644 --- a/mysql-test/main/xa_sync.result +++ b/mysql-test/main/xa_sync.result @@ -8,6 +8,8 @@ connection con1; XA START 'xatest'; XA END 'xatest'; XA PREPARE 'xatest'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back connection con2; SET debug_sync='xa_after_search SIGNAL parked WAIT_FOR go'; XA COMMIT 'xatest'; @@ -18,11 +20,11 @@ disconnect con1; SET debug_sync='now SIGNAL go'; connection con2; ERROR XAE04: XAER_NOTA: Unknown XID -*** Must have 'xatest' in the list +*** Must not have 'xatest' in the list XA RECOVER; formatID gtrid_length bqual_length data -1 6 0 xatest XA COMMIT 'xatest'; +ERROR XAE04: XAER_NOTA: Unknown XID disconnect con2; connection default; SET debug_sync='RESET'; @@ -32,6 +34,8 @@ connection con1; XA START 'xatest'; XA END 'xatest'; XA PREPARE 'xatest'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back connection con2; SET debug_sync='xa_after_search SIGNAL parked WAIT_FOR go'; XA ROLLBACK 'xatest'; @@ -42,11 +46,11 @@ disconnect con1; SET debug_sync='now SIGNAL go'; connection con2; ERROR XAE04: XAER_NOTA: Unknown XID -*** Must have 'xatest' in the list +*** Must not have 'xatest' in the list XA RECOVER; formatID gtrid_length bqual_length data -1 6 0 xatest XA ROLLBACK 'xatest'; +ERROR XAE04: XAER_NOTA: Unknown XID disconnect con2; connection default; SET debug_sync='RESET'; diff --git a/mysql-test/main/xa_sync.test b/mysql-test/main/xa_sync.test index ad1243ce6f80e..d81163affe33d 100644 --- a/mysql-test/main/xa_sync.test +++ b/mysql-test/main/xa_sync.test @@ -35,10 +35,10 @@ while ($i) connection con2; --error ER_XAER_NOTA reap; - --echo *** Must have 'xatest' in the list + --echo *** Must not have 'xatest' in the list XA RECOVER; - # second time yields no error - --error 0,1402 + # attempt to commit non-existing transaction fails + --error ER_XAER_NOTA --eval $op disconnect con2; diff --git a/mysql-test/suite/binlog/include/binlog_xa_prepare_disconnect.inc b/mysql-test/suite/binlog/include/binlog_xa_prepare_disconnect.inc index 4f4252904a453..8a493eb3ac8c2 100644 --- a/mysql-test/suite/binlog/include/binlog_xa_prepare_disconnect.inc +++ b/mysql-test/suite/binlog/include/binlog_xa_prepare_disconnect.inc @@ -10,7 +10,12 @@ # --connection default ---echo *** $num_trx_prepared prepared transactions must be in the list *** +--let $num_of_recovered = 0 +if (`select '$type' <> 'ro' and '$type' <> 'empty' and '$type' <> 'tmp'`) +{ + --let $num_of_recovered = $num_trx_prepared +} +--echo *** $num_of_recovered transactions must be in the list *** --replace_column 2 LEN1 3 LEN2 4 TRX_N XA RECOVER; diff --git a/mysql-test/suite/binlog/include/binlog_xa_prepared_do_and_restart.inc b/mysql-test/suite/binlog/include/binlog_xa_prepared_do_and_restart.inc index cbd740fdae418..3952972afb3bf 100644 --- a/mysql-test/suite/binlog/include/binlog_xa_prepared_do_and_restart.inc +++ b/mysql-test/suite/binlog/include/binlog_xa_prepared_do_and_restart.inc @@ -45,10 +45,9 @@ # D. Prove that not prepared XA remains to be cleared out by disconnection. # -# -# A. The temp table only prepared XA recovers only formally to -# let post recovery XA COMMIT or XA ROLLBACK with no effect. - +--echo # +--echo # A. The temp table only prepared XA recovers only formally to +--echo # let post recovery XA COMMIT or XA ROLLBACK with no effect. --let $type = tmp --let $index = 1 --let $sql_init1 = SET @@sql_log_bin = OFF @@ -63,9 +62,9 @@ --source suite/binlog/include/binlog_xa_prepare_connection.inc --let $conn3_id=`SELECT connection_id()` -# -# Various prohibited XA state changes to test here: -# +--echo # +--echo # Various prohibited XA state changes to test here: +--echo # --connection default # Stealing is not allowed @@ -84,10 +83,9 @@ --let $num_trx_prepared = $index --source suite/binlog/include/binlog_xa_prepare_disconnect.inc -# -# B. "Read-only" (select) prepared XA recovers only formally to -# let post recovery XA COMMIT or XA ROLLBACK with no effect. -# +--echo # +--echo # B. "Read-only" (select) prepared XA recovers only formally to +--echo # let post recovery XA COMMIT or XA ROLLBACK with no effect. --let $type=ro --let $index = 1 --let $sql_init1 = @@ -107,10 +105,10 @@ --inc $num_trx_prepared --source suite/binlog/include/binlog_xa_prepare_disconnect.inc -# -# C. Empty prepared XA recovers only formally to -# let post recovery XA COMMIT or XA ROLLBACK with no effect. -# +--echo # +--echo # C. Empty prepared XA recovers only formally to +--echo # let post recovery XA COMMIT or XA ROLLBACK with no effect. +--echo # --let $type=empty --let $index = 1 --let $sql_init1 = @@ -129,11 +127,11 @@ --inc $num_trx_prepared --source suite/binlog/include/binlog_xa_prepare_disconnect.inc -# -# D. Not prepared XA disconnects to be cleared out, -# no effect on data left as well. -# Few more prohibited XA state transactions is checked out. -# +--echo # +--echo # D. Not prepared XA disconnects to be cleared out, +--echo # no effect on data left as well. +--echo # Few more prohibited XA state transactions is checked out. +--echo # --let $type=unprepared --let $prev_count=`SELECT count(*) from t` @@ -161,12 +159,12 @@ if (`SELECT count(*) > $prev_count from t`) --die } -# -# II. Regular case. -# -# Prepared transactions get disconnected in three ways: -# actively, being killed and by the server shutdown. -# +--echo # +--echo # II. Regular case. +--echo # +--echo # Prepared transactions get disconnected in three ways: +--echo # actively, being killed and by the server shutdown. +--echo # --let $i=0 while ($i < $conn_number) { @@ -208,7 +206,7 @@ while ($i < $conn_number) --inc $i } -# [0, $rollback_number - 1] are rolled back now +--echo # [0, $rollback_number - 1] are rolled back now --connection default --let $i=0 @@ -219,7 +217,7 @@ while ($i < $rollback_number) --inc $i } -# [$rollback_number, $rollback_number + $commit_number - 1] get committed +--echo # [$rollback_number, $rollback_number + $commit_number - 1] get committed while ($i < $term_number) { --eval XA COMMIT 'trx_$i' @@ -229,13 +227,13 @@ while ($i < $term_number) --source include/$how_to_restart -# -# III. Post server-restart verification. -# It concludes survived XA:s with a number of commits and rollbacks -# as configured in the 1st part to check expected results in the end. -# Cleanup section consists of explicit disconnect (for killed, or -# not disconnected before shutdown). -# +--echo # +--echo # III. Post server-restart verification. +--echo # It concludes survived XA:s with a number of commits and rollbacks +--echo # as configured in the 1st part to check expected results in the end. +--echo # Cleanup section consists of explicit disconnect (for killed, or +--echo # not disconnected before shutdown). +--echo # # New XA can be prepared and committed --let $k = 0 @@ -265,11 +263,11 @@ while ($k < $post_restart_conn_number) --inc $k } -# -# Symmetrically to the pre-restart, the resurrected trx:s are committed -# [$term_number, $term_number + $commit_number - 1] -# and the rest is rolled back. -# +--echo # +--echo # Symmetrically to the pre-restart, the resurrected trx:s are committed +--echo # [$term_number, $term_number + $commit_number - 1] +--echo # and the rest is rolled back. +--echo # --let $i = $term_number while ($i < `SELECT $term_number + $commit_number`) @@ -290,9 +288,9 @@ while ($i < $conn_number) --inc $i } -# -# Verification of correct results of recovered XA transaction handling: -# +--echo # +--echo # Verification of correct results of recovered XA transaction handling: +--echo # SELECT * FROM t; --let $type=tmp diff --git a/mysql-test/suite/binlog/r/binlog_empty_xa_prepared.result b/mysql-test/suite/binlog/r/binlog_empty_xa_prepared.result index a16bccdd1e613..1d5a4020ce043 100644 --- a/mysql-test/suite/binlog/r/binlog_empty_xa_prepared.result +++ b/mysql-test/suite/binlog/r/binlog_empty_xa_prepared.result @@ -5,7 +5,7 @@ CREATE TEMPORARY TABLE tmp_1(c INT); XA END '3'; XA PREPARE '3'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back DROP TEMPORARY TABLE tmp_1; ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the ROLLBACK ONLY state ALTER TABLE tmp_1 DROP COLUMN c; @@ -30,18 +30,20 @@ XA START '3'; CREATE TEMPORARY TABLE tmp_1(c INT); XA END '3'; XA PREPARE '3'; +Warnings: +Note 4226 Found to be read-only XA transaction is rolled back DROP TEMPORARY TABLE tmp_1; -ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the PREPARED state +ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the ROLLBACK ONLY state ALTER TABLE tmp_1 DROP COLUMN c; -ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the PREPARED state +ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the ROLLBACK ONLY state DROP TEMPORARY SEQUENCE seq_1; -ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the PREPARED state +ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the ROLLBACK ONLY state ALTER SEQUENCE seq_1 INCREMENT BY 1; -ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the PREPARED state +ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the ROLLBACK ONLY state CREATE TEMPORARY TABLE tmp_2(c INT); -ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the PREPARED state +ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the ROLLBACK ONLY state CREATE TEMPORARY SEQUENCE seq_2; -ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the PREPARED state +ERROR XAE07: XAER_RMFAIL: The command cannot be executed when global transaction is in the ROLLBACK ONLY state XA ROLLBACK '3'; SET @@binlog_format="mixed"; # Proof of correct logging incl empty XA-PREPARE @@ -130,7 +132,7 @@ a XA END '1'; XA PREPARE '1'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect con1; connection default; XA RECOVER; @@ -146,7 +148,7 @@ a XA END '1'; XA PREPARE '1'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back XA COMMIT '1'; disconnect con1_2; connection default; @@ -185,7 +187,7 @@ a XA END '1'; XA PREPARE '1'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect con2; connection default; XA RECOVER; @@ -201,7 +203,7 @@ a XA END '1'; XA PREPARE '1'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back XA ROLLBACK '1'; disconnect con2_2; connection default; @@ -243,7 +245,7 @@ XA START 'a'; XA END 'a'; XA PREPARE 'a'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back XA ROLLBACK 'a'; ERROR XAE04: XAER_NOTA: Unknown XID include/show_binlog_events.inc @@ -255,9 +257,3 @@ a disconnect con_32852; connection default; include/show_binlog_events.inc -Log_name Pos Event_type Server_id End_log_pos Info -master-bin.000001 # Gtid # # GTID #-#-# -master-bin.000001 # Query # # use `test`; DROP /*!40005 TEMPORARY */ TABLE IF EXISTS `t1` -master-bin.000001 # Gtid # # BEGIN GTID #-#-# -master-bin.000001 # Query # # use `test`; CREATE TEMPORARY TABLE t1 (a INT) ENGINE=InnoDB -master-bin.000001 # Query # # ROLLBACK diff --git a/mysql-test/suite/binlog/r/binlog_truncate_active_log.result b/mysql-test/suite/binlog/r/binlog_truncate_active_log.result index 53cfb9577d7d3..376186f936e20 100644 --- a/mysql-test/suite/binlog/r/binlog_truncate_active_log.result +++ b/mysql-test/suite/binlog/r/binlog_truncate_active_log.result @@ -1,5 +1,6 @@ call mtr.add_suppression("Can.t init tc log"); call mtr.add_suppression("Aborting"); +call mtr.add_suppression("Found 1 prepared XA transactions"); RESET MASTER; SET @@global.sync_binlog=1; CREATE TABLE t (f INT) ENGINE=INNODB; @@ -133,12 +134,12 @@ SELECT * FROM t4; f DELETE FROM t; # Case C. -CREATE PROCEDURE sp_blank_xa() +CREATE PROCEDURE sp_xa() BEGIN -XA START 'blank'; -DELETE FROM t2 WHERE f = 0 /* no such record */; -XA END 'blank'; -XA PREPARE 'blank'; +XA START 'xid'; +DELETE FROM t WHERE f = 10; +XA END 'xid'; +XA PREPARE 'xid'; END| connect master1,localhost,root,,; connect master2,localhost,root,,; @@ -149,11 +150,11 @@ INSERT INTO t VALUES (10); INSERT INTO tm VALUES (10); connection master1; SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL master1_ready WAIT_FOR signal_never_arrives"; -INSERT INTO t VALUES (20); +CALL sp_xa; connection master2; SET DEBUG_SYNC= "now WAIT_FOR master1_ready"; SET DEBUG_SYNC= "commit_before_get_LOCK_after_binlog_sync SIGNAL master2_ready"; -CALL sp_blank_xa; +INSERT INTO t2 VALUES (20); connection master3; SET DEBUG_SYNC= "now WAIT_FOR master2_ready"; SELECT @@global.gtid_binlog_pos as 'Before the crash'; @@ -174,7 +175,7 @@ disconnect master2; disconnect master3; disconnect master4; # restart: --init-rpl-role=SLAVE --sync-binlog=1 --log-warnings=3 -FOUND 1 /Successfully truncated.*to remove transactions starting from GTID 0-1-15/ in mysqld.1.err +FOUND 1 /Successfully truncated.*to remove transactions starting from GTID 0-1-16/ in mysqld.1.err Pre-crash binlog file content: include/show_binlog_events.inc Log_name Pos Event_type Server_id End_log_pos Info @@ -182,12 +183,12 @@ master-bin.000003 # Gtid # # BEGIN GTID #-#-# master-bin.000003 # Query # # use `test`; DELETE FROM t master-bin.000003 # Xid # # COMMIT /* XID */ master-bin.000003 # Gtid # # GTID #-#-# -master-bin.000003 # Query # # use `test`; CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_blank_xa`() +master-bin.000003 # Query # # use `test`; CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_xa`() BEGIN -XA START 'blank'; -DELETE FROM t2 WHERE f = 0 /* no such record */; -XA END 'blank'; -XA PREPARE 'blank'; +XA START 'xid'; +DELETE FROM t WHERE f = 10; +XA END 'xid'; +XA PREPARE 'xid'; END master-bin.000003 # Gtid # # BEGIN GTID #-#-# master-bin.000003 # Query # # use `test`; INSERT INTO t VALUES (10) @@ -195,90 +196,13 @@ master-bin.000003 # Xid # # COMMIT /* XID */ master-bin.000003 # Gtid # # BEGIN GTID #-#-# master-bin.000003 # Query # # use `test`; INSERT INTO tm VALUES (10) master-bin.000003 # Query # # COMMIT +master-bin.000003 # Gtid # # XA START X'786964',X'',1 GTID #-#-# +master-bin.000003 # Query # # use `test`; DELETE FROM t WHERE f = 10 +master-bin.000003 # Query # # XA END X'786964',X'',1 +master-bin.000003 # XA_prepare # # XA PREPARE X'786964',X'',1 SELECT @@global.gtid_binlog_pos as 'After the crash'; After the crash -0-1-14 -"One row should be present in table 't'" -SELECT * FROM t; -f -10 -"No row should be present in table 't4'" -SELECT * FROM t4; -f -DELETE FROM t; -DROP PROCEDURE sp_blank_xa; -# Case D. -CREATE PROCEDURE sp_xa() -BEGIN -XA START 'xid'; -DELETE FROM t WHERE f = 10; -XA END 'xid'; -XA PREPARE 'xid'; -END| -connect master1,localhost,root,,; -connect master2,localhost,root,,; -connect master3,localhost,root,,; -connect master4,localhost,root,,; -connection default; -INSERT INTO t VALUES (10); -INSERT INTO tm VALUES (10); -connection master1; -SET DEBUG_SYNC= "commit_before_get_LOCK_commit_ordered SIGNAL master1_ready WAIT_FOR signal_never_arrives"; -CALL sp_xa; -connection master2; -SET DEBUG_SYNC= "now WAIT_FOR master1_ready"; -SET DEBUG_SYNC= "commit_before_get_LOCK_after_binlog_sync SIGNAL master2_ready"; -INSERT INTO t2 VALUES (20); -connection master3; -SET DEBUG_SYNC= "now WAIT_FOR master2_ready"; -SELECT @@global.gtid_binlog_pos as 'Before the crash'; -Before the crash -0-1-21 -connection master4; -SET DEBUG_SYNC= "ha_commit_trans_before_log_and_order SIGNAL master4_ready WAIT_FOR signal_never_arrives"; -INSERT INTO t4 VALUES (13); -connection master3; -SET DEBUG_SYNC= "now WAIT_FOR master4_ready"; -SELECT @@global.gtid_binlog_pos as 'Before the crash and never logged trx'; -Before the crash and never logged trx -0-1-21 -connection default; -# Kill the server -disconnect master1; -disconnect master2; -disconnect master3; -disconnect master4; -# restart: --init-rpl-role=SLAVE --sync-binlog=1 --log-warnings=3 -FOUND 1 /Successfully truncated.*to remove transactions starting from GTID 0-1-21/ in mysqld.1.err -Pre-crash binlog file content: -include/show_binlog_events.inc -Log_name Pos Event_type Server_id End_log_pos Info -master-bin.000004 # Gtid # # BEGIN GTID #-#-# -master-bin.000004 # Query # # use `test`; DELETE FROM t -master-bin.000004 # Xid # # COMMIT /* XID */ -master-bin.000004 # Gtid # # GTID #-#-# -master-bin.000004 # Query # # use `test`; DROP PROCEDURE sp_blank_xa -master-bin.000004 # Gtid # # GTID #-#-# -master-bin.000004 # Query # # use `test`; CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_xa`() -BEGIN -XA START 'xid'; -DELETE FROM t WHERE f = 10; -XA END 'xid'; -XA PREPARE 'xid'; -END -master-bin.000004 # Gtid # # BEGIN GTID #-#-# -master-bin.000004 # Query # # use `test`; INSERT INTO t VALUES (10) -master-bin.000004 # Xid # # COMMIT /* XID */ -master-bin.000004 # Gtid # # BEGIN GTID #-#-# -master-bin.000004 # Query # # use `test`; INSERT INTO tm VALUES (10) -master-bin.000004 # Query # # COMMIT -master-bin.000004 # Gtid # # XA START X'786964',X'',1 GTID #-#-# -master-bin.000004 # Query # # use `test`; DELETE FROM t WHERE f = 10 -master-bin.000004 # Query # # XA END X'786964',X'',1 -master-bin.000004 # XA_prepare # # XA PREPARE X'786964',X'',1 -SELECT @@global.gtid_binlog_pos as 'After the crash'; -After the crash -0-1-20 +0-1-15 "One row should be present in table 't'" SELECT * FROM t; f @@ -286,6 +210,7 @@ f "No row should be present in table 't4'" SELECT * FROM t4; f +xa rollback 'xid'; DELETE FROM t; DROP PROCEDURE sp_xa; # Cleanup diff --git a/mysql-test/suite/binlog/r/binlog_xa_prepared_bugs.result b/mysql-test/suite/binlog/r/binlog_xa_prepared_bugs.result index 77c9bb20ba532..4a9d37fa3df39 100644 --- a/mysql-test/suite/binlog/r/binlog_xa_prepared_bugs.result +++ b/mysql-test/suite/binlog/r/binlog_xa_prepared_bugs.result @@ -6,7 +6,7 @@ XA END 'xid_a'; XA PREPARE 'xid_a'; Warnings: Warning 1030 Got error 131 "Command not supported by the engine" from storage engine Aria -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back LOAD INDEX INTO CACHE c KEY(PRIMARY); Table Op Msg_type Msg_text test.c preload_keys Error XAER_RMFAIL: The command cannot be executed when global transaction is in the ROLLBACK ONLY state diff --git a/mysql-test/suite/binlog/r/binlog_xa_prepared_disconnect.result b/mysql-test/suite/binlog/r/binlog_xa_prepared_disconnect.result index f1083450df604..78e179ff3bbf5 100644 --- a/mysql-test/suite/binlog/r/binlog_xa_prepared_disconnect.result +++ b/mysql-test/suite/binlog/r/binlog_xa_prepared_disconnect.result @@ -4,6 +4,9 @@ CREATE VIEW v_processlist as SELECT * FROM performance_schema.threads where typ call mtr.add_suppression("Found 10 prepared XA transactions"); CREATE TABLE t (a INT) ENGINE=innodb; INSERT INTO t VALUES(100); +# +# A. The temp table only prepared XA recovers only formally to +# let post recovery XA COMMIT or XA ROLLBACK with no effect. connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; SET @@sql_log_bin = OFF; CREATE TEMPORARY TABLE tmp1 (a int) ENGINE=innodb; @@ -12,7 +15,7 @@ INSERT INTO tmp1 SET a=1; XA END 'trx1tmp'; XA PREPARE 'trx1tmp'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; SET @@sql_log_bin = OFF; CREATE TEMPORARY TABLE tmp1 (a int) ENGINE=innodb; @@ -21,7 +24,7 @@ INSERT INTO tmp1 SET a=1; XA END 'trx2tmp'; XA PREPARE 'trx2tmp'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; SET @@sql_log_bin = OFF; CREATE TEMPORARY TABLE tmp1 (a int) ENGINE=innodb; @@ -30,7 +33,10 @@ INSERT INTO tmp1 SET a=1; XA END 'trx3tmp'; XA PREPARE 'trx3tmp'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back +# +# Various prohibited XA state changes to test here: +# connection default; XA COMMIT 'trx1tmp'; ERROR XAE04: XAER_NOTA: Unknown XID @@ -39,7 +45,7 @@ ERROR XAE04: XAER_NOTA: Unknown XID XA START 'trx1tmp'; ERROR XAE08: XAER_DUPID: The XID already exists connection default; -*** 3 prepared transactions must be in the list *** +*** 0 transactions must be in the list *** XA RECOVER; formatID gtrid_length bqual_length data connection conn1tmp; @@ -48,6 +54,9 @@ connection default; XA COMMIT 'trx1tmp'; KILL connection CONN_ID; XA COMMIT 'trx3tmp'; +# +# B. "Read-only" (select) prepared XA recovers only formally to +# let post recovery XA COMMIT or XA ROLLBACK with no effect. connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx1ro'; SELECT * from t ORDER BY a; @@ -56,7 +65,7 @@ a XA END 'trx1ro'; XA PREPARE 'trx1ro'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx2ro'; SELECT * from t ORDER BY a; @@ -65,7 +74,7 @@ a XA END 'trx2ro'; XA PREPARE 'trx2ro'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx3ro'; SELECT * from t ORDER BY a; @@ -74,9 +83,9 @@ a XA END 'trx3ro'; XA PREPARE 'trx3ro'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back connection default; -*** 4 prepared transactions must be in the list *** +*** 0 transactions must be in the list *** XA RECOVER; formatID gtrid_length bqual_length data connection conn1ro; @@ -85,26 +94,30 @@ connection default; XA ROLLBACK 'trx1ro'; KILL connection CONN_ID; XA ROLLBACK 'trx3ro'; +# +# C. Empty prepared XA recovers only formally to +# let post recovery XA COMMIT or XA ROLLBACK with no effect. +# connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx1empty'; XA END 'trx1empty'; XA PREPARE 'trx1empty'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx2empty'; XA END 'trx2empty'; XA PREPARE 'trx2empty'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx3empty'; XA END 'trx3empty'; XA PREPARE 'trx3empty'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back connection default; -*** 5 prepared transactions must be in the list *** +*** 0 transactions must be in the list *** XA RECOVER; formatID gtrid_length bqual_length data connection conn1empty; @@ -113,6 +126,11 @@ connection default; XA COMMIT 'trx1empty'; KILL connection CONN_ID; XA COMMIT 'trx3empty'; +# +# D. Not prepared XA disconnects to be cleared out, +# no effect on data left as well. +# Few more prohibited XA state transactions is checked out. +# connect conn1$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx1unprepared'; INSERT INTO t set a=0; @@ -127,6 +145,12 @@ disconnect conn1unprepared; connection default; XA COMMIT 'trx1unprepared'; ERROR XAE04: XAER_NOTA: Unknown XID +# +# II. Regular case. +# +# Prepared transactions get disconnected in three ways: +# actively, being killed and by the server shutdown. +# connect conn$i, 127.0.0.1,root,,test,$MASTER_MYPORT,; SET @@binlog_format = STATEMENT; XA START 'trx_0'; @@ -287,18 +311,27 @@ XA END 'trx_19'; XA PREPARE 'trx_19'; connection default; KILL CONNECTION CONN_ID; +# [0, 5 - 1] are rolled back now connection default; XA ROLLBACK 'trx_0'; XA ROLLBACK 'trx_1'; XA ROLLBACK 'trx_2'; XA ROLLBACK 'trx_3'; XA ROLLBACK 'trx_4'; +# [5, 5 + 5 - 1] get committed XA COMMIT 'trx_5'; XA COMMIT 'trx_6'; XA COMMIT 'trx_7'; XA COMMIT 'trx_8'; XA COMMIT 'trx_9'; # restart +# +# III. Post server-restart verification. +# It concludes survived XA:s with a number of commits and rollbacks +# as configured in the 1st part to check expected results in the end. +# Cleanup section consists of explicit disconnect (for killed, or +# not disconnected before shutdown). +# connect conn_restart_$k, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'new_trx_0'; INSERT INTO t SET a=0; @@ -380,6 +413,11 @@ XA COMMIT 'new_trx_6'; XA COMMIT 'new_trx_7'; XA COMMIT 'new_trx_8'; XA COMMIT 'new_trx_9'; +# +# Symmetrically to the pre-restart, the resurrected trx:s are committed +# [10, 10 + 5 - 1] +# and the rest is rolled back. +# XA START 'trx_10'; ERROR XAE08: XAER_DUPID: The XID already exists XA COMMIT 'trx_10'; @@ -410,6 +448,9 @@ XA ROLLBACK 'trx_18'; XA START 'trx_19'; ERROR XAE08: XAER_DUPID: The XID already exists XA ROLLBACK 'trx_19'; +# +# Verification of correct results of recovered XA transaction handling: +# SELECT * FROM t; a 100 @@ -479,6 +520,9 @@ connection default; XA ROLLBACK 'trx_11'; ERROR XAE04: XAER_NOTA: Unknown XID disconnect conn10; +# +# A. The temp table only prepared XA recovers only formally to +# let post recovery XA COMMIT or XA ROLLBACK with no effect. connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; SET @@sql_log_bin = OFF; CREATE TEMPORARY TABLE tmp1 (a int) ENGINE=innodb; @@ -487,7 +531,7 @@ INSERT INTO tmp1 SET a=1; XA END 'trx1tmp'; XA PREPARE 'trx1tmp'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; SET @@sql_log_bin = OFF; CREATE TEMPORARY TABLE tmp1 (a int) ENGINE=innodb; @@ -496,7 +540,7 @@ INSERT INTO tmp1 SET a=1; XA END 'trx2tmp'; XA PREPARE 'trx2tmp'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; SET @@sql_log_bin = OFF; CREATE TEMPORARY TABLE tmp1 (a int) ENGINE=innodb; @@ -505,7 +549,10 @@ INSERT INTO tmp1 SET a=1; XA END 'trx3tmp'; XA PREPARE 'trx3tmp'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back +# +# Various prohibited XA state changes to test here: +# connection default; XA COMMIT 'trx1tmp'; ERROR XAE04: XAER_NOTA: Unknown XID @@ -514,7 +561,7 @@ ERROR XAE04: XAER_NOTA: Unknown XID XA START 'trx1tmp'; ERROR XAE08: XAER_DUPID: The XID already exists connection default; -*** 3 prepared transactions must be in the list *** +*** 0 transactions must be in the list *** XA RECOVER; formatID gtrid_length bqual_length data connection conn1tmp; @@ -523,6 +570,9 @@ connection default; XA COMMIT 'trx1tmp'; KILL connection CONN_ID; XA COMMIT 'trx3tmp'; +# +# B. "Read-only" (select) prepared XA recovers only formally to +# let post recovery XA COMMIT or XA ROLLBACK with no effect. connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx1ro'; SELECT * from t ORDER BY a; @@ -551,7 +601,7 @@ a XA END 'trx1ro'; XA PREPARE 'trx1ro'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx2ro'; SELECT * from t ORDER BY a; @@ -580,7 +630,7 @@ a XA END 'trx2ro'; XA PREPARE 'trx2ro'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx3ro'; SELECT * from t ORDER BY a; @@ -609,9 +659,9 @@ a XA END 'trx3ro'; XA PREPARE 'trx3ro'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back connection default; -*** 4 prepared transactions must be in the list *** +*** 0 transactions must be in the list *** XA RECOVER; formatID gtrid_length bqual_length data connection conn1ro; @@ -620,26 +670,30 @@ connection default; XA ROLLBACK 'trx1ro'; KILL connection CONN_ID; XA ROLLBACK 'trx3ro'; +# +# C. Empty prepared XA recovers only formally to +# let post recovery XA COMMIT or XA ROLLBACK with no effect. +# connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx1empty'; XA END 'trx1empty'; XA PREPARE 'trx1empty'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx2empty'; XA END 'trx2empty'; XA PREPARE 'trx2empty'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back connect conn$index$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx3empty'; XA END 'trx3empty'; XA PREPARE 'trx3empty'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back connection default; -*** 5 prepared transactions must be in the list *** +*** 0 transactions must be in the list *** XA RECOVER; formatID gtrid_length bqual_length data connection conn1empty; @@ -648,6 +702,11 @@ connection default; XA COMMIT 'trx1empty'; KILL connection CONN_ID; XA COMMIT 'trx3empty'; +# +# D. Not prepared XA disconnects to be cleared out, +# no effect on data left as well. +# Few more prohibited XA state transactions is checked out. +# connect conn1$type, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'trx1unprepared'; INSERT INTO t set a=0; @@ -662,6 +721,12 @@ disconnect conn1unprepared; connection default; XA COMMIT 'trx1unprepared'; ERROR XAE04: XAER_NOTA: Unknown XID +# +# II. Regular case. +# +# Prepared transactions get disconnected in three ways: +# actively, being killed and by the server shutdown. +# connect conn$i, 127.0.0.1,root,,test,$MASTER_MYPORT,; SET @@binlog_format = STATEMENT; XA START 'trx_0'; @@ -822,18 +887,27 @@ XA END 'trx_19'; XA PREPARE 'trx_19'; connection default; KILL CONNECTION CONN_ID; +# [0, 5 - 1] are rolled back now connection default; XA ROLLBACK 'trx_0'; XA ROLLBACK 'trx_1'; XA ROLLBACK 'trx_2'; XA ROLLBACK 'trx_3'; XA ROLLBACK 'trx_4'; +# [5, 5 + 5 - 1] get committed XA COMMIT 'trx_5'; XA COMMIT 'trx_6'; XA COMMIT 'trx_7'; XA COMMIT 'trx_8'; XA COMMIT 'trx_9'; # Kill and restart +# +# III. Post server-restart verification. +# It concludes survived XA:s with a number of commits and rollbacks +# as configured in the 1st part to check expected results in the end. +# Cleanup section consists of explicit disconnect (for killed, or +# not disconnected before shutdown). +# connect conn_restart_$k, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'new_trx_0'; INSERT INTO t SET a=0; @@ -915,6 +989,11 @@ XA COMMIT 'new_trx_6'; XA COMMIT 'new_trx_7'; XA COMMIT 'new_trx_8'; XA COMMIT 'new_trx_9'; +# +# Symmetrically to the pre-restart, the resurrected trx:s are committed +# [10, 10 + 5 - 1] +# and the rest is rolled back. +# XA START 'trx_10'; ERROR XAE08: XAER_DUPID: The XID already exists XA COMMIT 'trx_10'; @@ -945,6 +1024,9 @@ XA ROLLBACK 'trx_18'; XA START 'trx_19'; ERROR XAE08: XAER_DUPID: The XID already exists XA ROLLBACK 'trx_19'; +# +# Verification of correct results of recovered XA transaction handling: +# SELECT * FROM t; a 100 diff --git a/mysql-test/suite/binlog/t/binlog_truncate_active_log.inc b/mysql-test/suite/binlog/t/binlog_truncate_active_log.inc index 050e4ad8665aa..890628a30d818 100644 --- a/mysql-test/suite/binlog/t/binlog_truncate_active_log.inc +++ b/mysql-test/suite/binlog/t/binlog_truncate_active_log.inc @@ -67,4 +67,8 @@ SELECT * FROM t4; --inc $binlog_file_index # Local cleanup +if (`select '$query1' = 'CALL sp_xa'`) +{ + xa rollback 'xid'; +} DELETE FROM t; diff --git a/mysql-test/suite/binlog/t/binlog_truncate_active_log.test b/mysql-test/suite/binlog/t/binlog_truncate_active_log.test index e288aa476600f..00c1e13a4978c 100644 --- a/mysql-test/suite/binlog/t/binlog_truncate_active_log.test +++ b/mysql-test/suite/binlog/t/binlog_truncate_active_log.test @@ -15,6 +15,7 @@ call mtr.add_suppression("Can.t init tc log"); call mtr.add_suppression("Aborting"); +call mtr.add_suppression("Found 1 prepared XA transactions"); # The following cases are tested: # A. 2pc transaction is followed by a blank "zero-engines" one @@ -55,30 +56,8 @@ CREATE TABLE tm (f INT) ENGINE=Aria; --let $query2 = INSERT INTO t VALUES (20) --source binlog_truncate_active_log.inc - --echo # Case C. delimiter |; -CREATE PROCEDURE sp_blank_xa() -BEGIN - XA START 'blank'; - DELETE FROM t2 WHERE f = 0 /* no such record */; - XA END 'blank'; - XA PREPARE 'blank'; -END| -delimiter ;| - -# The same as in A with $query2 being the zero-engine XA transaction. -# Both $query1 and $query2 are going to be truncated. ---let $truncate_gtid_pos = 0-1-15 ---let $query1 = INSERT INTO t VALUES (20) ---let $query2 = CALL sp_blank_xa ---source binlog_truncate_active_log.inc - -DROP PROCEDURE sp_blank_xa; - - ---echo # Case D. -delimiter |; CREATE PROCEDURE sp_xa() BEGIN XA START 'xid'; @@ -90,7 +69,7 @@ delimiter ;| # The same as in B with $query1 being the prepared XA transaction. # Truncation must occurs at $query2. ---let $truncate_gtid_pos = 0-1-21 +--let $truncate_gtid_pos = 0-1-16 --let $query1 = CALL sp_xa --let $query2 = INSERT INTO t2 VALUES (20) --source binlog_truncate_active_log.inc diff --git a/mysql-test/suite/rpl/include/rpl_xa_concurrent_2pc.inc b/mysql-test/suite/rpl/include/rpl_xa_concurrent_2pc.inc index 1e7e9c0c6dfec..5a4126a196dec 100644 --- a/mysql-test/suite/rpl/include/rpl_xa_concurrent_2pc.inc +++ b/mysql-test/suite/rpl/include/rpl_xa_concurrent_2pc.inc @@ -224,9 +224,15 @@ ROLLBACK; set @@global.innodb_lock_wait_timeout= @save_lock_wait_timeout; set @@global.slave_transaction_retries= @save_trans_retries; +select @@global.gtid_slave_pos; + --echo # Ensure on slave restart, we can re-execute the XA transaction --source include/start_slave.inc ---source include/save_master_gtid.inc +--source include/sync_with_master_gtid.inc +let $diff_tables= master:t1, slave:t1; +--source include/diff_tables.inc +select * from t1; + --source include/stop_slave.inc set @@global.debug_dbug= @save_debug; @@ -243,6 +249,7 @@ set @@global.slave_transaction_retries= 0; --connection master XA START 'x'; --eval update t1 set b=b+1 where a=$hold_row +--eval select * from t1 where a=$hold_row XA END 'x'; XA PREPARE 'x'; --eval XA $xa_complete_sym 'x' @@ -272,7 +279,7 @@ ROLLBACK; --echo # There should not be any prepared rows seen by XA RECOVER XA RECOVER; ---echo # Ensuring data from second XAP isn't visible.. +--echo # Ensuring data from second XAP *isn't* visible.. if (`select count(*) from t1 where a=$new_row_idx`) { --die Failed, row exists @@ -286,12 +293,12 @@ set @@global.slave_transaction_retries= @save_trans_retries; --echo # Ensure on slave restart, we can re-execute the XA transaction --source include/start_slave.inc ---source include/save_master_gtid.inc ---source include/stop_slave.inc -set @@global.debug_dbug= @save_debug; ---source include/start_slave.inc +--source include/sync_with_master_gtid.inc +let $diff_tables= master:t1, slave:t1; +--source include/diff_tables.inc +select * from t1; ---echo # Ensuring data from second XAP is visible.. +--echo # Ensuring data from second XAP *is* visible.. if ($is_xac) { --let $expected_row_count= 1 @@ -302,7 +309,7 @@ if ($is_xar) } if (`select count(*) != $expected_row_count from t1 where a=$new_row_idx`) { - --die Failed, XA $xa_complete_sym was not observed + --die Failed, XA $xa_complete_sym was not observed } --echo # ..done diff --git a/mysql-test/suite/rpl/r/rpl_xa.result b/mysql-test/suite/rpl/r/rpl_xa.result index 33bbaecbc693f..5b8355cd3b974 100644 --- a/mysql-test/suite/rpl/r/rpl_xa.result +++ b/mysql-test/suite/rpl/r/rpl_xa.result @@ -60,7 +60,7 @@ select count(*) into @s2 from t1; xa end 'ro_2'; xa prepare 'ro_2';; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect master_ro_2; connection master; connect master_$xid, 127.0.0.1,root,,$db,$MASTER_MYPORT,; @@ -71,7 +71,7 @@ select 1; xa end 'ro_1'; xa prepare 'ro_1';; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect master_ro_1; connection master; connect master_$xid, 127.0.0.1,root,,$db,$MASTER_MYPORT,; @@ -80,7 +80,7 @@ select count(*) into @s2 from t1; xa end 'ro_2'; xa prepare 'ro_2';; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect master_ro_2; connection master; connect master_$xid, 127.0.0.1,root,,$db,$MASTER_MYPORT,; @@ -91,7 +91,7 @@ select 1; xa end 'ro_1'; xa prepare 'ro_1';; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect master_ro_1; *** Zero prepared xa:s must be in the list: connection master; @@ -180,7 +180,7 @@ insert into tm set a=1; xa end 'rw_myisam'; xa prepare 'rw_myisam';; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect master_rw_myisam; connection master; connect master_$xid, 127.0.0.1,root,,$db,$MASTER_MYPORT,; @@ -189,7 +189,7 @@ insert into tm set a=1; xa end 'rw_myisam'; xa prepare 'rw_myisam';; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect master_rw_myisam; *** rw_myisam prepared must be in the list: connection master; diff --git a/mysql-test/suite/rpl/r/rpl_xa_concurrent_2pc.result b/mysql-test/suite/rpl/r/rpl_xa_concurrent_2pc.result index 6e9c6b0564da9..a9b017edf49d9 100644 --- a/mysql-test/suite/rpl/r/rpl_xa_concurrent_2pc.result +++ b/mysql-test/suite/rpl/r/rpl_xa_concurrent_2pc.result @@ -132,9 +132,20 @@ connection slave; include/stop_slave_io.inc set @@global.innodb_lock_wait_timeout= @save_lock_wait_timeout; set @@global.slave_transaction_retries= @save_trans_retries; +select @@global.gtid_slave_pos; +@@global.gtid_slave_pos +0-1-303 # Ensure on slave restart, we can re-execute the XA transaction include/start_slave.inc -include/save_master_gtid.inc +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1, slave:t1] +select * from t1; +a b +-1 1 +0 0 +1 0 +2 1 +3 2 include/stop_slave.inc set @@global.debug_dbug= @save_debug; # Case b) @@ -148,6 +159,9 @@ set @@global.slave_transaction_retries= 0; connection master; XA START 'x'; update t1 set b=b+1 where a=-1; +select * from t1 where a=-1; +a b +-1 2 XA END 'x'; XA PREPARE 'x'; XA COMMIT 'x'; @@ -170,7 +184,7 @@ ROLLBACK; # There should not be any prepared rows seen by XA RECOVER XA RECOVER; formatID gtrid_length bqual_length data -# Ensuring data from second XAP isn't visible.. +# Ensuring data from second XAP *isn't* visible.. # ..done connection slave; include/stop_slave_io.inc @@ -178,11 +192,17 @@ set @@global.innodb_lock_wait_timeout= @save_lock_wait_timeout; set @@global.slave_transaction_retries= @save_trans_retries; # Ensure on slave restart, we can re-execute the XA transaction include/start_slave.inc -include/save_master_gtid.inc -include/stop_slave.inc -set @@global.debug_dbug= @save_debug; -include/start_slave.inc -# Ensuring data from second XAP is visible.. +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1, slave:t1] +select * from t1; +a b +-1 2 +0 0 +1 0 +2 1 +3 2 +4 0 +# Ensuring data from second XAP *is* visible.. # ..done # Cleanup connection slave; @@ -436,9 +456,16 @@ connection slave; include/stop_slave_io.inc set @@global.innodb_lock_wait_timeout= @save_lock_wait_timeout; set @@global.slave_transaction_retries= @save_trans_retries; +select @@global.gtid_slave_pos; +@@global.gtid_slave_pos +0-1-1303 # Ensure on slave restart, we can re-execute the XA transaction include/start_slave.inc -include/save_master_gtid.inc +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1, slave:t1] +select * from t1; +a b +-1 0 include/stop_slave.inc set @@global.debug_dbug= @save_debug; # Case b) @@ -452,6 +479,9 @@ set @@global.slave_transaction_retries= 0; connection master; XA START 'x'; update t1 set b=b+1 where a=-1; +select * from t1 where a=-1; +a b +-1 1 XA END 'x'; XA PREPARE 'x'; XA ROLLBACK 'x'; @@ -474,7 +504,7 @@ ROLLBACK; # There should not be any prepared rows seen by XA RECOVER XA RECOVER; formatID gtrid_length bqual_length data -# Ensuring data from second XAP isn't visible.. +# Ensuring data from second XAP *isn't* visible.. # ..done connection slave; include/stop_slave_io.inc @@ -482,11 +512,12 @@ set @@global.innodb_lock_wait_timeout= @save_lock_wait_timeout; set @@global.slave_transaction_retries= @save_trans_retries; # Ensure on slave restart, we can re-execute the XA transaction include/start_slave.inc -include/save_master_gtid.inc -include/stop_slave.inc -set @@global.debug_dbug= @save_debug; -include/start_slave.inc -# Ensuring data from second XAP is visible.. +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1, slave:t1] +select * from t1; +a b +-1 0 +# Ensuring data from second XAP *is* visible.. # ..done # Cleanup connection slave; diff --git a/mysql-test/suite/rpl/r/rpl_xa_concurrent_2pc_lsu_off.result b/mysql-test/suite/rpl/r/rpl_xa_concurrent_2pc_lsu_off.result index 39b35136117a7..434985f581657 100644 --- a/mysql-test/suite/rpl/r/rpl_xa_concurrent_2pc_lsu_off.result +++ b/mysql-test/suite/rpl/r/rpl_xa_concurrent_2pc_lsu_off.result @@ -132,9 +132,20 @@ connection slave; include/stop_slave_io.inc set @@global.innodb_lock_wait_timeout= @save_lock_wait_timeout; set @@global.slave_transaction_retries= @save_trans_retries; +select @@global.gtid_slave_pos; +@@global.gtid_slave_pos +0-1-303 # Ensure on slave restart, we can re-execute the XA transaction include/start_slave.inc -include/save_master_gtid.inc +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1, slave:t1] +select * from t1; +a b +-1 1 +0 0 +1 0 +2 1 +3 2 include/stop_slave.inc set @@global.debug_dbug= @save_debug; # Case b) @@ -148,6 +159,9 @@ set @@global.slave_transaction_retries= 0; connection master; XA START 'x'; update t1 set b=b+1 where a=-1; +select * from t1 where a=-1; +a b +-1 2 XA END 'x'; XA PREPARE 'x'; XA COMMIT 'x'; @@ -170,7 +184,7 @@ ROLLBACK; # There should not be any prepared rows seen by XA RECOVER XA RECOVER; formatID gtrid_length bqual_length data -# Ensuring data from second XAP isn't visible.. +# Ensuring data from second XAP *isn't* visible.. # ..done connection slave; include/stop_slave_io.inc @@ -178,11 +192,17 @@ set @@global.innodb_lock_wait_timeout= @save_lock_wait_timeout; set @@global.slave_transaction_retries= @save_trans_retries; # Ensure on slave restart, we can re-execute the XA transaction include/start_slave.inc -include/save_master_gtid.inc -include/stop_slave.inc -set @@global.debug_dbug= @save_debug; -include/start_slave.inc -# Ensuring data from second XAP is visible.. +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1, slave:t1] +select * from t1; +a b +-1 2 +0 0 +1 0 +2 1 +3 2 +4 0 +# Ensuring data from second XAP *is* visible.. # ..done # Cleanup connection slave; @@ -387,9 +407,16 @@ connection slave; include/stop_slave_io.inc set @@global.innodb_lock_wait_timeout= @save_lock_wait_timeout; set @@global.slave_transaction_retries= @save_trans_retries; +select @@global.gtid_slave_pos; +@@global.gtid_slave_pos +0-1-1303 # Ensure on slave restart, we can re-execute the XA transaction include/start_slave.inc -include/save_master_gtid.inc +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1, slave:t1] +select * from t1; +a b +-1 0 include/stop_slave.inc set @@global.debug_dbug= @save_debug; # Case b) @@ -403,6 +430,9 @@ set @@global.slave_transaction_retries= 0; connection master; XA START 'x'; update t1 set b=b+1 where a=-1; +select * from t1 where a=-1; +a b +-1 1 XA END 'x'; XA PREPARE 'x'; XA ROLLBACK 'x'; @@ -425,7 +455,7 @@ ROLLBACK; # There should not be any prepared rows seen by XA RECOVER XA RECOVER; formatID gtrid_length bqual_length data -# Ensuring data from second XAP isn't visible.. +# Ensuring data from second XAP *isn't* visible.. # ..done connection slave; include/stop_slave_io.inc @@ -433,11 +463,12 @@ set @@global.innodb_lock_wait_timeout= @save_lock_wait_timeout; set @@global.slave_transaction_retries= @save_trans_retries; # Ensure on slave restart, we can re-execute the XA transaction include/start_slave.inc -include/save_master_gtid.inc -include/stop_slave.inc -set @@global.debug_dbug= @save_debug; -include/start_slave.inc -# Ensuring data from second XAP is visible.. +include/sync_with_master_gtid.inc +include/diff_tables.inc [master:t1, slave:t1] +select * from t1; +a b +-1 0 +# Ensuring data from second XAP *is* visible.. # ..done # Cleanup connection slave; diff --git a/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result b/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result index 862e2981bac70..33fc704818bc6 100644 --- a/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result +++ b/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result @@ -20,7 +20,7 @@ XA START 'x'; XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back XA COMMIT 'x';; include/save_master_gtid.inc FLUSH LOGS; @@ -56,7 +56,7 @@ XA START 'x'; XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; @@ -96,7 +96,7 @@ XA START 'x'; XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back XA ROLLBACK 'x';; include/save_master_gtid.inc FLUSH LOGS; @@ -132,7 +132,7 @@ XA START 'x'; XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; @@ -177,7 +177,7 @@ ERROR 23000: Duplicate entry '1' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back XA COMMIT 'x';; include/save_master_gtid.inc FLUSH LOGS; @@ -215,7 +215,7 @@ ERROR 23000: Duplicate entry '1' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; @@ -257,7 +257,7 @@ ERROR 23000: Duplicate entry '1' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back XA ROLLBACK 'x';; include/save_master_gtid.inc FLUSH LOGS; @@ -295,7 +295,7 @@ ERROR 23000: Duplicate entry '1' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; @@ -344,7 +344,7 @@ ERROR 23000: Duplicate entry '1' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back XA COMMIT 'x';; Warnings: Warning 1196 Some non-transactional changed tables couldn't be rolled back @@ -384,7 +384,7 @@ ERROR 23000: Duplicate entry '1' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; @@ -426,7 +426,7 @@ ERROR 23000: Duplicate entry '1' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back XA ROLLBACK 'x';; Warnings: Warning 1196 Some non-transactional changed tables couldn't be rolled back @@ -466,7 +466,7 @@ ERROR 23000: Duplicate entry '1' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; @@ -516,7 +516,7 @@ ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back XA COMMIT 'x';; include/save_master_gtid.inc FLUSH LOGS; @@ -556,7 +556,7 @@ ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; @@ -600,7 +600,7 @@ ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back XA ROLLBACK 'x';; include/save_master_gtid.inc FLUSH LOGS; @@ -640,7 +640,7 @@ ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; @@ -693,7 +693,7 @@ ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back XA COMMIT 'x';; Warnings: Warning 1196 Some non-transactional changed tables couldn't be rolled back @@ -735,7 +735,7 @@ ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; @@ -779,7 +779,7 @@ ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back XA ROLLBACK 'x';; Warnings: Warning 1196 Some non-transactional changed tables couldn't be rolled back @@ -821,7 +821,7 @@ ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; @@ -877,7 +877,7 @@ ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back XA COMMIT 'x';; Warnings: Warning 1196 Some non-transactional changed tables couldn't be rolled back @@ -919,7 +919,7 @@ ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; @@ -963,7 +963,7 @@ ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back XA ROLLBACK 'x';; Warnings: Warning 1196 Some non-transactional changed tables couldn't be rolled back @@ -1005,7 +1005,7 @@ ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; @@ -1059,7 +1059,7 @@ ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back XA COMMIT 'x';; Warnings: Warning 1196 Some non-transactional changed tables couldn't be rolled back @@ -1101,7 +1101,7 @@ ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; @@ -1145,7 +1145,7 @@ ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back XA ROLLBACK 'x';; Warnings: Warning 1196 Some non-transactional changed tables couldn't be rolled back @@ -1187,7 +1187,7 @@ ERROR 23000: Duplicate entry '2' for key 'PRIMARY' XA END 'x'; XA PREPARE 'x'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect con1; connection server_1; XA RECOVER; diff --git a/mysql-test/suite/rpl/r/rpl_xa_gtid_pos_auto_engine.result b/mysql-test/suite/rpl/r/rpl_xa_gtid_pos_auto_engine.result index 87a4d98712088..cb8edc38afcaf 100644 --- a/mysql-test/suite/rpl/r/rpl_xa_gtid_pos_auto_engine.result +++ b/mysql-test/suite/rpl/r/rpl_xa_gtid_pos_auto_engine.result @@ -69,7 +69,7 @@ select count(*) into @s2 from t1; xa end 'ro_2'; xa prepare 'ro_2';; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect master_ro_2; connection master; connect master_$xid, 127.0.0.1,root,,$db,$MASTER_MYPORT,; @@ -80,7 +80,7 @@ select 1; xa end 'ro_1'; xa prepare 'ro_1';; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect master_ro_1; connection master; connect master_$xid, 127.0.0.1,root,,$db,$MASTER_MYPORT,; @@ -89,7 +89,7 @@ select count(*) into @s2 from t1; xa end 'ro_2'; xa prepare 'ro_2';; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect master_ro_2; connection master; connect master_$xid, 127.0.0.1,root,,$db,$MASTER_MYPORT,; @@ -100,7 +100,7 @@ select 1; xa end 'ro_1'; xa prepare 'ro_1';; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect master_ro_1; *** Zero prepared xa:s must be in the list: connection master; @@ -189,7 +189,7 @@ insert into tm set a=1; xa end 'rw_myisam'; xa prepare 'rw_myisam';; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect master_rw_myisam; connection master; connect master_$xid, 127.0.0.1,root,,$db,$MASTER_MYPORT,; @@ -198,7 +198,7 @@ insert into tm set a=1; xa end 'rw_myisam'; xa prepare 'rw_myisam';; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect master_rw_myisam; *** rw_myisam prepared must be in the list: connection master; diff --git a/mysql-test/suite/rpl/r/rpl_xa_survive_disconnect.result b/mysql-test/suite/rpl/r/rpl_xa_survive_disconnect.result index 79c8b60fc12e1..9488ee522a1ce 100644 --- a/mysql-test/suite/rpl/r/rpl_xa_survive_disconnect.result +++ b/mysql-test/suite/rpl/r/rpl_xa_survive_disconnect.result @@ -100,7 +100,7 @@ a XA END '4'; XA PREPARE '4'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect master2; connect master_bulk_conn$i, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'bulk_trx_10'; diff --git a/mysql-test/suite/rpl/r/rpl_xa_survive_disconnect_lsu_off.result b/mysql-test/suite/rpl/r/rpl_xa_survive_disconnect_lsu_off.result index 79c8b60fc12e1..9488ee522a1ce 100644 --- a/mysql-test/suite/rpl/r/rpl_xa_survive_disconnect_lsu_off.result +++ b/mysql-test/suite/rpl/r/rpl_xa_survive_disconnect_lsu_off.result @@ -100,7 +100,7 @@ a XA END '4'; XA PREPARE '4'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back disconnect master2; connect master_bulk_conn$i, 127.0.0.1,root,,test,$MASTER_MYPORT,; XA START 'bulk_trx_10'; diff --git a/mysql-test/suite/rpl/r/rpl_xa_survive_disconnect_mixed_engines.result b/mysql-test/suite/rpl/r/rpl_xa_survive_disconnect_mixed_engines.result index e0c9317d3d6e9..e1f32b1254b80 100644 --- a/mysql-test/suite/rpl/r/rpl_xa_survive_disconnect_mixed_engines.result +++ b/mysql-test/suite/rpl/r/rpl_xa_survive_disconnect_mixed_engines.result @@ -23,7 +23,7 @@ INSERT INTO tm VALUES (3); XA END 'xa_trx'; XA PREPARE 'xa_trx'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back XA COMMIT 'xa_trx' ; Warnings: Warning 1196 Some non-transactional changed tables couldn't be rolled back @@ -97,7 +97,7 @@ ROW_COUNT() XA END 'xa_trx'; XA PREPARE 'xa_trx'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back XA COMMIT 'xa_trx' ; include/sync_slave_sql_with_master.inc connection master; @@ -204,7 +204,7 @@ INSERT INTO tm VALUES (3); XA END 'xa_trx'; XA PREPARE 'xa_trx'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back xa rollback 'xa_trx' ; Warnings: Warning 1196 Some non-transactional changed tables couldn't be rolled back @@ -288,7 +288,7 @@ ROW_COUNT() XA END 'xa_trx'; XA PREPARE 'xa_trx'; Warnings: -Note 4183 Found to be read-only XA transaction is rolled back +Note 4226 Found to be read-only XA transaction is rolled back xa rollback 'xa_trx' ; include/sync_slave_sql_with_master.inc connection master; From 1e47ca53e932dda95119bb738e3465d26ae4b35e Mon Sep 17 00:00:00 2001 From: Andrei Elkin Date: Tue, 13 May 2025 19:54:43 +0300 Subject: [PATCH 3/8] MDEV-36804 rollback to savepoint should not visit TC_LOG::run_prepare_ordered .. .. otherwise it asserts expecting a non-zero value of ha_info::m_ht in the function's loop. However since it's a rollback operation such state is possible as a top-level resets the ha_info:s of transaction in a loop so some of them can get reset before binlog_rollback() is called. Such transaction that is "spawned" to binlog-and-rollback (because may contain unrollable side-effects) should not have visited TC_LOG::run_prepare_ordered() in the first place. And it did so 'cos of incorrect cache_mngr->using_xa value. This is fixed now. --- .../suite/binlog/r/mdev-32830_qa_tests.result | 23 ++++++++++++++++++ .../suite/binlog/t/mdev-32830_qa_tests.test | 24 +++++++++++++++++++ sql/log.cc | 4 +++- 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 mysql-test/suite/binlog/r/mdev-32830_qa_tests.result create mode 100644 mysql-test/suite/binlog/t/mdev-32830_qa_tests.test diff --git a/mysql-test/suite/binlog/r/mdev-32830_qa_tests.result b/mysql-test/suite/binlog/r/mdev-32830_qa_tests.result new file mode 100644 index 0000000000000..f6642a5d3e469 --- /dev/null +++ b/mysql-test/suite/binlog/r/mdev-32830_qa_tests.result @@ -0,0 +1,23 @@ +CREATE TABLE t (c INT) ENGINE=InnoDB; +XA START 'a'; +SAVEPOINT s; +INSERT INTO t VALUES (1); +CREATE TEMPORARY TABLE t (c INT) ENGINE=InnoDB; +SELECT * FROM mysql.proc; +ROLLBACK WORK TO s; +XA END 'a'; +XA ROLLBACK 'a'; +# Expected binlog events: +include/show_binlog_events.inc +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; CREATE TABLE t (c INT) ENGINE=InnoDB +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Annotate_rows # # INSERT INTO t VALUES (1) +master-bin.000001 # Table_map # # table_id: # (test.t) +master-bin.000001 # Write_rows_v1 # # table_id: # flags: STMT_END_F +master-bin.000001 # Query # # ROLLBACK +Cleanup +DROP TEMPORARY TABLE t; +DROP TABLE t; +End of tests diff --git a/mysql-test/suite/binlog/t/mdev-32830_qa_tests.test b/mysql-test/suite/binlog/t/mdev-32830_qa_tests.test new file mode 100644 index 0000000000000..5792179173182 --- /dev/null +++ b/mysql-test/suite/binlog/t/mdev-32830_qa_tests.test @@ -0,0 +1,24 @@ +--source include/have_binlog_format_row.inc +--source include/have_innodb.inc + +CREATE TABLE t (c INT) ENGINE=InnoDB; +XA START 'a'; +SAVEPOINT s; +INSERT INTO t VALUES (1); +CREATE TEMPORARY TABLE t (c INT) ENGINE=InnoDB; +--disable_result_log +SELECT * FROM mysql.proc; +--enable_query_log +ROLLBACK WORK TO s; + +XA END 'a'; +XA ROLLBACK 'a'; + +--echo # Expected binlog events: +source include/show_binlog_events.inc; + +--echo Cleanup +DROP TEMPORARY TABLE t; +DROP TABLE t; + +--echo End of tests diff --git a/sql/log.cc b/sql/log.cc index cf8863ed1abb1..cf5b1800b8b48 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -2097,9 +2097,11 @@ binlog_rollback_flush_trx_cache(THD *thd, bool all, { /* for not prepared use plain ROLLBACK */ if (thd->transaction->xid_state.get_state_code() == XA_PREPARED) + { buflen= serialize_with_xid(thd->transaction->xid_state.get_xid(), buf, query, q_len); - cache_mngr->using_xa= TRUE; + cache_mngr->using_xa= TRUE; + } } Query_log_event end_evt(thd, buf, buflen, TRUE, TRUE, TRUE, 0); From 8d4abfeca2481d44b8fc6a6357e693e807296f72 Mon Sep 17 00:00:00 2001 From: Andrei Elkin Date: Wed, 14 May 2025 20:15:12 +0300 Subject: [PATCH 4/8] MDEV-36802 flaw in external xa-commit with multiple xa-capable engines MDEV-32830 base patch missed out supporting XA-COMPLETE via hton::"complete"_by_xid with multiple engines some of them are and some are not defined ordered_commit(). The base patch fails with an assert in a path that should have been unreachable in the first place. The assert path revealed a second commit-by-xid capable engine (Spider) was running commit_by_xid() as if it is ordered_commit()-type of while Spider does not define ordered_commit. For engines featuring the ordered-commit undefined and commit-by-xid defined commit_by_xid() is considered as a general so cannot be invoked out of binlog-group-commit (run_prepare_ordered()). It would be a full commit similarly to the normal transaction case be invoked after binlog-group-commit engine loop is done^\footnote{% also specified in p.5 low-level design points of MDEV-32830 description}. For that this patch - extends commit_or_rollback_xa_engine() with a hint for xarollback_handlerton() argument that enables to understand which phase of XA completion it's being run. - elaborates in xacommit_handlerton(). Specifically to fix this bug, not invoke a defined hton->complete_by_xid when the method is to run as ordered-"complete" by engine lacking the ordered_commit. --- mysql-test/include/have_spider.inc | 4 ++ mysql-test/include/have_spider.opt | 1 + .../r/mdev-36802_multiple_xa_engine.result | 9 ++++ .../t/mdev-36802_multiple_xa_engine.test | 15 ++++++ sql/handler.cc | 54 ++++++++++++++++--- sql/handler.h | 2 +- sql/log.cc | 2 +- 7 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 mysql-test/include/have_spider.inc create mode 100644 mysql-test/include/have_spider.opt create mode 100644 mysql-test/suite/binlog/r/mdev-36802_multiple_xa_engine.result create mode 100644 mysql-test/suite/binlog/t/mdev-36802_multiple_xa_engine.test diff --git a/mysql-test/include/have_spider.inc b/mysql-test/include/have_spider.inc new file mode 100644 index 0000000000000..68056cf9aa939 --- /dev/null +++ b/mysql-test/include/have_spider.inc @@ -0,0 +1,4 @@ +if (`SELECT COUNT(*) = 0 FROM INFORMATION_SCHEMA.ENGINES WHERE engine = 'SPIDER' AND support IN ('YES', 'DEFAULT', 'ENABLED')`) +{ + --skip Test requires Spider engine +} diff --git a/mysql-test/include/have_spider.opt b/mysql-test/include/have_spider.opt new file mode 100644 index 0000000000000..18d5f6f333b2f --- /dev/null +++ b/mysql-test/include/have_spider.opt @@ -0,0 +1 @@ +--plugin-load-add=$HA_SPIDER_SO diff --git a/mysql-test/suite/binlog/r/mdev-36802_multiple_xa_engine.result b/mysql-test/suite/binlog/r/mdev-36802_multiple_xa_engine.result new file mode 100644 index 0000000000000..144046aae9d65 --- /dev/null +++ b/mysql-test/suite/binlog/r/mdev-36802_multiple_xa_engine.result @@ -0,0 +1,9 @@ +CREATE TABLE t (c INT) ENGINE=InnoDB; +SET pseudo_slave_mode=1; +XA START 'a'; +INSERT INTO t VALUES (1),(2),(3); +XA END 'a'; +XA PREPARE 'a'; +XA COMMIT 'a'; +ERROR HY000: This xid does not exist +DROP TABLE t; diff --git a/mysql-test/suite/binlog/t/mdev-36802_multiple_xa_engine.test b/mysql-test/suite/binlog/t/mdev-36802_multiple_xa_engine.test new file mode 100644 index 0000000000000..f9ad0b65f995d --- /dev/null +++ b/mysql-test/suite/binlog/t/mdev-36802_multiple_xa_engine.test @@ -0,0 +1,15 @@ +--source include/have_binlog_format_statement.inc +--source include/have_innodb.inc +--source include/have_spider.inc + +CREATE TABLE t (c INT) ENGINE=InnoDB; + +SET pseudo_slave_mode=1; +XA START 'a'; +INSERT INTO t VALUES (1),(2),(3); +XA END 'a'; +XA PREPARE 'a'; +--error 12607 +XA COMMIT 'a'; + +DROP TABLE t; diff --git a/sql/handler.cc b/sql/handler.cc index 7e1b1188f76d4..ee0ccc570b2de 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -2468,34 +2468,70 @@ int ha_rollback_trans(THD *thd, bool all) struct xahton_st { XID *xid; int result; + int ordered; }; -/* Commit an engine branch of XA */ +/* + Commit an engine branch of XA. The function serves for XA completion + after disconnect. + It can be the ordered commit (note OC label below) e.g when requested + form binlog group commit provided the particiant is capable of that, + or it can be a "general" commit-by-xid of the ordered-commit uncapable + particiants (note !OC label) or in the binlog-off server (!B label). +*/ static bool xacommit_handlerton(THD *, transaction_participant *hton, void *arg) { - if (hton->recover && hton != &binlog_tp) + if (hton == &binlog_tp || !hton->commit_by_xid) + return FALSE; + + int ordered= ((struct xahton_st *)arg)->ordered; + if ((ordered == 0) /* !B */ || + (ordered == 1 && hton->commit_ordered) /* OC */ || + (ordered == -1 && !hton->commit_ordered)) /* !OC */ { + // In case ordered is 1, it's assumed commit_by_xid is has + // the ordered commit property. hton->commit_by_xid(((struct xahton_st *)arg)->xid); ((struct xahton_st *)arg)->result= 0; } return FALSE; } -/* Rollback an engine branch of XA */ +/* + The rollback XA recovery similar to the commit one. +*/ bool xarollback_handlerton(THD *, transaction_participant *hton, void *arg) { - if (hton->recover && hton != &binlog_tp) + if (hton == &binlog_tp || !hton->rollback_by_xid) + return FALSE; + + int ordered= ((struct xahton_st *)arg)->ordered; + if ((ordered == 0) || + (ordered == 1 && hton->commit_ordered) || + (ordered == -1 && !hton->commit_ordered)) { + // In case ordered is 1, it's assumed rollback_by_xid has + // the ordered ~commit~ rollback propery. hton->rollback_by_xid(((struct xahton_st *)arg)->xid); ((struct xahton_st *)arg)->result= 0; } return FALSE; } -/* completes xa transaction in engine */ -int commit_or_rollback_xa_engine(XID *xid, bool is_commit) +/** + Completes xa transaction in engines upon xa or server recovery. + + @param xid pointer to a XID object + @param is_commit which of xa- commit or rollback to execute + @param ordered 1 means the commit/rollback + operation must be handled by tp::commit_ordered, + -1 means skip any tp::commit_ordered participant, + 0 means execute "complete"_by_xid regardless. + @return zero as success or error code as failure +*/ +int commit_or_rollback_xa_engine(XID *xid, bool is_commit, int ordered) { - struct xahton_st xaop= { xid, 1 }; + struct xahton_st xaop= { xid, 1, ordered }; tp_foreach(NULL, is_commit ? xacommit_handlerton : xarollback_handlerton, &xaop); @@ -2524,13 +2560,15 @@ int ha_commit_or_rollback_by_xid(XID *xid, bool is_commit, THD *thd) binlog_commit_by_xid(xid); else binlog_rollback_by_xid(xid); + // order-commit uncapable participants commit (todo: optimize away for normal cases) + rc= commit_or_rollback_xa_engine(xid, is_commit, -1); } else { int rc= !thd->rgi_slave ? 0 : thd->wait_for_prior_commit(); if (!rc) { - rc= commit_or_rollback_xa_engine(xid, is_commit); + rc= commit_or_rollback_xa_engine(xid, is_commit, 0); } } diff --git a/sql/handler.h b/sql/handler.h index cb52a584f563c..e6453129abe3c 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -5716,7 +5716,7 @@ int ha_change_key_cache(KEY_CACHE *old_key_cache, KEY_CACHE *new_key_cache); /* transactions: interface to handlerton functions */ int ha_start_consistent_snapshot(THD *thd); int ha_commit_or_rollback_by_xid(XID *xid, bool commit, THD *thd); -int commit_or_rollback_xa_engine(XID *xid, bool is_commit); +int commit_or_rollback_xa_engine(XID *xid, bool is_commit, int ordered); int ha_commit_one_phase(THD *thd, bool all); int ha_commit_trans(THD *thd, bool all); int ha_rollback_trans(THD *thd, bool all); diff --git a/sql/log.cc b/sql/log.cc index cf5b1800b8b48..dc92f7dc8f1d9 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -10301,7 +10301,7 @@ inline void run_xa_complete_ordered(THD *thd) XID *xid= thd->transaction->xid_state.get_xid(); bool is_commit= thd->lex->sql_command == SQLCOM_XA_COMMIT; - commit_or_rollback_xa_engine(xid, is_commit); + commit_or_rollback_xa_engine(xid, is_commit, 1); return; } From 4331ea8ae45a25ba29ff0412f2c9b25b12b14d2c Mon Sep 17 00:00:00 2001 From: Andrei Elkin Date: Thu, 15 May 2025 11:47:55 +0300 Subject: [PATCH 5/8] MDEV-36800 Assertion `thd_arg->in_multi_stmt_transaction_mode() at rolling back.. XA in XA_ROLLBACK_ONLY state. The assert was too strict having not anticipated to face a XA-PREPARE ending in rolling back. That's a correct behaviour of read-only disconnecting XA. Fixed to relax the assert. --- .../suite/binlog/r/mdev-32830_qa_tests.result | 14 +++++++++++ .../suite/binlog/t/mdev-32830_qa_tests.test | 25 ++++++++++++++++++- sql/log_event_server.cc | 4 ++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/mysql-test/suite/binlog/r/mdev-32830_qa_tests.result b/mysql-test/suite/binlog/r/mdev-32830_qa_tests.result index f6642a5d3e469..45231cf278577 100644 --- a/mysql-test/suite/binlog/r/mdev-32830_qa_tests.result +++ b/mysql-test/suite/binlog/r/mdev-32830_qa_tests.result @@ -1,3 +1,4 @@ +set @@session.binlog_format=row; CREATE TABLE t (c INT) ENGINE=InnoDB; XA START 'a'; SAVEPOINT s; @@ -20,4 +21,17 @@ master-bin.000001 # Query # # ROLLBACK Cleanup DROP TEMPORARY TABLE t; DROP TABLE t; +set @@session.binlog_format=statement; +CREATE TABLE t1 (c CHAR(1),c2 CHAR(1)) ENGINE=MyISAM; +CREATE TEMPORARY TABLE t (a INT) ENGINE=InnoDB SELECT+1 a; +XA START 'a'; +INSERT t SELECT+1 seq_1_to_1; +SET pseudo_slave_mode=1; +INSERT INTO t1 (c) VALUES (1); +XA END 'a'; +XA PREPARE 'a'; +# cleanup +XA ROLLBACK 'a'; +DROP TABLE t; +DROP TABLE t1; End of tests diff --git a/mysql-test/suite/binlog/t/mdev-32830_qa_tests.test b/mysql-test/suite/binlog/t/mdev-32830_qa_tests.test index 5792179173182..aaf19f88c97bc 100644 --- a/mysql-test/suite/binlog/t/mdev-32830_qa_tests.test +++ b/mysql-test/suite/binlog/t/mdev-32830_qa_tests.test @@ -1,6 +1,9 @@ ---source include/have_binlog_format_row.inc +--source include/have_log_bin.inc --source include/have_innodb.inc +# MDEV-36804 +set @@session.binlog_format=row; + CREATE TABLE t (c INT) ENGINE=InnoDB; XA START 'a'; SAVEPOINT s; @@ -21,4 +24,24 @@ source include/show_binlog_events.inc; DROP TEMPORARY TABLE t; DROP TABLE t; +# MDEV-36800 +set @@session.binlog_format=statement; + +CREATE TABLE t1 (c CHAR(1),c2 CHAR(1)) ENGINE=MyISAM; +CREATE TEMPORARY TABLE t (a INT) ENGINE=InnoDB SELECT+1 a; +XA START 'a'; +INSERT t SELECT+1 seq_1_to_1; +SET pseudo_slave_mode=1; +INSERT INTO t1 (c) VALUES (1); +XA END 'a'; +XA PREPARE 'a'; + +--echo # cleanup +--error ER_XAER_NOTA +XA ROLLBACK 'a'; +--error ER_BAD_TABLE_ERROR +DROP TABLE t; +DROP TABLE t1; + + --echo End of tests diff --git a/sql/log_event_server.cc b/sql/log_event_server.cc index eccc84c3b44f4..f67f7f6d9667c 100644 --- a/sql/log_event_server.cc +++ b/sql/log_event_server.cc @@ -2907,7 +2907,9 @@ Gtid_log_event::Gtid_log_event(THD *thd_arg, uint64 seq_no_arg, } else if (thd->lex->sql_command == SQLCOM_XA_PREPARE) { - DBUG_ASSERT(thd_arg->in_multi_stmt_transaction_mode()); + /* destined to rollback xa has already reset the multi-stmt feature */ + DBUG_ASSERT(thd->in_multi_stmt_transaction_mode() || + xid_state.get_state_code() == XA_ROLLBACK_ONLY); uint8 count= ha_count_rw_2pc(thd_arg, true); extra_engines= count > 1 ? 0 : UCHAR_MAX; From 600f1933622815e6606d67a1dcf810b02214229c Mon Sep 17 00:00:00 2001 From: Andrei Elkin Date: Thu, 15 May 2025 20:25:51 +0300 Subject: [PATCH 6/8] MDEV-36825 a new MDEV-32830 assert did not expect Spider's peculiarity A ha_rollback_trans() DBUG_ASSERT(thd->lex->sql_command != SQLCOM_XA_ROLLBACK || thd->transaction->xid_state.is_explicit_XA()) expected an XA sql_command match a XID state. However Spider engine can visit this function even when Spider is not the xa transaction participant (sic!) through XA ROLLBACK 'xid' from an external connection. The assert is refined, at least temporarily to satisfy MDEV-32830 testing, to let the function run an internal Spider's transaction. --- .../r/mdev-36802_multiple_xa_engine.result | 11 ++++++++++ .../t/mdev-36802_multiple_xa_engine.test | 20 +++++++++++++++++++ sql/handler.cc | 4 +++- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/mysql-test/suite/binlog/r/mdev-36802_multiple_xa_engine.result b/mysql-test/suite/binlog/r/mdev-36802_multiple_xa_engine.result index 144046aae9d65..6e333a4838e88 100644 --- a/mysql-test/suite/binlog/r/mdev-36802_multiple_xa_engine.result +++ b/mysql-test/suite/binlog/r/mdev-36802_multiple_xa_engine.result @@ -7,3 +7,14 @@ XA PREPARE 'a'; XA COMMIT 'a'; ERROR HY000: This xid does not exist DROP TABLE t; +CREATE TABLE t (c INT) ENGINE=InnoDB; +SET pseudo_slave_mode=1; +XA START 'b'; +INSERT INTO t VALUES (1); +XA END 'b'; +XA PREPARE 'b'; +Warnings: +Warning 4205 Pseudo thread id should not be modified by the client as it will be overwritten +XA ROLLBACK 'b'; +# cleanup +DROP TABLE t; diff --git a/mysql-test/suite/binlog/t/mdev-36802_multiple_xa_engine.test b/mysql-test/suite/binlog/t/mdev-36802_multiple_xa_engine.test index f9ad0b65f995d..f9f7f7f61d8fe 100644 --- a/mysql-test/suite/binlog/t/mdev-36802_multiple_xa_engine.test +++ b/mysql-test/suite/binlog/t/mdev-36802_multiple_xa_engine.test @@ -13,3 +13,23 @@ XA PREPARE 'a'; XA COMMIT 'a'; DROP TABLE t; + +# MDEV-36825 +# The mtr test passes anyway never noticing the assert. +# Its core fails as in the description with an explict +# INSTALL PLUGIN Spider SONAME 'ha_spider.so'; +# instead of source include/have_spider.inc. +SET max_session_mem_used=32768; +CREATE TABLE t (c INT) ENGINE=InnoDB; + +XA START 'b'; +--error ER_OPTION_PREVENTS_STATEMENT +INSERT INTO t VALUES (); +INSERT INTO t VALUES (); +SET pseudo_slave_mode=1; +XA END 'b'; +XA PREPARE 'b'; +XA ROLLBACK 'b'; + +--echo # cleanup +DROP TABLE t; diff --git a/sql/handler.cc b/sql/handler.cc index ee0ccc570b2de..c88f9fe77ab9d 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -2367,8 +2367,10 @@ int ha_rollback_trans(THD *thd, bool all) int err; bool has_binlog= has_binlog_hton(ha_info); + // !m_ht->commit_ordered condition can occur e.g with Spider DBUG_ASSERT(thd->lex->sql_command != SQLCOM_XA_ROLLBACK || - thd->transaction->xid_state.is_explicit_XA()); + (thd->transaction->xid_state.is_explicit_XA() || + !thd->transaction->stmt.ha_list->ht()->commit_ordered)); if (has_binlog && (err= binlog_tp.rollback(thd, all))) { From ed346472d7694d3aef627a93c801dbee7930639b Mon Sep 17 00:00:00 2001 From: Andrei Elkin Date: Fri, 16 May 2025 13:01:04 +0300 Subject: [PATCH 7/8] MDEV-36799 refine the second MDEV-32455-type asssert CACHE INDEX t PARTITION after XA-PREPARE also this a line previously assert for another "exceptional" sql command: ... This is really a variety of MDEV-32455. The assert did not expect another use case of implicit rollback with XA transaction while the sql command should have been (I think) rejected with XAER_RMFAIL: The command cannot be executed when global transaction is in the ACTIVE state The assert is refined and its another instance removed being redundant. --- .../suite/binlog/r/mdev-36802_multiple_xa_engine.result | 7 +++++-- sql/log.cc | 9 ++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/mysql-test/suite/binlog/r/mdev-36802_multiple_xa_engine.result b/mysql-test/suite/binlog/r/mdev-36802_multiple_xa_engine.result index 6e333a4838e88..8d7f2338c7bf2 100644 --- a/mysql-test/suite/binlog/r/mdev-36802_multiple_xa_engine.result +++ b/mysql-test/suite/binlog/r/mdev-36802_multiple_xa_engine.result @@ -7,10 +7,13 @@ XA PREPARE 'a'; XA COMMIT 'a'; ERROR HY000: This xid does not exist DROP TABLE t; +SET max_session_mem_used=32768; CREATE TABLE t (c INT) ENGINE=InnoDB; -SET pseudo_slave_mode=1; XA START 'b'; -INSERT INTO t VALUES (1); +INSERT INTO t VALUES (); +ERROR HY000: The MariaDB server is running with the --max-session-mem-used=32768 option so it cannot execute this statement +INSERT INTO t VALUES (); +SET pseudo_slave_mode=1; XA END 'b'; XA PREPARE 'b'; Warnings: diff --git a/sql/log.cc b/sql/log.cc index dc92f7dc8f1d9..b46cfce3cc2e6 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -10309,8 +10309,8 @@ inline void run_xa_complete_ordered(THD *thd) { DBUG_ASSERT(thd->lex->sql_command == SQLCOM_XA_COMMIT || (thd->lex->sql_command == SQLCOM_XA_ROLLBACK || - (thd->lex->sql_command == SQLCOM_PRELOAD_KEYS && - thd->get_stmt_da()->get_sql_errno() /* MDEV-32455 */))); + /* MDEV-32455, MDEV-36799 */ + thd->get_stmt_da()->get_sql_errno() == ER_XAER_RMFAIL)); transaction_participant *ht= ha_info->ht(); if (ht == &binlog_tp || !ht->commit_ordered) @@ -10321,11 +10321,6 @@ inline void run_xa_complete_ordered(THD *thd) } else { - DBUG_ASSERT((thd->lex->sql_command == SQLCOM_XA_ROLLBACK || - (thd->lex->sql_command == SQLCOM_PRELOAD_KEYS && thd->get_stmt_da()->get_sql_errno() /* MDEV-32455 */)) || - thd->transaction->xid_state.get_error() == - ER_XA_RBROLLBACK); - ht->rollback(thd, true); } } From 14c105a8bd6be7463cf30a2af0122fc086c72dda Mon Sep 17 00:00:00 2001 From: Andrei Elkin Date: Fri, 16 May 2025 16:21:04 +0300 Subject: [PATCH 8/8] MDEV-36826 read-only on slave XA-prepare thought it failed to assert An observed assert in rpl_group_info::unmark_start_commit DBUG_ASSERT(!gco->next_gco || gco->next_gco->wait_count > e->count_committing_event_groups || allow_unmark_after_complete); was overreaction. The reason that execution got to that point was MDEV-33921 underfixed that issue for the slave parallel mode. That's done now. The former test is extended to hit the assert by the (buggy) BASE of this commit. --- mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result | 1 + mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test | 1 + sql/handler.cc | 6 +++++- sql/rpl_rli.cc | 4 ++++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result b/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result index 33fc704818bc6..5f0a2adabbcc7 100644 --- a/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result +++ b/mysql-test/suite/rpl/r/rpl_xa_empty_transaction.result @@ -1234,6 +1234,7 @@ include/stop_slave.inc SET @@GLOBAL.replicate_ignore_db= ""; SET @@GLOBAL.replicate_do_db= "db2"; change master to master_use_gtid=slave_pos; +SET @@GLOBAL.slave_parallel_workers=2; include/start_slave.inc connection server_1; use db1; diff --git a/mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test b/mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test index a422616f7ada2..891195699b4e8 100644 --- a/mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test +++ b/mysql-test/suite/rpl/t/rpl_xa_empty_transaction.test @@ -199,6 +199,7 @@ change master to master_use_gtid=slave_pos; SET @@GLOBAL.replicate_ignore_db= ""; SET @@GLOBAL.replicate_do_db= "db2"; change master to master_use_gtid=slave_pos; +SET @@GLOBAL.slave_parallel_workers=2; --source include/start_slave.inc --connection server_1 diff --git a/sql/handler.cc b/sql/handler.cc index c88f9fe77ab9d..c18cdcfb87788 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -2332,7 +2332,11 @@ int ha_rollback_trans(THD *thd, bool all) if (thd->rgi_slave && !thd->rgi_slave->worker_error && - thd->rgi_slave->did_mark_start_commit) + thd->rgi_slave->did_mark_start_commit && + /* rollback-only XA executes rollback on success, should not unmark */ + !(thd->transaction->xid_state.is_explicit_XA() && + thd->transaction->xid_state.get_state_code() == + XA_ROLLBACK_ONLY)) thd->rgi_slave->unmark_start_commit(); } #endif diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc index d82d75eb36e5e..3535810e3b5d3 100644 --- a/sql/rpl_rli.cc +++ b/sql/rpl_rli.cc @@ -2537,6 +2537,10 @@ rpl_group_info::unmark_start_commit() if (!did_mark_start_commit) return; did_mark_start_commit= false; + /* MDEV-36826 read-only xa tried unmarking for no reason */ + DBUG_ASSERT(!(thd->transaction->xid_state.is_explicit_XA() && + thd->transaction->xid_state.get_state_code() == + XA_ROLLBACK_ONLY)); e= this->parallel_entry; mysql_mutex_lock(&e->LOCK_parallel_entry);