单条INSERT超千行易触发max_allowed_packet错误且解析性能骤降,应拆分为500–2000行/批并包裹事务;万行以上首选LOAD DATA INFILE,其绕过SQL解析、速度快10倍。

单条INSERT语句超万行会触发max_allowed_packet报错
MySQL默认max_allowed_packet是4MB(5.7)或64MB(8.0+),但实际能塞进一条INSERT的行数远低于“万行”——因为每行值都要转义、加引号、算逗号,真实字节数增长很快。实测在UTF8mb4 + 中文字段下,1000行就可能接近1.5MB;一旦超限,直接报错:Packet for query is too large。
所以“写一条万行INSERT”不是性能问题,而是根本执行不了。别硬扛,得拆。
- 用
SELECT @@max_allowed_packet确认当前值,单位是字节 - 单次
VALUES列表建议控制在500–2000行之间,视字段长度动态调整(比如全是INT可稍多,含TEXT/BLOB必须更少) - 拆分时按字节估算比按行数更可靠:拼好SQL后用
LENGTH()或Python的len(sql.encode('utf8'))预判
VALUES列表过长会导致SQL解析变慢甚至OOM
MySQL解析器对超长VALUES列表的处理不是线性的。当单条语句超过约2000行,语法树构建开销明显上升,某些版本还会在prepare阶段卡顿,甚至触发Out of memory(尤其在低配容器环境)。
这不是配置能完全解决的问题,是语法层面的设计限制。官方文档也明确建议避免单语句插入“very large number of rows”。
- 不要试图调高
sort_buffer_size或tmp_table_size来硬撑长VALUES - 优先选择分批+事务封装,而不是堆单条SQL
- 如果必须生成大SQL(如离线导出脚本),用
mysqldump --skip-extended-insert反向参考其分块逻辑
替代方案:LOAD DATA INFILE比长INSERT快10倍以上
真要插万行以上,LOAD DATA INFILE是唯一合理选择。它绕过SQL解析、参数绑定、权限逐行检查等全部客户端路径,数据直接由Server端读文件、切分、批量写入引擎层。
注意它不走网络协议,是Server本地文件读取(除非用LOAD DATA LOCAL INFILE,此时文件从客户端传入,需额外开启local_infile=1)。
- 文件格式必须严格:字段用
\t或指定分隔符,行用\n,字符串需加引号并转义 - 执行前确保
secure_file_priv允许该路径,或改用LOCAL模式(需客户端和服务端都启用) - 示例:
LOAD DATA LOCAL INFILE '/tmp/batch.csv' INTO TABLE user FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n' (name, age, phone);
容易被忽略的关键点:autocommit和事务边界
即使你把万行拆成20条500行的INSERT,如果每条都自动提交,性能依然很差——因为每条都在刷redo log、sync磁盘、更新buffer pool链表。
真正起效的是把这批拆分后的语句包进一个事务里,最后统一COMMIT。但事务也不能无限大:InnoDB的undo log会膨胀,锁持有时间拉长,主从延迟加剧。
- 推荐每5000–50000行提交一次,具体看服务器内存和binlog压力
- 不要在事务里混杂SELECT或UPDATE,避免锁升级和间隙锁扩散
- 如果表有唯一索引冲突风险,
INSERT IGNORE或ON DUPLICATE KEY UPDATE会额外增加索引查找开销,慎用


















