单条INSERT VALUES建议每批500–2000行,对应SQL长度100KB–2MB,需规避max_allowed_packet限制、解析耗时及连接池超时;MyBatis需分段提交而非仅设BATCH模式;并发插入须应用层去重、ID预分配、每批独立事务;关键配置需调优innodb_flush_log_at_trx_commit=2、bulk_insert_buffer_size≥64M、max_allowed_packet≥256M。

单条 INSERT VALUES 能塞多少行才不超时
MySQL 本身不限制 VALUES 后面写几组数据,但实际能塞多少,取决于三个硬性限制:网络传输上限、SQL 解析耗时、服务端配置。最常卡住的是 max_allowed_packet(默认 64MB),一旦拼出的 SQL 超过这个值,直接报错 Packets larger than max_allowed_packet are not allowed;其次是 JDBC 驱动或连接池的 queryTimeout,比如 HikariCP 默认 30 秒,拼了 10 万行进去,解析+执行很容易超。
实操建议:
- 每批控制在
500–2000行之间,对应 SQL 长度约100 KB – 2 MB,兼容大多数配置 - 上线前用
SELECT LENGTH(?)测下拼出来的 SQL 字节数,别只数行数 - 如果必须插更多,优先考虑
LOAD DATA INFILE,它绕过 SQL 解析层,快一个数量级
MyBatis 批量插入超时,光设 ExecutorType.BATCH 不够
很多人以为只要 openSession(ExecutorType.BATCH) 就万事大吉,结果还是超时——因为 MyBatis 的 BATCH 模式只是把 SQL 缓存在内存里,真正发出去前仍要拼成一条巨长的 INSERT INTO ... VALUES (),(),()...。如果 list 有 5 万条,它就真给你拼 5 万个括号,不超时才怪。
关键动作是「分段提交」:
- 循环中每累计
500条调一次mapper.insert(),然后手动session.flushStatements() - 避免用
<foreach></foreach>一次性生成超长 SQL,改用 Java 层分批构造INSERT语句 - 确认数据源配置里
queryTimeout设到了足够值(如600秒),否则连接池先杀请求
并发批量插入时主键冲突和锁等待怎么破
高并发下多个线程同时跑批量插入,哪怕每批都用事务包着,也容易在唯一索引字段(比如 order_no)上抢锁,出现 Lock wait timeout exceeded 或死锁。这不是“加个索引”能解决的,得从数据源头控重和 ID 分配逻辑入手。
必须做的三件事:
- 应用层去重:插入前用
Map<string object></string>或Set<string></string>过滤重复的业务唯一键(如device_id),别指望数据库报错再 catch - 自增 ID 预分配:不用
SELECT MAX(id)(会串行化),改用REPLACE INTO或提前申请 ID 段(如INSERT INTO seq VALUES ()+LAST_INSERT_ID()) - 每批独立事务:
START TRANSACTION; INSERT ... ; COMMIT;包住整批,不要跨批共用一个事务,否则一个失败全滚,重试成本爆炸
哪些 MySQL 配置不调,批量插入永远快不起来
就算 SQL 写得再好,innodb_flush_log_at_trx_commit=1 和默认的 bulk_insert_buffer_size=8M 也会把你拖回石器时代。这些参数不是“可选优化项”,而是批量写入的基础设施。
上线前必查:
-
innodb_flush_log_at_trx_commit=2:牺牲极小概率的崩溃丢失(非 0),换取日志刷盘性能翻倍 -
bulk_insert_buffer_size调到64M或更高(需配合可用内存),专为INSERT ... SELECT和多值INSERT加速 -
max_allowed_packet至少设为256M,否则拼大 SQL 直接被拒 - 批量前临时
ALTER TABLE t DISABLE KEYS,插完再ENABLE,对 MyISAM 有效,InnoDB 无效但无害











