结论是单条INSERT插入万级数据效率极低,应采用合并VALUES+手动事务+合理分批(500–2000行),配合调大max_allowed_packet、禁用自动提交、优化innodb_flush_log_at_trx_commit等配置,必要时改用LOAD DATA INFILE。

直接说结论:单条 INSERT 插入万级数据,基本是自找麻烦;用合并 VALUES + 手动事务 + 合理分批,能稳稳提速 5–10 倍,再配合几个关键配置调整,轻松突破瓶颈。
怎么写批量 INSERT 才不被 MySQL 拒绝
最常见错误是拼出超长 SQL,触发 max_allowed_packet 限制,报错 Packets larger than max_allowed_packet are not allowed。这不是语法错,是服务端直接截断。
- 每条
INSERT INTO ... VALUES (...), (...), (...)建议控制在 1000–2000 行以内(具体看单行数据大小,宽字段要更保守) - 用
SHOW VARIABLES LIKE 'max_allowed_packet'查当前值,默认 4MB 或 64MB,不够就调大(如SET GLOBAL max_allowed_packet = 1024*1024*256) - 字段顺序必须严格一致,
NULL和字符串混排容易引发驱动类型推断失败,尤其 Python/Java 的 ORM 或 pymysql/mysql-connector-py - 避免在
VALUES里写函数,比如NOW()、UUID()—— MySQL 会退化为逐行计算,失去批量优化效果
为什么开了事务还是慢?autocommit 没关干净
很多代码看似用了 BEGIN / COMMIT,但实际每条 INSERT 仍被自动提交,根本没进事务。根源常在连接层或 ORM 默认行为。
- 显式执行
SET autocommit = 0,再BEGIN,比只靠START TRANSACTION更可靠 - Django 用户注意:
@transaction.atomic默认生效,但若嵌套调用或手动commit()可能提前提交;Flask-SQLAlchemy 需确认db.session.commit()是最后统一调用 - 事务太大(如 5 万行一提交)会撑爆
innodb_log_buffer_size和 undo log,建议按 1000–5000 行切分,中间加COMMIT,别贪大 - 长时间未提交的事务可能被
wait_timeout中断,插入中途报MySQL server has gone away,需捕获并重试
哪些 MySQL 配置不调等于白优化
光改 SQL 写法,底层参数不匹配,性能提升会卡在 I/O 层。这几个必须动手:
-
innodb_flush_log_at_trx_commit = 0:导入期间临时设为 0(仅限离线任务),跳过每次 commit 的 redo log 强制刷盘,速度可翻 3 倍以上;恢复为 1 保数据安全 -
bulk_insert_buffer_size:专为INSERT ... VALUES设计,从默认 8MB 提到 64–256MB,对大批次插入有明显加速 -
innodb_buffer_pool_size至少设为总数据量的 50%,否则频繁磁盘换页,所有优化归零 - 如果主键是自增整数且数据按主键升序插入,B+ 树页分裂极少;反之用 UUID 或随机 ID,插入速度可能掉一半,且表碎片严重
比 INSERT 更快的方案:什么时候该换 LOAD DATA INFILE
当数据源是本地文件(CSV/TSV),且你有数据库服务器文件读取权限,LOAD DATA INFILE 是唯一比批量 INSERT 更快的原生方案,通常快 2–3 倍。
- 它绕过 SQL 解析器,直接走存储引擎层,没有网络序列化/反序列化开销
- 必须确保文件路径在 MySQL 服务端(不是客户端),且用户有
FILE权限 - 不支持参数化,字段映射靠列顺序,空值/转义需提前处理好;遇到脏数据会整批失败,不如
INSERT ... ON DUPLICATE KEY UPDATE容错强 - 线上环境若无法开通
LOCAL INFILE,可用mysqlimport命令替代,效果等同
真正卡住批量插入的,往往不是语法不会写,而是事务边界没控住、配置没对齐、或者主键无序导致 B+ 树反复分裂——这些点不排查,光拼 VALUES 数量再多也没用。


















