From 32dee36acd2f921ed21962d945a1d4db22528b57 Mon Sep 17 00:00:00 2001 From: yeweixu Date: Thu, 18 Dec 2025 12:01:03 +0800 Subject: [PATCH] [bugfix] previous gtids may loss xa commit/rollback gtids Description: During binlog rotation, LOCK_log is acquired to prevent new binlog writes. It then waits for m_atomic_prep_xids to decrement to zero, ensuring all in-flight committing transactions are completed. However, for XA COMMIT/XA ROLLBACK statements, binlog entries are generated without an XID. Consequently, binlog rotation does not wait for these transactions to complete. This may result in rotation occurring before their commits finish, causing the previous gtids captured during rotation to miss their GTIDs. https://bugs.mysql.com/bug.php?id=118797 --- .../r/binlog_rotate_not_wait_xa_commit.result | 49 +++++++++++++++++++ ...inlog_rotate_not_wait_xa_commit-master.opt | 3 ++ .../t/binlog_rotate_not_wait_xa_commit.test | 48 ++++++++++++++++++ sql/binlog.cc | 22 ++++++++- 4 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 mysql-test/suite/innodb/r/binlog_rotate_not_wait_xa_commit.result create mode 100644 mysql-test/suite/innodb/t/binlog_rotate_not_wait_xa_commit-master.opt create mode 100644 mysql-test/suite/innodb/t/binlog_rotate_not_wait_xa_commit.test diff --git a/mysql-test/suite/innodb/r/binlog_rotate_not_wait_xa_commit.result b/mysql-test/suite/innodb/r/binlog_rotate_not_wait_xa_commit.result new file mode 100644 index 00000000000..499b199b5fb --- /dev/null +++ b/mysql-test/suite/innodb/r/binlog_rotate_not_wait_xa_commit.result @@ -0,0 +1,49 @@ +RESET REPLICA; +create table t1 (id int primary key auto_increment); +create table t2 (id int primary key auto_increment); +insert into t1 values(); +xa start '1'; +insert into t1 values(); +xa end '1'; +xa prepare '1'; +SET SESSION debug="+d,force_rotate"; +SET debug_sync="bgc_after_commit_stage_before_rotation WAIT_FOR conn2_wait"; +insert into t2 values();; +SET debug_sync="update_gtid_state_before_global_tsid_lock WAIT_FOR conn1_wait"; +xa commit '1';; +SET debug_sync = "now SIGNAL conn2_wait"; +SET debug_sync = "now SIGNAL conn1_wait"; +drop table t1; +show binlog events in 'master-bin.000001'; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Format_desc # # Server ver: 9.5.0-debug, Binlog ver: 4 +master-bin.000001 # Previous_gtids # # +master-bin.000001 # Gtid # # SET @@SESSION.GTID_NEXT= 'MASTER_UUID:1' +master-bin.000001 # Query # # use `test`; create table t1 (id int primary key auto_increment) /* xid=65 */ +master-bin.000001 # Gtid # # SET @@SESSION.GTID_NEXT= 'MASTER_UUID:2' +master-bin.000001 # Query # # use `test`; create table t2 (id int primary key auto_increment) /* xid=66 */ +master-bin.000001 # Gtid # # SET @@SESSION.GTID_NEXT= 'MASTER_UUID:3' +master-bin.000001 # Query # # BEGIN +master-bin.000001 # Table_map # # table_id: # (test.t1) +master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F +master-bin.000001 # Xid # # COMMIT /* xid=67 */ +master-bin.000001 # Gtid # # SET @@SESSION.GTID_NEXT= 'MASTER_UUID:4' +master-bin.000001 # Query # # XA START X'31',X'',1 +master-bin.000001 # Table_map # # table_id: # (test.t1) +master-bin.000001 # Write_rows # # 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 # Gtid # # SET @@SESSION.GTID_NEXT= 'MASTER_UUID:5' +master-bin.000001 # Query # # BEGIN +master-bin.000001 # Table_map # # table_id: # (test.t2) +master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F +master-bin.000001 # Xid # # COMMIT /* xid=74 */ +master-bin.000001 # Gtid # # SET @@SESSION.GTID_NEXT= 'MASTER_UUID:6' +master-bin.000001 # Query # # XA COMMIT X'31',X'',1 +master-bin.000001 # Rotate # # master-bin.000002;pos=4 +show binlog events in 'master-bin.000002'; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000002 # Format_desc # # Server ver: 9.5.0-debug, Binlog ver: 4 +master-bin.000002 # Previous_gtids # # MASTER_UUID:1-6 +master-bin.000002 # Gtid # # SET @@SESSION.GTID_NEXT= 'MASTER_UUID:7' +master-bin.000002 # Query # # use `test`; DROP TABLE `t1` /* generated by server */ /* xid=79 */ diff --git a/mysql-test/suite/innodb/t/binlog_rotate_not_wait_xa_commit-master.opt b/mysql-test/suite/innodb/t/binlog_rotate_not_wait_xa_commit-master.opt new file mode 100644 index 00000000000..0e29785b18e --- /dev/null +++ b/mysql-test/suite/innodb/t/binlog_rotate_not_wait_xa_commit-master.opt @@ -0,0 +1,3 @@ +--log-bin=master-bin +--gtid_mode=on +--enforce_gtid_consistency=on diff --git a/mysql-test/suite/innodb/t/binlog_rotate_not_wait_xa_commit.test b/mysql-test/suite/innodb/t/binlog_rotate_not_wait_xa_commit.test new file mode 100644 index 00000000000..ca865ef814e --- /dev/null +++ b/mysql-test/suite/innodb/t/binlog_rotate_not_wait_xa_commit.test @@ -0,0 +1,48 @@ +--source include/have_debug.inc +--source include/have_debug_sync.inc +--source include/have_log_bin.inc +--source include/force_restart.inc + +RESET REPLICA; +let $MYSQLD_DATADIR= `select @@datadir`; +--let $master_uuid= `SELECT @@GLOBAL.SERVER_UUID` + +--connect(conn1,localhost,root,,test) +--connect(conn2,localhost,root,,test) +--connect(conn3,localhost,root,,test) + +create table t1 (id int primary key auto_increment); +create table t2 (id int primary key auto_increment); + +insert into t1 values(); + +connection conn1; +xa start '1';insert into t1 values();xa end '1';xa prepare '1'; + +connection conn2; +SET SESSION debug="+d,force_rotate"; +SET debug_sync="bgc_after_commit_stage_before_rotation WAIT_FOR conn2_wait"; +--send insert into t2 values(); + +sleep 1; + +connection conn1; +SET debug_sync="update_gtid_state_before_global_tsid_lock WAIT_FOR conn1_wait"; +--send xa commit '1'; + +connection conn3; +SET debug_sync = "now SIGNAL conn2_wait"; +sleep 2; +SET debug_sync = "now SIGNAL conn1_wait"; + +drop table t1; + +--replace_result $master_uuid MASTER_UUID +--replace_column 2 # 4 # 5 # +--replace_regex /table_id: [0-9]+/table_id: #/ +show binlog events in 'master-bin.000001'; + +--replace_result $master_uuid MASTER_UUID +--replace_column 2 # 4 # 5 # +--replace_regex /table_id: [0-9]+/table_id: #/ +show binlog events in 'master-bin.000002'; diff --git a/sql/binlog.cc b/sql/binlog.cc index 6f67db05e0f..33c17f63a26 100644 --- a/sql/binlog.cc +++ b/sql/binlog.cc @@ -712,6 +712,8 @@ class binlog_cache_data { return flags.with_xid; } + bool has_xa_prepare_commit() const { return flags.with_xa_prepare_commit; } + bool is_trx_cache() const { return flags.transactional; } my_off_t get_byte_position() const { return m_cache.length(); } @@ -751,6 +753,7 @@ class binlog_cache_data { flags.with_start = false; flags.with_end = false; flags.with_content = false; + flags.with_xa_prepare_commit = false; m_event_counter = 0; } } @@ -968,6 +971,11 @@ class binlog_cache_data { This indicates that the cache contain content other than START/END. */ bool with_content : 1; + + /* + This indicates that the cache contain content XA PREPARE/COMMIT + */ + bool with_xa_prepare_commit : 1; } flags; /// Compress the current transaction "in-place", if possible @@ -1232,6 +1240,10 @@ class binlog_cache_mngr { return 0; } + bool has_xa_prepare_commit() { + return stmt_cache.has_xa_prepare_commit() || trx_cache.has_xa_prepare_commit(); + } + /** Check if at least one of transactions and statement binlog caches contains an empty transaction, other one is empty or contains an @@ -1536,6 +1548,13 @@ int binlog_cache_data::write_event(Log_event *ev) { if (ev->starts_group()) flags.with_start = true; if (ev->ends_group()) flags.with_end = true; if (!ev->starts_group() && !ev->ends_group()) flags.with_content = true; + if (ev->get_type_code() == mysql::binlog::event::QUERY_EVENT) { + Query_log_event *qev = static_cast(ev); + if ((qev->is_query_prefix_match(STRING_WITH_LEN("XA COMMIT")) || + qev->is_query_prefix_match(STRING_WITH_LEN("XA ROLLBACK")))) { + flags.with_xa_prepare_commit = true; + } + } m_event_counter++; DBUG_PRINT("debug", ("event_counter= %lu", static_cast(m_event_counter))); @@ -7380,6 +7399,7 @@ std::pair MYSQL_BIN_LOG::flush_thread_caches(THD *thd) { binlog_cache_mngr *cache_mngr = thd_get_cache_mngr(thd); my_off_t bytes = 0; bool wrote_xid = false; + bool has_xa_prepare_commit = cache_mngr->has_xa_prepare_commit(); int error = cache_mngr->flush(thd, &bytes, &wrote_xid); if (!error && bytes > 0) { /* @@ -7387,7 +7407,7 @@ std::pair MYSQL_BIN_LOG::flush_thread_caches(THD *thd) { this function documentation for more info. */ thd->set_trans_pos(log_file_name, m_binlog_file->position()); - if (wrote_xid) inc_prep_xids(thd); + if (wrote_xid || has_xa_prepare_commit) inc_prep_xids(thd); } DBUG_PRINT("debug", ("bytes: %llu", bytes)); return std::make_pair(error, bytes); -- 2.37.1