Bug #88534 | XA may lost prepared transaction and cause different between master and slave. | ||
---|---|---|---|
Submitted: | 17 Nov 2017 11:04 | Modified: | 23 Mar 2021 8:17 |
Reporter: | Ze Yang (OCA) | Email Updates: | |
Status: | Verified | Impact on me: | |
Category: | MySQL Server: XA transactions | Severity: | S1 (Critical) |
Version: | 5.7, 5.7.20 | OS: | Any |
Assigned to: | CPU Architecture: | Any |
[17 Nov 2017 11:04]
Ze Yang
[20 Nov 2017 9:48]
MySQL Verification Team
Hello Michael Yang, Thank you for the report and test case. Verified as described with 5.7.20 src build after patching. Please note that in order to submit contributions you must first sign the Oracle Contribution Agreement (OCA). More details are described in "Contributions" tab, please ensure to re-send the patch via that tab. Otherwise we would not be able to accept it. Thanks, Umesh
[20 Nov 2017 9:58]
MySQL Verification Team
test results
Attachment: 88534.results (application/octet-stream, text), 9.01 KiB.
[23 Feb 2018 3:51]
Ze Yang
Last days I thought this bug again. If we call InnoDB prepare first, the binlog would lost, the master would have a prepared xa transaction but the slave not. Then we found two methods to solve this problem. the second method may better . 1. Remain 'XA prepare': binlog prepare->engine prepare(ensure engine flush logs), 'XA COMMIT': binlog commit->engine commit. Write 'xa rollback' when we found the storage engine on master lost prepared transaction. When the server restarted, we search the last binlog to check whether there's a 'xa prepare' which no 'xa commit/rollback' matched. If we find the not matched 'xa prepare x' in binlog, we check whether there's prepared xa 'x' in the storage engine. If there's no prepared xa transacton, we write 'xa rollback x' to the binlog to cancel prepared transaction on slave. But this method not friendly to HA. If we change to slave after the master crash, there's error happened. 2. 'XA PREPARE': engine prepare->binlog prepare, 'XA COMMIT' binlog commit-> engine commit. Add one table like the 'mysql.gtid_executed' table to record the xa prepared transactions when rotate binlog. Search the last binlog to decide whether the prepared transaction in storage engine should be rollbacked, committed or remained prepared. If we don't add one table to recored the xa prepared transaction when rotate binlog, we would not know whether the prepared transaction's binlog was logged as 'xa prepare' may recorded in the purged binlog. So add a table to record xa prepared transaction when rotate binlog, then we could use binlog to decide the transaction state.
[23 Mar 2021 8:17]
Ze Yang
My solution, may be helpful for others: The execution order: 'XA PREPARE':engine->prepare, binlog->prepare. 'XA COMMIT one phase': engine->prepare, binlog->commit, engine->commit. 'XA COMMIT': binlog->commit, engine->commit (ensure engine commit before binary log rotate). 'XA ROLLBACK': binlog->rollback, engine->rollback (ensure engine rollback before binary log rotate). Add two types of event for external XA recovery. Previous_prepared_xids_log_event: Containing the xids of all external XA transactions that are in XA-PREPARED state when rotate binary log. Write to the binary log after Previous_gtids_log_event. This event is for getting the state of external XA without traverse all the binary logs. XA_preparing_log_event: Containing the xid of external XA transaction that execute 'XA PREPARE' before engine->prepare. This event is for recognizing the state of external XA which have the same xid with some one that just execute 'XA COMMIT' successfully.