主从复制本身不会发生死锁,死锁只出现在从库本地并发执行时;SQL线程重放relay log本质是普通DML事务,若与业务连接、监控脚本或并行Worker线程加锁顺序冲突,便会触发InnoDB死锁并报ERROR 1213。

主从复制中不会发生“死锁回滚报错”——这是个常见误解。MySQL 主从复制本身是单向、串行的(从库 SQL 线程按 relay log 顺序重放),不存在事务间循环等待,因此 SHOW ENGINE INNODB STATUS 中看到的死锁,一定发生在从库本地执行 SQL 时,而非复制机制导致。
为什么从库会报死锁错误(如 ERROR 1213)
从库 SQL 线程在重放 relay log 时,本质就是在一个事务里执行普通 DML。如果该语句触发了与从库上其他并发连接(比如业务读写、监控脚本、定时任务)的锁冲突,就可能真死锁。
- 典型场景:从库开了写权限,业务直接连从库做
UPDATE或INSERT ... ON DUPLICATE KEY UPDATE,恰好和 SQL 线程重放的同表语句形成反序加锁 - 另一个高发点:从库启用了
log_slave_updates=ON,且自身又作为另一台从库的主库,形成级联复制。此时从库既是消费者又是生产者,锁竞争面扩大 - 间隙锁干扰:主库在
REPEATABLE READ下执行范围SELECT ... FOR UPDATE,从库重放时若索引缺失或查询条件未命中索引,会升级为全表扫描+间隙锁,加剧冲突
如何确认死锁是否来自复制线程
关键看 SHOW ENGINE INNODB STATUS 输出中事务的来源标识:
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
- 真正由 SQL 线程触发的事务,
MySQL thread id通常为1(或固定小数值),且query id显示为0,OS thread handle对应slave_sql线程 - 如果是业务连接引发的冲突,
thread id是普通连接 ID(如 127、345),query id非零,query字段能看到具体业务 SQL - 务必检查
LATEST DETECTED DEADLOCK段里两个事务的query是否都含UPDATE/INSERT,且其中一个明显是复制重放语句(如UPDATE t1 SET x=1 WHERE id=123来自 relay log)
排查时必须检查的三个配置项
这些配置直接影响从库锁行为和死锁概率:
-
innodb_lock_wait_timeout:默认 50 秒。从库若设得过小(如 5 秒),轻微阻塞就会被误判为“锁等待超时”,日志报Lock wait timeout exceeded,但它不是死锁。需和ERROR 1213严格区分 -
transaction_isolation:从库若设为REPEATABLE READ(默认),间隙锁活跃;而主库用READ COMMITTED,会导致从库重放时锁范围变大。建议主从隔离级别保持一致 -
slave_parallel_workers> 0:开启并行复制后,多个 worker 线程可能并发操作同一张表,即使主库顺序执行,从库也可能因并行打乱顺序,人为制造反序加锁。查SHOW PROCESSLIST看是否有多个Slave_worker在跑
快速定位和临时缓解步骤
别急着改代码,先用最小动作确认问题范围:
- 立即执行
SHOW SLAVE STATUS\G,确认Seconds_Behind_Master是否持续增长,SQL_Delay是否非零——延迟本身会拉长锁持有时间 - 运行
SELECT * FROM performance_schema.events_waits_summary_by_instance WHERE EVENT_NAME LIKE 'wait/synch/mutex/innodb/%' AND COUNT_STAR > 1000;,看 innodb 行锁等待是否异常高 - 临时停掉所有非必要连接:
KILL掉除system user(SQL 线程)和replication(IO 线程)外的所有连接,再观察是否还有死锁。如果消失,说明是外部写入干扰 - 最稳妥的临时方案:
STOP SLAVE; SET GLOBAL slave_parallel_workers = 0; START SLAVE;切回单线程复制,排除并行引入的不确定性
真正棘手的点在于:死锁现场稍纵即逝,而 SHOW ENGINE INNODB STATUS 只保留最后一次记录。线上必须提前打开 innodb_print_all_deadlocks = ON,否则你永远不知道上周五凌晨三点到底发生了什么。

















