多值INSERT性能瓶颈在于每批仍需完整SQL解析、优化及隐式提交导致I/O与锁竞争;建议分批提交、禁用约束、优先用LOAD DATA INFILE,其绕过SQL层,速度提升5–20倍。

INSERT ... VALUES 多值插入的性能瓶颈在哪
直接用 INSERT INTO t (a,b) VALUES (1,2), (3,4), (5,6) 批量插几十或几百行没问题,但到十万级以上就明显变慢。根本原因是每条 INSERT 语句仍需走完整解析、优化、事务日志写入流程;MySQL 默认 autocommit=1,意味着每批都隐式提交,磁盘 I/O 和锁竞争会成为瓶颈。
实操建议:
- 把多值
INSERT拆成每 1000–5000 行一批(具体看单行数据大小),避免单条语句超长触发max_allowed_packet限制 - 显式加
BEGIN; ... COMMIT;包裹整批导入,关闭 autocommit - 禁用唯一索引和外键约束(
SET FOREIGN_KEY_CHECKS = 0;,ALTER TABLE t DISABLE KEYS;),导入完成后再启用
LOAD DATA INFILE 是百万级导入的首选方案
LOAD DATA INFILE 是 MySQL 原生批量导入机制,绕过 SQL 解析层,直接读取文本逐行构造记录,速度通常是 INSERT 的 5–20 倍。它要求文件在数据库服务器本地(或启用 LOCAL 模式)。
常见错误现象:ERROR 1148 (42000): The used command is not allowed with this MySQL version —— 多因服务端未开启 local_infile 或客户端未启用 --local-infile=1。
实操建议:
- 导出源数据为 tab 分隔的纯文本(避免逗号/换行干扰),字段用
\N表示 NULL - 执行前确认权限:
GRANT FILE ON *.* TO 'user'@'%'; - 命令示例:
LOAD DATA INFILE '/var/lib/mysql-files/data.tsv' INTO TABLE my_table FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n' IGNORE 1 LINES;
用 INSERT ... SELECT 替代循环 INSERT
当数据已存在于另一张表(或可构造临时表),INSERT INTO t1 SELECT * FROM t2 WHERE ... 是零拷贝式批量写入,不经过客户端中转,适合 ETL 场景。
性能关键点:
- 目标表最好无活跃事务或长查询,否则
SELECT可能被阻塞 - 若源表很大,加
LIMIT分批次(如每次 5 万行),配合WHERE id BETWEEN ? AND ?避免全表扫描 - 注意字符集和列类型兼容性,否则报错
Incorrect string value或截断
Python/Pandas 写入时别直接用 to_sql(..., if_exists='append')
Pandas to_sql 默认每行发一条 INSERT,百万行就是百万次网络往返+SQL 解析,极慢。即使设了 chunksize=10000,底层仍是多条 INSERT,没解决核心问题。
更优路径:
- 用
df.to_csv(..., index=False, header=False)导出为本地 TSV 文件 - 再调用
mysql -u user -p -e "LOAD DATA INFILE ..."或用 PyMySQL/MySQLdb 执行LOAD DATA语句 - 若必须走 ORM,考虑 SQLAlchemy 的
bulk_insert_mappings(),但仍有开销,不如原生 LOAD
实际导入百万级数据,最易被忽略的是:服务器磁盘 I/O 能力和 innodb_log_file_size 配置。日志文件太小会导致频繁 checkpoint,拖慢 LOAD DATA;SSD 和 HDD 的吞吐差距可达 10 倍。先测单线程导入速度,再决定是否并行分片——盲目并发可能让 I/O 成瓶颈。

















