单条INSERT在高并发下拖垮MySQL,因其默认为独立事务,引发频繁redo日志刷盘、行锁争用、逐行索引更新及大量网络往返,导致I/O与锁等待激增,QPS受限。

为什么单条INSERT在高并发下会拖垮MySQL
因为每条 INSERT 默认是一次独立事务,涉及日志刷盘、行锁/表锁争用、索引逐条更新。并发一高,磁盘I/O和锁等待直接爆炸,QPS卡在几百就上不去。
典型现象:show processlist 里一堆 update 或 insert 状态卡住;SHOW ENGINE INNODB STATUS 显示大量 lock wait timeout exceeded;慢查询日志里全是单条 INSERT。
- 即使开了
autocommit=1,每条仍触发一次redo log刷盘(除非用组提交+足够快的磁盘) - InnoDB对主键或唯一索引冲突检查是逐行做的,多条一起插反而能批量校验
- 网络往返次数翻倍——1000条单插 = 1000次TCP包,而1条多值插最多2~3次
怎么写真正的多行INSERT语句
不是简单拼SQL字符串,关键在语法合规、长度可控、避免超限。
正确写法:INSERT INTO t (a,b,c) VALUES (1,2,3), (4,5,6), (7,8,9); —— 注意逗号分隔,不是分号;括号必须完整;字段名只写一次。
- 单条语句最多插多少行?看
max_allowed_packet(默认4MB),实际建议单批 ≤ 1000 行,否则容易触发Packets larger than max_allowed_packet are not allowed - 所有值必须类型一致,比如不能混用
NULL和空字符串,否则可能触发隐式转换+全表扫描 - 如果含
ON DUPLICATE KEY UPDATE,要确保冲突字段有唯一索引,否则变成全表扫描更新
什么时候必须拆分批次而不是硬塞一大条
不是越长越好。拆分是为了绕过MySQL内部限制和降低锁粒度。
必须拆的情况:max_allowed_packet 不够、事务太大导致undo log暴涨、主从延迟飙升、插入过程中某一行报错导致整条失败。
- 每批控制在 500~1000 行较稳妥;用
INSERT ... SELECT时更要小心,它可能锁住源表或触发临时表 - 别用
LOAD DATA INFILE替代——它虽快但需要文件权限,且无法做应用层数据清洗或条件过滤 - 拆分后记得加
START TRANSACTION+COMMIT包裹每批,否则每条又变自动提交,失去批量意义
MyISAM和InnoDB在批量插入时表现差异在哪
别迷信引擎切换。MyISAM看似支持 INSERT DELAYED,但5.6之后已废弃,且不支持事务、崩溃后易损坏。
InnoDB才是高并发插入的现实选择,但得配对优化:
- 关掉唯一键检查?不行。
SET UNIQUE_CHECKS=0只对后续CREATE INDEX有效,对INSERT无效 - 关外键检查?可以,但仅限导入阶段:
SET FOREIGN_KEY_CHECKS=0,完事后务必开回来 - 真正有效的:调大
innodb_log_file_size(减少刷盘频率)、用innodb_flush_log_at_trx_commit=2(牺牲一点持久性换吞吐)
最常被忽略的一点:批量插入前没预估好自增ID分配。如果用 REPLACE INTO 或 INSERT ... ON DUPLICATE KEY UPDATE,自增值仍会跳变,可能让下游依赖ID顺序的逻辑出错。










