直接 INSERT INTO VALUES 卡死因每条语句默认独立事务,10万次引发频繁redo写盘、B+树分裂、锁竞争与日志阻塞;应改用批量VALUES+禁用autocommit+分批提交,并关闭unique_checks和foreign_key_checks。

为什么直接 INSERT INTO VALUES 会卡死?
因为每条 INSERT INTO ... VALUES (...) 默认是独立事务,10 万次就是 10 万次日志写入、锁竞争和事务开销。尤其在 InnoDB 下,频繁提交会触发大量 redo log 刷盘和索引 B+ 树分裂,CPU 和 I/O 都会打满。
常见现象:Lock wait timeout exceeded、MySQL server has gone away、客户端长时间无响应。
- 单条插入耗时通常 >10ms(含网络+解析+执行+提交),10 万条 ≈ 16 分钟,远超默认
wait_timeout(通常 28800 秒但连接可能提前中断) - 事务日志(redo log)可能填满 active log space,导致写阻塞
- 唯一索引或外键检查逐条进行,无法批量跳过中间校验
用 INSERT INTO ... VALUES (...), (...), (...) 批量拼接
这是最简单有效的提速方式,把多行数据合并进一条语句,大幅减少 SQL 解析、权限校验和事务开销。
实操建议:
- 每批控制在 1000–5000 行之间(取决于单行数据长度;总 SQL 长度别超
max_allowed_packet,默认 4MB) - 避免在循环里拼字符串累加,改用数组收集后
join()或StringBuffer构建 - 示例(Python + PyMySQL):
values = [(1,'a'),(2,'b'),(3,'c')] sql = "INSERT INTO t (id, name) VALUES " + ",".join(["%s"] * len(values)) cursor.execute(sql, values)
- 注意:所有值必须统一类型,NULL 要显式写成
None(Python)或NULL(SQL 字符串中)
配合禁用自动提交 + 手动 COMMIT 控制事务粒度
即使用了批量 VALUES,如果每次 execute 后都自动 commit,还是等于 100 次事务(假设分 100 批)。必须关掉 autocommit,自己控制 commit 时机。
- MySQL 连接初始化时设
autocommit=False(PyMySQL/MySQLdb)或connection.autocommit(False) - 每 5000–10000 行 commit 一次:太小 → 事务开销没降够;太大 → rollback 成本高、锁持有太久
- 务必在 finally 块里
connection.rollback(),防止异常后连接卡在未提交状态 - 注意:
SET autocommit = 0是会话级,但 ORM(如 SQLAlchemy)可能覆盖它,优先用驱动原生接口
其他关键优化点(容易被忽略)
光靠拼 SQL 和调事务还不够,下面几项不处理,依然可能卡住或失败。
- 临时关闭唯一性检查:
SET unique_checks = 0(导入完记得设回 1),否则每行都走唯一索引查找 - 暂停外键约束:
SET foreign_key_checks = 0,适用于你确定数据合规的场景 - 确认
innodb_log_file_size足够大(建议 ≥1GB),否则小 log file 会频繁 checkpoint,拖慢写入 - 如果表有二级索引,考虑先删索引、插完再重建(
DROP INDEX+CREATE INDEX),比边插边维护快 3–5 倍 - 不要用
LOAD DATA INFILE除非文件在数据库服务器本地——它虽最快,但权限和路径限制多,且无法做应用层数据转换
批量插入不是“拼得越多越好”,而是要在单语句长度、事务大小、日志压力、索引维护成本之间找平衡点。最容易被忽略的是 unique_checks 和 foreign_key_checks 这两个会话变量,它们默认开启,会在每一行都触发额外的索引扫描,10 万行下性能折损常超 40%。










