自增锁本质是保证并发插入时id不重复、不回退的轻量互斥机制;默认mode=1下纯insert values仅抢短mutex,但insert select等批量语句会持表级锁至执行结束,导致高并发阻塞。

自增锁不是行锁,但会卡住所有INSERT
自增主键本身不慢,锁竞争的根源在于InnoDB必须保证多个并发事务拿到**不重复、不回退**的ID。只要涉及AUTO_INCREMENT列的写入,InnoDB就要介入分配——这个过程默认带同步控制,哪怕你只插一行 INSERT INTO t VALUES (NULL, 'x'),也得抢一个轻量互斥锁(mode=1)或等表级锁释放(mode=0/1下的批量语句)。
常见错误现象:
-
SHOW PROCESSLIST里大量连接卡在Waiting for table level lock,但表上没其他长事务 - 单条
INSERT在压测中从 2ms 跳到 800ms+,EXPLAIN完全正常,说明不是执行计划问题 - 监控看到
innodb_autoinc_lock相关函数 CPU 占用飙升,而磁盘IO、网络延迟都平稳
哪些INSERT会触发最重的表级自增锁?
不是所有 INSERT 都一样。真正让并发崩掉的是那些需要**预估行数、保证连续ID段**的语句,它们在 innodb_autoinc_lock_mode = 1(默认)下会直接升级为语句级表锁,直到整条语句执行完才释放。
这些语句包括:
INSERT INTO t SELECT * FROM s WHERE ...REPLACE INTO t SELECT ...LOAD DATA INFILE-
INSERT INTO t VALUES (1, 'a'), (NULL, 'b'), (3, 'c')(Mixed-mode:部分显式指定ID)
注意:INSERT INTO t VALUES (NULL, 'a'), (NULL, 'b') 这种纯隐式插入,在 mode=1 下只抢轻量互斥锁,很快释放;但只要混进一个显式ID或SELECT,整条语句就变重。
设成 innodb_autoinc_lock_mode = 2 真的就没锁了?
不是“全没锁”,而是“只对特定写法免锁”。mode=2 的本质是放弃连续性换并发:它对 INSERT INTO t VALUES () 类语句彻底跳过锁,靠预分配 ID 段(比如一次拿 32 个)实现无锁并发;但对上面列出的 Bulk/Mixed 插入,依然要加语句级锁。
必须同步满足的条件:
-
SELECT @@binlog_format必须返回ROW,否则主从复制可能丢数据或报错 -
SET GLOBAL innodb_autoinc_lock_mode = 2是会话级生效,重启失效,必须写进my.cnf的[mysqld]段才持久 - ID空洞不可逆:事务回滚后预分配的ID不回收,
id可能从 1001 直接跳到 1033
绕过自增锁的终极办法:别让MySQL生成ID
参数调优有边界,真正治本是把ID生成逻辑移出数据库。用应用层生成 UUID 或 snowflake ID,然后走 INSERT INTO t (id, ...) VALUES ('xxx', ...) ——InnoDB 当作普通值插入,完全不触发 AUTO_INCREMENT 机制,自然没有自增锁这回事。
适用场景:
- 订单号、日志ID、设备上报记录等不需要连续数字的业务主键
- 分库分表架构中,各节点不再依赖本地自增,避免全局ID冲突
- 已有表改造困难时,可新增
external_id字段作业务主键,原id仅作内部索引
最容易被忽略的一点:改了 innodb_autoinc_lock_mode 却没查 binlog_format,或者误以为所有 INSERT 都受益于 mode=2,结果线上还是卡——其实你压测跑的那条语句,根本不在 mode=2 的免锁范围内。











