XA 是数据库对两阶段提交的标准接口。MySQL 的 XA START、XA END、XA PREPARE、XA COMMIT 各自处于什么状态?连接断开、实例崩溃后 prepared 事务去哪了?代价多大?这篇用真实的 MySQL 26.7 逐条试出来。
mysql> XA START 'x1' -> OK mysql> INSERT INTO orders VALUES (1, 'CREATED') -> OK mysql> COMMIT -> 错误:OperationalError 1399 XAER_RMFAIL: The command cannot be executed when global transaction is in the ACTIVE state mysql> XA PREPARE 'x1' -> 错误:OperationalError 1399 XAER_RMFAIL: The command cannot be executed when global transaction is in the ACTIVE state mysql> XA END 'x1' -> OK mysql> INSERT INTO orders VALUES (2, 'CREATED') -> 错误:OperationalError 1399 XAER_RMFAIL: The command cannot be executed when global transaction is in the IDLE state mysql> XA PREPARE 'x1' -> OK mysql> SELECT 1 -> ((1,),) 另一个会话此时看到的订单数: 0 (prepare 了但还没 commit,不可见) XA RECOVER: ((1, 2, 0, 'x1'),) mysql> XA COMMIT 'x1' -> OK 提交后订单数: 1 ,XA RECOVER: ()
XA START 之后是 ACTIVE,不能直接 COMMIT 或 PREPARE;XA END 之后是 IDLE,不能再写;XA PREPARE 之后才可以被任何会话 XA COMMIT 或 XA ROLLBACK。XA RECOVER 只给 xid,没有 prepare 了多久。
XA START;INSERT;XA END 之后断开连接:XA RECOVER = () ,订单数 = 0 (没 prepare 的 XA 事务随连接一起回滚) XA PREPARE 之后断开连接:XA RECOVER = ((1, 2, 0, 'y2'),) ,订单数 = 0 mysql> XA COMMIT 'y2' -> OK 换一个连接 XA COMMIT 之后订单数 = 1
没有 prepare 的 XA 事务随连接一起回滚;prepare 之后事务属于服务端,断开连接后换一个连接就能收尾。
别的会话 SELECT qty: 100 (旧值)
别的会话 UPDATE 同一行:OperationalError 1205 Lock wait timeout exceeded; try restarting transaction,等了 2004.2 ms
information_schema.innodb_trx(trx_state, trx_mysql_thread_id, trx_rows_locked, trx_started 距现在多少秒): [('RUNNING', 0, 1, 2)] —— 这个 prepared 的事务在里面显示成 RUNNING,线程号 0(已经没有连接了)行锁一直持有:读不被挡,写同一行等到 innodb_lock_wait_timeout。在 information_schema.innodb_trx 里它显示成 RUNNING、线程号 0,不容易认出来。
kill -9 mysqld(此时 k1 已 PREPARE,k2 还在 ACTIVE)
重启用时 611.1 ms
重启后 XA RECOVER: ((1, 2, 0, 'k1'),) ,订单数(k2 的插入): 0
重启后别的会话 UPDATE 同一行: OperationalError 1205 Lock wait timeout exceeded; try restarting transaction (行锁在恢复时被重新持有)
mysql> XA COMMIT 'k1' -> OK
XA COMMIT 之后 qty = 99
binlog 里的 XA 相关事件:
('binlog.000001', 'Query', "XA START X'7831',X'',1")
('binlog.000001', 'Query', "XA END X'7831',X'',1")
('binlog.000001', 'XA_prepare', "XA PREPARE X'7831',X'',1")
('binlog.000001', 'Query', "XA COMMIT X'7831',X'',1")
('binlog.000001', 'Query', "XA START X'7932',X'',1")
('binlog.000001', 'Query', "XA END X'7932',X'',1")
('binlog.000001', 'XA_prepare', "XA PREPARE X'7932',X'',1")
('binlog.000001', 'Query', "XA COMMIT X'7932',X'',1")
('binlog.000001', 'Query', "XA START X'7a31',X'',1")
('binlog.000001', 'Query', "XA END X'7a31',X'',1")
('binlog.000001', 'XA_prepare', "XA PREPARE X'7a31',X'',1")
('binlog.000001', 'Query', "XA ROLLBACK X'7a31',X'',1")
('binlog.000001', 'Query', "XA START X'6b31',X'',1")
('binlog.000001', 'Query', "XA END X'6b31',X'',1")
('binlog.000001', 'XA_prepare', "XA PREPARE X'6b31',X'',1")
('binlog.000002', 'Query', "XA COMMIT X'6b31',X'',1")
mysqld 错误日志里和 XA / prepared 有关的行:
[Server] Starting XA crash recovery...
[Server] Found 1 prepared XA transactions
[Server] XA crash recovery finished.prepare 之后 mysqld 被 kill -9,重启后 XA RECOVER 里还在、锁被恢复,没 prepare 的 XA 事务消失。binlog 里有 XA_prepare 事件。
写法 | 中位数 | p99 | 每个事务的 redo fsync(两个实例合计) | 相对普通事务 ------------------------------+--------+--------+--------------------------+------- 普通事务(一个实例) | 0.2 ms | 0.4 ms | 1.01 | 1.0x XA(同一个实例,走完 prepare + commit) | 0.3 ms | 0.7 ms | 3.00 | 1.8x XA(跨两个实例) | 0.6 ms | 1.7 ms | 6.02 | 3.4x redo fsync 来自 Innodb_os_log_fsyncs;binlog 的 fsync(sync_binlog=1)没有计在内,因为没有对应的状态变量 一个事务里每条语句平均引起几次 redo fsync(Innodb_os_log_fsyncs 的增量,单个实例,各重复 200 个事务取平均;单次测量会被后台刷盘线程干扰,BEGIN 这类不写日志的语句也会量到约 0.1 的本底): 普通事务:BEGIN 0.11, INSERT 0.10, UPDATE 0.09, COMMIT 1.08 合计 1.38 XA 事务: XA START 0.04, INSERT INTO 0.08, UPDATE stock 0.10, XA END 0.12, XA PREPARE 1.10, XA COMMIT 2.06 合计 3.49
XA 提交 = XA PREPARE 约 1 次 + XA COMMIT 约 2 次 redo fsync,单个实例就是 3 次,跨两个实例 6 次,中位数 3.4 倍。