MySQL中INSERT不直接锁表,但大事务批量插入会因锁升级、自增锁争用及长持有undo/锁资源而实质阻塞DML;核心应控制事务粒度,分批提交(1000–5000行/批),优先用LOAD DATA INFILE或COPY导入,并预检主键冲突。

为什么直接 INSERT INTO ... VALUES (...) 会锁表
MySQL 的 INSERT 在事务中默认走行级锁,但批量插入时若一次性提交太多行(比如单条语句含上万 VALUES),InnoDB 会升级为间隙锁或页级锁,甚至触发自增锁争用;更关键的是,大事务会长时间持有 undo log 和锁资源,阻塞其他 DML。PostgreSQL 虽无“锁表”概念,但长事务仍会拖慢 vacuum,堆积 bloat。
分批插入 + 显式事务控制(MySQL/PostgreSQL 通用)
核心不是“一次插多少”,而是“每次事务体积可控、提交及时”。重点在控制事务粒度和避免隐式提交干扰:
- 单批次控制在 1,000–5,000 行(
INSERT ... VALUES (...), (...), ...),具体看单行数据大小,建议总 SQL 长度 ≤ 1MB - 禁用自动提交:
SET autocommit = 0(MySQL)或显式BEGIN(PG) - 每批后手动
COMMIT,不要等连接断开或脚本结束才提交 - 避免在循环内反复
PREPARE/EXECUTE(MySQL)——预编译一次复用即可
用 LOAD DATA INFILE 或 COPY 替代 INSERT(最快且最轻量)
这是真正绕过 SQL 解析层的方式,不走查询优化器,不生成大量 binlog event(MySQL)或 WAL 记录(PG),锁竞争最小:
- MySQL:确保文件在数据库服务器本地,用
LOAD DATA INFILE '/path/data.csv';若客户端上传,改用LOAD DATA LOCAL INFILE(需服务端开启local_infile=ON) - PostgreSQL:用
COPY table_name FROM '/path/data.csv' WITH (FORMAT CSV),比INSERT快 5–20 倍 - 注意字段顺序、NULL 处理、引号转义 —— 错误格式会导致整批失败,建议先用小样本验证
- CSV 文件不能有 BOM,行尾统一为
\n(非\r\n),否则 PG 可能报extra data after last expected column
避开主键/唯一索引冲突导致的隐式锁等待
即使分批插入,若数据含重复主键或违反唯一约束,InnoDB 会在插入前对对应索引记录加 next-key lock,造成串行化等待:
- 插入前用
INSERT IGNORE(MySQL)或ON CONFLICT DO NOTHING(PG)兜底,但别滥用——它们仍要先查索引再决定跳过,高并发下查本身也抢锁 - 更稳妥的是:提前用
SELECT id FROM table WHERE id IN (...)扫出已存在 ID,做内存去重后再插入 - 如果目标表有频繁读的二级索引,考虑临时
DROP INDEX,插完再重建(仅适用于维护窗口期)
真正卡住百万级插入的,往往不是语法或配置,而是没意识到“事务边界”和“索引维护成本”才是锁压力源头。分批、文件导入、预检重复,三者组合用,比调 innodb_lock_wait_timeout 或改隔离级别实在得多。










