长事务会让从库“卡死”是因为堵而非慢,因SQL Thread串行回放binlog,未提交的长事务会阻塞后续所有事务执行,导致Seconds_Behind_Master飙升且Exec_Master_Log_Pos长时间不动。

长事务为什么会让从库“卡死”
不是因为慢,而是因为堵。MySQL从库的SQL Thread是串行回放binlog的,一个未提交的长事务(哪怕只是UPDATE了10万行但没COMMIT),会把后续所有事务全拦在后面。你看到的Seconds_Behind_Master飙升,本质是Exec_Master_Log_Pos长时间不动——它根本没机会执行下一个事件。
更隐蔽的是:长事务本身不一定是大DML。比如一个带子查询的UPDATE,若子表t2缺索引,每行都触发一次全表扫描,锁住间隙+行锁几十秒,就足以让其他事务排队等待。
拆分事务必须满足三个硬条件
光喊“拆小”没用,得按数据边界切,否则性能反降:
- 用主键或自增ID分段:
WHERE id BETWEEN 100001 AND 200000,避免OFFSET导致的深分页扫描 - 每次处理后显式
COMMIT,不能依赖autocommit=1自动提交(高频小事务会抬高IO压力) - 批量操作控制在500–1000条/事务,INSERT/UPDATE/DELETE都适用;超量则拆,不足也别硬凑
特别注意:ALTER TABLE这类DDL在MySQL 5.7+虽标称online,但仍是单事务、长持有MDL锁,必须单独安排窗口期,不能混在业务事务里。
哪些“看起来没事”的事务其实最危险
事务大小 ≠ SQL长度,而取决于它实际锁住的数据页数量和时间:
-
SELECT ... FOR UPDATE没加WHERE条件,锁全表;有WHERE但字段无索引,照样锁全表 - 未显式
START TRANSACTION的单条语句,在autocommit=1下仍是独立事务——高频写场景下,每秒几百个微事务,innodb_flush_log_at_trx_commit=1会把磁盘IO打满 - 业务代码里用
try...finally但忘了rollback,异常后连接归还连接池,事务却挂在InnoDB里持续不提交
查活跃长事务用这条SQL:SELECT trx_id, trx_started, TIMEDIFF(NOW(), trx_started) AS duration, trx_state, trx_query FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 30 AND trx_state = 'ACTIVE',重点盯duration > 30且trx_state = 'ACTIVE'的记录。
验证优化是否真起效的关键指标
别只刷SHOW SLAVE STATUS\G看Seconds_Behind_Master,它滞后、抖动大。直接证据有三个:
-
SHOW PROCESSLIST里SQL Thread状态是否长期卡在executing(说明还在跑单个事务) -
Relay_Master_Log_File和Exec_Master_Log_Pos的差值是否稳定收敛(说明relay log正被持续消费) - 从库开启
slow_query_log后,日志里是否不再出现执行时间超5秒的DML语句
真正卡住同步的从来不是吞吐量,而是单点阻塞。把“改完再提交”变成“改一点、提交、再改一点”,从库SQL线程才能喘气——这点在订单、金融类系统里,延迟从秒级跳到分钟级,下游补偿逻辑和人工核对成本就完全失控了。


















