MySQL 存储过程循环插入慢因每行默认单事务、未批量提交、索引实时更新;SQL Server 应改用 INSERT SELECT 分批 + 禁用索引;PostgreSQL DO 块需显式 BEGIN/COMMIT 或用 generate_series();Oracle FORALL 需合理设 LIMIT 并注意触发器兼容性。

MySQL 里用存储过程循环插入 10 万行测试数据,为什么特别慢?
因为默认每条 INSERT 都走完整事务流程,没批量、没禁用索引、没关自动提交——结果不是“快填”,而是“卡死”。
- 每次循环都触发一次磁盘写入(除非显式
START TRANSACTION+COMMIT包住) -
AUTO_INCREMENT字段在高并发模拟下可能隐式加锁,拖慢自增步进 - 如果表上有二级索引,每插一行都要更新所有索引页,IO 翻倍
- 没关
autocommit时,等于每行都在开事务、写 redo log、刷盘
SQL Server 中用 WHILE 循环 +INSERT SELECT 填充测试数据,怎么避免日志爆炸?
直接 INSERT INTO …… SELECT TOP 1000 …… FROM sys.objects 递归拼接,比单行 INSERT 快十倍,且日志量可控。
- 用
SELECT INTO或INSERT SELECT替代逐行VALUES,减少解析和计划重编译开销 - 把循环粒度从“每行”提到“每千行”,配合
GO 100分批提交(注意:不是 T -SQL 的GO,是客户端批处理命令) - 临时关闭非聚集索引:
ALTER INDEX [idx_name] ON [table] DISABLE,填完再REBUILD - 确保数据库恢复模式为
BULK_LOGGED(仅限大容量操作),否则日志照常满写
PostgreSQL 用 DO 块写匿名存储过程插入测试数据,为什么报错“cannot execute INSERT in a read-only transaction”?
因为 DO 块默认运行在只读事务上下文中,而 INSERT 需要写权限。
- 必须显式加
BEGIN/COMMIT,不能只靠DO $$ …… $$包裹 - 正确写法是:
DO $$ BEGIN FOR i IN 1..10000 LOOP INSERT INTO t(x) VALUES (i); END LOOP; COMMIT; END $$; - 更推荐用
generate_series()一次性生成:INSERT INTO t(x) SELECT g FROM generate_series(1,10000) AS g;——无循环、无 PL/pgSQL 开销、不锁表 - 如果真要用循环,记得在
DO前设SET LOCAL statement_timeout = '30s'防卡死
Oracle 用 PL/SQL 写 BULK COLLECT + FORALL 插入百万级数据,哪些参数影响实际速度?
BULK COLLECT LIMIT和 FORALL SAVE EXCEPTIONS 的组合方式,直接决定内存占用和错误定位成本。
-
LIMIT值太小(如 50)→ 上下文切换频繁;太大(如 10000)→ PGA 内存溢出风险;建议 200–500 之间实测 - 没加
SAVE EXCEPTIONS时,一条失败整批回滚;加了之后需遍历SQL%BULK_EXCEPTIONS,但能继续执行 - 务必在
FORALL前加EXECUTE IMMEDIATE 'ALTER SESSION SET "_serial_direct_read"=true'绕过 buffer cache(对大表有效) - 如果目标表有
TRIGGER,FORALL不会自动批量触发——得手动改写成FORALL i IN …… INSERT …… VALUES (arr(i))并确认触发器逻辑兼容
真正卡住的从来不是语法,是事务边界、索引维护时机、还有你忘了 COMMIT 在哪一层——循环体里漏写,或者误以为存储过程自动帮你收尾了。