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在哪一层——循环体里漏写,或者误以为存储过程自动帮你收尾了。

















