单条insert慢是因每条都触发完整事务流程(刷盘、写binlog、校验、索引更新),优化需三步:①改用insert...values(...),(...)批量写入;②显式begin/commit关闭autocommit;③大数据量优先用load data infile。

为什么单条 INSERT 会慢得离谱
MySQL 默认每条 INSERT 都是一次独立事务,意味着每次都要刷盘、写 binlog、校验约束、更新索引。哪怕表只有几列,1000 条单条插入可能耗时数秒——不是网络或 CPU 拖累,是磁盘 I/O 和事务开销堆出来的。
常见错误现象:INSERT INTO t VALUES (1,'a'); INSERT INTO t VALUES (2,'b'); 这种写法在应用层循环执行,SHOW PROCESSLIST 里能看到大量 Query 状态卡住;监控里 innodb_log_waits 或 slow_queries 明显上升。
- InnoDB 表必须走聚簇索引,每插一行都可能触发页分裂
- 没显式开启事务时,autocommit=1 强制每条语句自成事务
- 客户端驱动(比如 Python 的
pymysql)默认不批量,execute()调用一次只发一条
用 INSERT ... VALUES (...), (...) 批量写入
这是最简单见效的优化,把多行数据塞进一条 SQL,减少网络往返和解析开销。MySQL 官方建议单条 INSERT 不超过 1000 行,实际要看平均行大小和 max_allowed_packet 设置。
使用场景:导入中间结果、定时批处理、后台任务生成数据。
-
max_allowed_packet必须 ≥ 单条批量 SQL 字节数,否则报错Packets larger than max_allowed_packet are not allowed - 别一次性塞 10 万行——容易 OOM 或锁表太久,拆成 500~2000 行/批更稳
- 字段顺序、NULL 处理、字符串转义必须严格一致,否则
Column count doesn't match value count - 示例:
INSERT INTO logs (uid, action, ts) VALUES (1,'login','2024-01-01'), (2,'logout','2024-01-01'), (3,'view','2024-01-01');
显式事务 + 关闭 autocommit 是硬要求
光靠批量 SQL 不够。如果还在 autocommit=1 下执行,每条批量 INSERT 仍是一次事务,性能提升有限。必须手动 BEGIN / COMMIT 包裹多条批量语句。
参数差异:innodb_flush_log_at_trx_commit=1(默认)保证崩溃安全但最慢;设为 2 可大幅提升吞吐,代价是极端断电可能丢 1 秒数据——多数业务可接受。
- 应用层要确保
commit()成功才认为写入完成,不能只看 execute() 返回 - 事务太大(如 10 万行)会导致
innodb_lock_wait_timeout超时,或撑爆innodb_log_file_size - 避免在事务中混杂 SELECT FOR UPDATE 或长查询,会延长锁持有时间
- Python 示例:
conn.autocommit = False; cursor.execute("INSERT ..."); cursor.execute("INSERT ..."); conn.commit()
LOAD DATA INFILE 比 INSERT 快一个数量级
当数据已存在本地文件或能导出成文本,LOAD DATA INFILE 是 MySQL 原生最快的写入方式——它绕过 SQL 解析层,直接走存储引擎接口,速度通常是批量 INSERT 的 5~20 倍。
使用场景:ETL 导入、日志归档、离线报表初始化。
- 文件需在 MySQL 服务端可读,或加
LOCAL关键字(需客户端和服务端都开启local_infile) - 字段分隔符、行结束符、NULL 标识必须和文件严格匹配,否则整批失败,报错像
Incorrect integer value: '' for column 'id' - 目标表最好禁用唯一索引和外键约束(
SET FOREIGN_KEY_CHECKS=0,ALTER TABLE t DISABLE KEYS),导入完再启用 - 示例:
LOAD DATA LOCAL INFILE '/tmp/data.csv' INTO TABLE logs FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n' (uid,action,@ts) SET ts = STR_TO_DATE(@ts, '%Y-%m-%d %H:%i:%s');
innodb_buffer_pool_size 是否足够缓存热数据——这些地方一松动,快写的收益就打折扣。











