真实批量插入应优先使用 INSERT INTO ... SELECT 集合操作而非 WHILE 循环或游标;SQL Server 需避免锁表和日志暴涨,MySQL 须借临时表中转,PostgreSQL 要慎用 ON CONFLICT 与触发器;所有场景均需控制事务粒度、索引策略及并发影响。

SQL Server 中用 INSERT INTO ... SELECT 替代循环插入
直接在存储过程中写 WHILE 循环逐行 INSERT,性能差、锁表久、日志暴涨。真实批量插入应该走集合操作。
常见错误是把应用层的“一行一行处理”思维带进存储过程——比如先查出 10 万条记录,再用游标或 WHILE 插入,结果执行十几分钟还卡死事务日志。
- 优先用
INSERT INTO target_table (...) SELECT ... FROM source_table,一次提交整批 - 若数据来自参数(如表值参数 TVP),则
INSERT INTO ... SELECT * FROM @tvp - 确保目标表有合适索引;但插入前可考虑临时禁用非必要索引(尤其是唯一约束和外键),插完再重建
- 大批次(>10 万行)建议分批,用
OFFSET-FETCH或TOP N控制每批 5000–10000 行,避免单次事务过大
MySQL 存储过程中无法直接批量插入?用临时表中转
MySQL 存储过程不支持表值参数,也不能在 INSERT ... SELECT 中直接引用复杂参数列表。硬拼 SQL 字符串又易 SQL 注入且难维护。
可行路径是:先把数据灌进临时表,再从临时表批量插入目标表。
- 创建临时表时加
ON COMMIT DROP(MySQL 8.0+)或依赖会话生命周期自动销毁 - 调用方先执行
INSERT INTO temp_batch (...) VALUES (...), (...), ...(应用层拼好或用客户端批量 API) - 存储过程内只做
INSERT INTO real_table SELECT * FROM temp_batch,干净安全 - 注意临时表字段类型要和目标表严格一致,否则隐式转换可能丢精度或报错
Truncated incorrect DOUBLE value
PostgreSQL 的 INSERT ... ON CONFLICT 要小心触发器和 RETURNING
PG 的批量 upsert 看似简单,但实际在存储过程中组合使用时容易踩坑:触发器重复执行、RETURNING 返回行数与预期不符、冲突后未返回原始主键。
-
INSERT INTO t (...) SELECT ... FROM data_source ON CONFLICT (col) DO UPDATE SET ...是标准写法,但DO UPDATE部分不能引用EXCLUDED.*外的别名,否则报错column "excluded" does not exist - 如果需要返回插入/更新后的 ID,必须写
RETURNING id,且该子句会使整个语句变成“有返回值”的形式,调用时需用PERFORM或RETURN QUERY包裹 - 触发器会在每行上独立触发——即使批量插入 1 万行,
BEFORE INSERT触发器也会执行 1 万次,性能敏感场景应改用BEFORE STATEMENT
所有数据库都绕不开的事务与锁问题
批量插入不是“快就完了”,关键是控制影响范围。默认一个大事务包住全部插入,在高并发下极易阻塞其他读写操作。
- 显式用
BEGIN TRANSACTION/COMMIT,但不要让事务跨过存储过程边界——调用方应负责事务控制,存储过程只做“工作单元” - SQL Server 中开启
READ_COMMITTED_SNAPSHOT可缓解读阻塞,但不解决写写冲突 - PostgreSQL 默认的
READ COMMITTED隔离级别下,插入不会阻塞 SELECT,但多个并发批量插入仍可能因索引竞争出现deadlock detected - MySQL 的
innodb_lock_wait_timeout默认 50 秒,大批量插入期间若被锁住,应用端可能等不到结果就超时断开
真正麻烦的不是语法怎么写,而是你没意识到:插入 10 万行时,锁住的是索引 B+ 树的某一段,还是整张表,取决于引擎、隔离级别、是否有合适索引——这些得看执行计划,不能光靠“加个事务”蒙混过关。










