长事务直接导致主从延迟,因其卡死从库sql线程;应通过information_schema.innodb_trx按trx_started和trx_state定位,结合checkpoint表与主键分段实现安全拆分。

长事务是主从延迟的根因,不是“可能造成”延迟,而是直接卡死 SQL 线程——它不慢,是停着不动。 一旦一个 UPDATE 改了 50 万行还没提交,从库 SQL 线程就堵在那条语句上,后续所有 binlog 全排队,Seconds_Behind_Master 会按秒线性上涨,且无法被并行复制绕过。
怎么快速定位正在运行的长事务
别只看 SHOW PROCESSLIST,它只显示连接状态,不反映事务真实生命周期。真正有效的入口是 information_schema.innodb_trx:
- 执行
SELECT trx_id, trx_started, trx_state, trx_mysql_thread_id, trx_query FROM information_schema.innodb_trx ORDER BY trx_started;—— 关键看trx_started时间戳和当前时间差 -
trx_state = 'RUNNING'但trx_started是 10 分钟前,说明这事务早该结束了,极可能是应用层没COMMIT或卡在外部调用里 - 配合
sys.innodb_lock_waits或performance_schema.data_locks查它是否正持有锁、阻塞别人 - 注意:某些 ORM(如 Django 的
atomic块)或连接池(如 HikariCP 的leakDetectionThreshold)未配置超时,会导致事务“隐形悬挂”
为什么 max_execution_time 对长事务无效
max_execution_time 只作用于单条 SELECT,对 UPDATE/DELETE/INSERT ... SELECT 或事务内多语句完全不生效。它不能终止事务本身,只能中断某一条语句的执行。
- 真正能限制事务生命周期的是:
innodb_lock_wait_timeout(控制等锁超时)、wait_timeout(空闲连接断开)、以及应用层显式设置的事务超时(如 Spring 的@Transactional(timeout = 30)) - MySQL 8.0.14+ 新增
lock_wait_timeout会话级变量,可比全局innodb_lock_wait_timeout更灵活,但需客户端主动设置 - 生产环境必须关闭自动提交(
autocommit = 0),否则每个语句都是独立事务,max_execution_time就失去意义
如何让长事务拆分后不漏不重、支持断点续跑
用 LIMIT + ORDER BY created_at 拆分,90% 场景会漏数据或重复更新——因为 created_at 无索引时排序不可靠,且并发写入下“第 N 批”边界模糊。
- 安全做法只有一条:基于主键/唯一索引分段,三步闭环:
SELECT MIN(id), MAX(id)→ 构造WHERE id BETWEEN x AND y AND status = 'pending'→ 执行后查ROW_COUNT(),为 0 则退出 - 每次成功处理完一个区间,必须把最大
id写入一张 checkpoint 表(如replica_split_checkpoint(last_id)),下次启动先SELECT last_id FROM replica_split_checkpoint,再从id > ?开始 - 脚本自身要带超时(单次
UPDATE超过 5 秒KILL)和重试(最多 3 次),否则某一批卡住,整个流程挂死,反而加剧延迟 - 切记:拆分逻辑必须和原始业务条件完全一致(比如
status = 'pending'),否则第二批可能误改第一批已更新过的行
最常被忽略的点是 checkpoint 的持久化时机——必须在事务内和业务 UPDATE 同批提交,不能先更新数据再写 checkpoint,否则崩溃后无法恢复;也不能写完 checkpoint 再更新,否则失败时 checkpoint 已推进,数据却没改,造成“假完成”。











