多行insert比单条循环插入锁开销更小:前者在单事务中批量加锁、统一释放,唯一索引检查一次,避免锁碎片化;后者每行独立事务,频繁加锁释放、重复索引查找,易引发锁等待与升级。

多行INSERT只持有一把行锁,不是N把
单条INSERT在InnoDB里默认是独立事务,每执行一次,就要为新插入的那行数据加一次X锁(排他锁),插入完立刻释放。1000次循环 = 1000次加锁/释放动作,锁管理器要反复登记、清理、唤醒等待线程。而INSERT INTO t VALUES (1,2), (3,4), (5,6)这种写法,整个语句在一个事务上下文中执行,InnoDB会一次性评估所有待插入行的位置,只对涉及的索引页和目标行加锁,且锁持续到语句结束——不是逐行锁再释放,而是批量预判后统一持有。
唯一索引冲突检查只做一次,不是N次
当表上有UNIQUE KEY或PRIMARY KEY时,每次单条INSERT都要查一遍索引树确认是否重复,这个过程本身就要加S锁或X锁,还可能触发间隙锁(gap lock)。而多行INSERT会把所有待插入值先收集起来,再一次性走B+树搜索路径,做合并去重和冲突判定。实测中,有唯一索引的表插入1万条数据,单条循环平均触发1.2万次索引查找,批量插入通常控制在300次以内。
锁升级风险低,不易阻塞其他DML
- 单条循环在高并发下容易形成“锁链”:事务A插第100行时被阻塞,后面99个INSERT全卡住,其他会话想更新同一范围数据就直接等超时
- 多行INSERT虽然锁持有时间略长,但它是原子性的——要么全部成功,要么全部回滚,不会出现“插了一半就卡住”的中间态
- 如果批大小控制在
1000–5000行,InnoDB一般不会触发锁升级(比如从行锁升成页锁或表锁),而单条循环在慢查询堆积时更容易被判定为“长期持有锁”,间接提高锁升级概率
事务包装后,锁生命周期完全可控
很多人忽略一点:不显式开事务的多行INSERT,仍比单条快;但加上BEGIN; ... COMMIT;后,锁才真正“收口”。原因在于:
- 未加事务时,多行INSERT仍是自动提交,锁在语句结束即释放
- 显式事务下,所有插入行的锁会一直保持到
COMMIT,避免了中间被其他事务干扰,也减少了锁状态切换次数 - 注意:
innodb_lock_wait_timeout是按事务计,不是按语句计——一个包了5000行的事务,只要没超时,锁就不会被强制释放











