自增锁在主从复制中引发死锁,是因为从库并行复制时多个工作线程并发竞争同一张表的表级自增锁,与行锁形成“持有自增锁→等待行锁”或“持有行锁→等待自增锁”的循环等待链,尤其在innodb_autoinc_lock_mode=1且执行INSERT...SELECT时尤为高发。

为什么自增锁会在主从复制中引发死锁
自增锁(AUTO-INC lock)本身不是行锁,而是一种表级轻量锁,用于保证 INSERT ... SELECT、批量插入等语句获取连续、不重复的自增值。问题出在:当从库开启并行复制(slave_parallel_workers > 0)且存在并发写入(哪怕只是监控脚本或误连应用执行了 INSERT),多个 Worker Thread 或 SQL Thread 与用户事务同时竞争同一张表的自增锁,就可能形成“持有自增锁 → 等待行锁”或“持有行锁 → 等待自增锁”的循环链。
典型现象是:SHOW PROCESSLIST 中大量连接卡在 executing 状态,SHOW ENGINE INNODB STATUS\G 的 LATEST DETECTED DEADLOCK 区块里出现 setting auto-inc lock 和 waiting for table metadata lock 或 waiting for lock 混合出现;Seconds_Behind_Master 突然飙升或卡住不动。
确认是否为自增锁死锁而非普通行锁死锁
不能只看 Last_SQL_Error 是否含 Deadlock found——自增锁冲突有时不报该错误,而是表现为长时间阻塞。必须交叉验证:
- 查
SHOW ENGINE INNODB STATUS\G,定位TRANSACTIONS部分,找状态为ACTIVE且lock struct(s)明确含AUTO-INC的事务 - 运行
SELECT * FROM performance_schema.threads WHERE THREAD_ID IN (SELECT TRX_MYSQL_THREAD_ID FROM information_schema.INNODB_TRX WHERE TRX_STATE = 'LOCK WAIT');,确认等待线程是否属于replication_applier类型 - 检查
SELECT * FROM performance_schema.replication_applier_status_by_worker\G,若某个WORKER_ID的LAST_ERROR_MESSAGE为空但LAST_APPLIED_TRANSACTION停滞,且对应线程在INNODB STATUS中显示正在setting auto-inc lock,基本可断定
关闭自增锁争用的三个硬性操作
自增锁死锁本质是并发模型与锁粒度不匹配,修复需从源头切断竞争路径:
-
SET GLOBAL read_only = ON:禁用所有非 SUPER 用户写入,但注意这不拦 SUPER —— 必须紧接着执行SET GLOBAL super_read_only = ON - 将从库的
innodb_autoinc_lock_mode设为2(交错模式):它允许INSERT ... VALUES并发获取自增值,不阻塞其他 INSERT;但要求 binlog_format =ROW(主从都必须满足) - 停用
INSERT ... SELECT类语句在从库执行:这类语句强制使用传统表级自增锁(innodb_autoinc_lock_mode = 1时),应改用批量INSERT VALUES+ 应用层生成 ID,或拆分为SELECT+ 多条单行INSERT
并行复制下自增锁的兼容性陷阱
MySQL 8.0+ 的 WRITESET 并行策略默认不感知自增锁,Worker 之间仍可能因插入同一张表而撞上 AUTO-INC 锁。关键点在于:
-
slave_parallel_type = LOGICAL_CLOCK(即COMMIT_ORDER)比WRITESET更安全:它按提交顺序调度 Worker,天然降低同表并发插入概率 - 若必须用
WRITESET,需确保表有显式主键或非空唯一键(否则 WRITESET 哈希失效,退化为单线程回放) -
slave_preserve_commit_order = ON必须开启:否则即使启用了并行,Worker 提交顺序混乱也会加剧自增锁排队效应
最易被忽略的是:innodb_autoinc_lock_mode = 2 在从库生效的前提是主库也设为此值,且主从 binlog 格式一致——否则主库生成的自增值序列无法被从库安全复现,反而引发数据不一致或更隐蔽的锁等待。


















