自增锁本质是串行化“获取并推进自增值”动作,防止并发分配重复ID;它不保证主键唯一(由索引和约束兜底),也不锁整行数据,仅控制自增计数器的原子递增。

自增锁不是为了“保证主键唯一”,而是防止ID重复分配
MySQL 的 AUTO_INCREMENT 列本身由索引和唯一约束兜底,真正需要锁的环节是「分配下一个可用 ID」这个动作。多个事务如果同时读取当前最大 ID、+1、写入,不加控制就会撞车。自增锁(AUTO_INC lock)干的就是这件事:它不锁整张表的数据,只串行化「获取并推进自增值」这一小段逻辑。
innodb_autoinc_lock_mode=1 时,普通 INSERT 为什么看起来“不锁表”?
因为实际用的是内存互斥量(mutex),不是传统表级 X 锁。事务 A 获取 ID 1001 后立刻释放 mutex,B 就能紧接着拿 1002 —— 这个过程快、轻量,但代价是 ID 可能不连续(比如 A 拿了 1001 后 rollback,B 已经拿了 1002,1001 就空缺了)。
-
INSERT INTO t (name) VALUES ('a')属于 simple insert,走 mutex 路径 -
INSERT INTO t SELECT ...属于 bulk insert,即使innodb_autoinc_lock_mode=1也必须持表级AUTO_INC锁直到语句结束 - 若事务里混用
INSERT和INSERT SELECT,可能让自增 ID 分配行为变得不可预测
为什么 INSERT SELECT 容易卡住甚至死锁?
它本质是两阶段操作:先对源表加 next-key lock(比如扫描 t2 的所有行),再向目标表申请自增 ID。两个并发事务执行相同语句时,可能出现 A 持着 t2 的行锁等 t1 的自增锁,B 持着 t2 的行锁等 t1 的自增锁 —— 形成循环等待。
- 现象:
SHOW ENGINE INNODB STATUS里看到*** (1) WAITING FOR THIS LOCK TO BE GRANTED: AUTO_INC和*** (2) HOLDS THE LOCK(S): index `PRIMARY` of table `db`.`t2`并列 - 解决方向不是“加更多索引”,而是避免在高并发路径上用
INSERT INTO t1 SELECT ... FROM t2 - 真要批量导入,优先考虑应用层分页 + 批量单行
INSERT,或临时切到innodb_autoinc_lock_mode = 2(仅限 ROW 格式复制)
REPLACE 和 INSERT ON DUPLICATE KEY UPDATE 对自增锁的影响差异很大
REPLACE 是隐式 DELETE + INSERT,哪怕最终因唯一键冲突回滚,自增 ID 也已被消耗;而 INSERT ON DUPLICATE KEY UPDATE 只有真正插入新行时才申请 ID —— 这是它更安全的核心原因。
-
REPLACE INTO t VALUES (NULL, 'x'):每次执行都推进自增计数器,ID 快速耗尽 -
INSERT INTO t VALUES (NULL, 'x') ON DUPLICATE KEY UPDATE name='x':冲突时只更新,不申请新 ID - 注意:如果表有多个唯一索引,冲突检测顺序会影响是否触发插入,进而影响 ID 分配
实际调优时,最常被忽略的是 innodb_autoinc_lock_mode 和 binlog_format 的耦合关系:设成 2 却用 STATEMENT 复制,从库会丢数据。这个组合错误不会报错,但后果严重。


















