INSERT SELECT 卡住其他插入的主因是 InnoDB 在执行时加语句级 AUTO_INC 表锁,而非行锁或间隙锁;该锁持续至整条语句结束,导致并发 INSERT SELECT 串行化执行,且 innodb_autoinc_lock_mode=2 对含 SELECT 的语句无效。

INSERT SELECT 为什么一跑就卡住其他插入
根本不是行锁或间隙锁的问题,是 InnoDB 在分配自增值时主动升级了锁粒度。只要语句里带 SELECT(比如 INSERT INTO t SELECT * FROM s)、REPLACE SELECT 或 LOAD DATA,InnoDB 就会在执行前加语句级 AUTO_INC 表锁——整条语句结束才释放。
现象非常具体:SHOW PROCESSLIST 里一堆 Waiting for table level lock,慢日志里原本几毫秒的单条 INSERT 突然变成几百毫秒甚至秒级。
- 哪怕子查询只返回 10 行,它也会锁住目标表的自增机制
- 多个
INSERT SELECT会排队,实际是串行执行 -
REPLACE INTO t SELECT和INSERT ... ON DUPLICATE KEY UPDATE在有唯一索引冲突路径下,也可能退化为 bulk 行为,触发同样锁
innodb_autoinc_lock_mode=2 真的能解吗
设成 innodb_autoinc_lock_mode = 2 确实能让纯值插入(如 INSERT INTO t VALUES ())走轻量 mutex 分配,避开表级锁;但对含 SELECT 的语句,它照样退化为语句级锁——不是参数没生效,是设计如此。
更重要的是,mode=2 有硬依赖:必须同时满足 binlog_format = ROW + enforce_gtid_consistency = ON + gtid_mode = ON。缺一不可。
- 只改
innodb_autoinc_lock_mode而不检查SELECT @@binlog_format,返回STATEMENT就是危险操作 -
mode=2下事务回滚后 ID 不回收,空洞变多;若业务用id+1推算下一条或做分页,会漏数据 -
INSERT INTO t (id, name) VALUES (1, 'a'), (NULL, 'b')属于 mixed-mode,不享受无锁,仍会触发表级锁
不调参数,怎么从 SQL 写法上绕开
参数只是工具,真正压垮并发的常是写入模式本身。与其死磕锁,不如换写法:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 把大
INSERT SELECT拆成小批量SELECT+ 多值INSERT,每批控制在 100–500 行,显式事务包裹(BEGIN; ... ; COMMIT) - 用
INSERT INTO t (a,b) VALUES (),(),()替代循环单条,减少网络往返和解析开销 - 高冲突场景慎用
ON DUPLICATE KEY UPDATE:唯一键冲突频繁时易升间隙锁,考虑先SELECT ... FOR UPDATE再决定INSERT或UPDATE - 超大批量导入优先用
LOAD DATA INFILE:绕过 SQL 解析层,快 3–10 倍,但注意secure_file_priv路径限制和编码一致
为什么 mixed-mode 插入也危险
看起来是 simple insert,比如 INSERT INTO t (id, name) VALUES (1, 'a'), (NULL, 'b'),但它属于 mixed-mode —— 部分显式指定 ID、部分留 NULL 让 InnoDB 分配。这种写法在 innodb_autoinc_lock_mode = 1 下会被当作 bulk 处理,同样触发表级 AUTO_INC 锁。
更隐蔽的是 INSERT ... ON DUPLICATE KEY UPDATE:语法像 simple insert,但执行时若检测到唯一键冲突,内部会走 replace 路径,InnoDB 就可能升级锁。
真正安全的 simple insert 只有一种:所有值都由 InnoDB 分配,且不带 SELECT、不混用显式 ID,例如 INSERT INTO t VALUES () 或 INSERT INTO t (name) VALUES ('x'), ('y')。










