编程 transaction_replay 救不回事务的三处盲区:会话状态重建与 checksum 校验

2026-11-11 00:03:45

transaction_replay 救不回事务的三处盲区:会话状态重建与 checksum 校验

transaction_replay 默认是 false。打开它之后,也并不意味着事务一定能被救回来。readwritesplit 的迁移路径由两个条件决定:新连接上的会话状态能不能重建,服务器响应能不能被校验和证明一致。只要任一条件不成立,结果要么是关闭客户端连接,要么更糟:事务确实重放过,但产生了重复执行。

项目信息:

重放做了什么

readwritesplit 会维护一份 session command history,记录 SET、USE 这类改变连接状态的命令。当需要新建连接时,它会把旧连接上的这些会话命令在新连接上重放一遍。

事务迁移的顺序是:先在新连接上回放会话命令、重建会话状态,再重放事务本身。

开启 transaction_replay 会隐式打开 delayed_retry 与 master_reconnection,并把 master_failure_mode 覆盖为 fail_on_write;也就是说,任何显式配置的这三项都会被改掉。事务所在节点故障时,路由器尝试迁移到替换节点;如果没有可用的替换节点,客户端连接会被关闭。

盲区一:事务中途改会话状态,重放顺序是反的

文档给的例子:

BEGIN;
SET SESSION sql_mode='';
INSERT INTO t VALUES (...);
COMMIT;

这条事务如果要重放,实际执行的第一件事是“恢复会话状态”:把事务里那条 SET SESSION sql_mode='' 先执行一遍,然后再执行整段事务。于是 sql_mode 的生效时点从“BEGIN 之后”变成了“BEGIN 之前”,原本依赖前一个状态才能成功的语句现在报错,重放结果与记录下来的结果对不上,checksum mismatch,客户端连接被关闭。

autocommit 是同类问题。另一条已知限制是:不要用 prepared statement 去提交或回滚事务,也不要用它改 autocommit。

这类失败无法靠调大重试次数解决,因为每次重放的顺序都一样。

盲区二:会话命令历史只有 50 条,而且会被裁剪

参数默认值说明
max_sescmd_history50service 级通用参数,按命令条数计,不是字节数
prune_sescmd_historytrue超出上限时裁掉旧命令
disable_sescmd_historyfalse从 24.02 起置 true 会自动禁用 master_reconnection
master_reconnectiontrue(>= 24.02)/ false(= 24.02)或 0s(grep trx_max_size_exceeded

日志里关注 `checksum mismatch` 与 `Causal read` 两个关键字。

配置片段:

```ini
[Read-Write-Service]
type=service
router=readwritesplit
transaction_replay=true
transaction_replay_safe_commit=true
transaction_replay_checksum=full
max_sescmd_history=200
prune_sescmd_history=true

max_sescmd_history 的具体取值取决于应用的 SET / USE 密度,需自行验证。

升级注意

25.01 中 reuse_prepared_statements 从 readwritesplit 移出到 PsReuse filter,optimistic_trx 移到 OptimisticTrx filter。25.01.0 有一个回归:transaction_replay_safe_commit=true 会连带禁用 autocommit 写入的重放;25.01.1 修改了 delayed_retry,不再重试已经发送到数据库的查询。

推荐文章

程序员茄子在线接单