Bug #106818 Replication may fail when XA transactions use replace into statements
Submitted: 24 Mar 2022 11:23 Modified: 7 Jun 2022 2:29
Reporter: Dongchao Yang Email Updates:
Status: Verified Impact on me:
None 
Category:MySQL Server: Replication Severity:S2 (Serious)
Version:5.7,8.0, 5.7.37 OS:Any
Assigned to: CPU Architecture:Any
Tags: replace into, replication, xa

[24 Mar 2022 11:23] Dongchao Yang
Description:
In a master-slave replication scenario, when master executes XA transactions with replace into statements, replication may fail with lock wait timeout. 

The following conditions are required to cause the replication failure:
(1) A table with unique key.
(2) XA transactions.
(3) Replace into statements in XA transactions cause the replacement of existing unique key records. 
(4) The records replaced by replace into statements have not been purged due to a high workload or the need to read the replaced records by other readviews. 

When a replace into statement encounters an existing duplicate unique key record, it locks the next record of this unique key record even the next record is delete-marked and has not been purged yet.

Assume a, b, c are the consecutive unique key records of a table. Replication error happens when:
(1) Session1 deletes unique key 'b' and commits the transaction;
(2) Unique key 'b' is not purged on master for some reason(noted above);
(3) The delete statement is replicated to slave and executed by slave;
(4) Unique key 'b' is purged by slave;
(5) Session1 executes an XA transaction(called XA1) and replaces unique key 'a', causing the lock of the delete-marked unique key 'b'. Then session1 prepares the XA transaction. Note that unique key 'c' is not locked by the transaction because it is not the next record of 'a';
(6) Session2 executes an XA transaction(called XA2) and replaces unique key 'c'.
(7) The two XA transactions are replicated to slave. Since 'b' is purged on slave, 'c' is the next record of 'a'. So XA1 locks unique key 'c', XA2 will never get the lock of 'c'. Replication fails.

How to repeat:
Testcase:

(1) master opt file:

issue_rpl_lock_wait_timeout_when_replace_into_and_xa_transactions-master.opt

--gtid_mode=ON
--enforce_gtid_consistency=ON
--binlog_format=row
--binlog_transaction_dependency_tracking=COMMIT_ORDER

(2) slave opt file:

issue_rpl_lock_wait_timeout_when_replace_into_and_xa_transactions-slave.opt

--gtid_mode=ON
--enforce_gtid_consistency=ON
--binlog_format=row
--binlog_transaction_dependency_tracking=COMMIT_ORDER
--innodb_lock_wait_timeout=3
--slave_transaction_retries=0
--slave_parallel_workers=0
--innodb_status_output_locks=ON

(3) testcase file

issue_rpl_lock_wait_timeout_when_replace_into_and_xa_transactions.test

# Replication fails with lock wait timeout in the following case.

-- source include/master-slave.inc

--echo # 1. Prepare

--let $rpl_connection_name= master
--source include/rpl_connection.inc

select @@global.binlog_transaction_dependency_tracking;

CREATE TABLE t (
  id INT NOT NULL AUTO_INCREMENT,
  k VARCHAR(20),
	c VARCHAR(20),
  PRIMARY KEY(id),
	UNIQUE KEY uk(k)
) ENGINE=InnoDB;

INSERT INTO t VALUES(1, 'aaa', 'aaaaa');
INSERT INTO t VALUES(2, 'bbb', 'bbbbb');
INSERT INTO t VALUES(3, 'ccc', 'ccccc');

SELECT * FROM t;

CREATE USER user1;
GRANT SELECT, UPDATE, DELETE, INSERT ON test.* to user1;

--connect(conn1,localhost,user1)
--connect(conn2,localhost,user1)
--connect(conn3,localhost,user1)

--echo
--echo # 2. Start a tansaction and don't end it. This will prevent the purge thread from running.

--connection conn1
BEGIN;
SET SESSION TRANSACTION_ISOLATION = 'REPEATABLE-READ';
SELECT * FROM t;

--echo
--echo # 3. Delete the record of k = 'bbb'. It won't be purged by master but it will be replicated to slave and will be purged on slave.

--connection conn2
DELETE FROM t WHERE k = 'bbb';

--source include/rpl_sync.inc

--echo sleep 5 seconds to wait for slave to purge the deleted record.
--sleep 5

--echo
--echo # 4. Execute XA transactions and replace the records of k = 'aaa' and 'ccc'

--connection conn2
XA START 'XA1';
REPLACE INTO t values(1, 'aaa', 'xxxxx');
XA END 'XA1';
XA PREPARE 'XA1';

--connection conn3
XA START 'XA2';
REPLACE INTO t values(3, 'ccc', 'yyyyy');
XA END 'XA2';
XA PREPARE 'XA2';

--echo
--echo # 5. Replication error occurs.

--let $rpl_connection_name= master
--source include/rpl_connection.inc

--sleep 1

--let $rpl_connection_name= slave
--source include/rpl_connection.inc

select @@global.innodb_lock_wait_timeout;

--echo
--echo There is a lock wait in SHOW ENGINE INNODB STATUS.
query_vertical SHOW ENGINE INNODB STATUS;

--echo sleep 5 seconds to wait for lock wait timeout.
--sleep 5

--echo
--echo SHOW SLAVE STATUS shows lock wait timeout.
query_vertical SHOW SLAVE STATUS;

=======

Normally, the test result will show the replication error. If it doesn't, please run the testcase a couple of times, there is a possibility that the slave hasn't purged the deleted record in 5 seconds.

Suggested fix:
I think a replace into statement should release the lock of the next unique record of the replaced record. Maybe we should release the lock when at the end of XA PREPARE;
[24 Mar 2022 12:31] MySQL Verification Team
Hello Dongchao Yang,

Thank you for the report and feedback.
Observed that 5.7.37 is affected(no issues in 8.0.28 with the provided mtr test case).

regards,
Umesh
[24 Mar 2022 14:59] Dongchao Yang
The replace into statement is replicated to slave and become an update statement which adds an S lock on the next unique record. It seems the lock logic of the an update statement on the slave is similar to the replace into statement on the master.
[7 Jun 2022 2:29] Dongchao Yang
Could you please tell me which commit fixed the issue in mysql8.0?