真正提速的关键是绕过高频小事务、减少日志和索引实时开销;必须配合显式事务(如SET autocommit=0 + COMMIT)、临时禁用约束索引,并使用原生批量接口(如MySQL rewriteBatchedStatements、PostgreSQL COPY),而非依赖ORM默认行为。

直接用单条 INSERT INTO ... VALUES (...) 循环插入几千行,性能会断崖式下跌;真正提速的关键不是“一次插多少”,而是绕过高频小事务、减少日志和索引实时开销。
多值INSERT必须配事务,否则白优化
只把多行塞进一个 VALUES 里还不够。MySQL/PostgreSQL/SQL Server 默认 autocommit = 1,每条语句仍是独立事务,redo log 或 WAL 还是刷个不停。
- MySQL:执行
SET autocommit = 0;后再批量插入,最后COMMIT;;或用BEGIN; ... COMMIT; - PostgreSQL:必须显式
BEGIN;+COMMIT;,不能依赖隐式事务 - SQLite:用
BEGIN IMMEDIATE;避免写冲突,比BEGIN DEFERRED;更早加锁 - 容易踩的坑:
COMMIT忘写会导致连接挂起、锁表、后续查询阻塞;事务太大(如 50 万行)可能撑爆innodb_log_file_size或 undo 表空间
每批多少行?别硬背数字,看参数和数据
批次大小不是固定值,它受协议限制、内存和日志容量共同约束。
- MySQL:单条语句最多约 65535 个参数(不是行数),但实际受
max_allowed_packet限制;运行时查SELECT @@max_allowed_packet,按 1000–2000 行一组更稳妥 - PostgreSQL:
VALUES行数无硬上限,但计划器在几千行后可能变慢,建议 ≤ 5000 行/批 - SQL Server:单语句超 1000 行易触发参数绑定失败,且 T-SQL 解析压力陡增;优先走
SqlBulkCopy或BULK INSERT - SQLite:默认
SQLITE_MAX_VARIABLE_NUMBER=999,必须分段;可用PRAGMA compile_options确认
索引和约束不是摆设,导入前该关就得关
每插一行就校验外键、更新二级索引、跑触发器,等于把批量操作退化成逐行处理。
- MySQL:导入前
ALTER TABLE t DISABLE KEYS;(仅对 MyISAM 有效),InnoDB 推荐SET FOREIGN_KEY_CHECKS = 0;+ 手动删重建索引 - PostgreSQL:用
SET CONSTRAINTS ALL DEFERRED;延迟到事务末尾校验,或建表时加NOT VALID约束,导入后再VALIDATE CONSTRAINT - SQL Server:
ALTER INDEX IX_Name ON t DISABLE;,导入后REBUILD;别忘了检查是否含UNIQUE或CHECK约束,它们无法禁用,只能临时删掉 - 注意:操作完必须恢复并验证一致性,否则后续查询可能返回错误结果或拒绝写入
别拿ORM当批量工具,原生接口才是真快
JPA 的 saveAll()、MyBatis 的 <foreach> 默认仍是循环单条,除非你明确调了驱动层的批处理开关。
- MySQL JDBC:必须启用
rewriteBatchedStatements=true(旧驱动需配合useServerPrepStmts=false),否则addBatch()只是拼 SQL 字符串,不走服务端批处理 - PostgreSQL:JDBC 的
copy协议(如copy_fromin psycopg2)比任何INSERT快 5–20 倍,但它不是标准 SQL,不能写在普通字符串里 - SQL Server:.NET 场景下,
SqlBulkCopy是唯一能逼近BULK INSERT性能的选择;务必设BatchSize = 5000,开启EnableStreaming = true避免内存溢出 - 关键点:不要在循环里反复
new PreparedStatement,复用同一实例 +addBatch()才有效
最常被忽略的是:数据库配置本身就在拖慢你——比如 innodb_flush_log_at_trx_commit = 1(强持久)和 = 2(折中)之间,批量插入速度可能差 3–5 倍;而 buffer_pool_size 太小会让 InnoDB 反复刷脏页,等于用磁盘当内存使。

















