本质是UNDO表空间撑爆或快照过期,需按逻辑批次切分事务、显式断点续跑、用BULK COLLECT+FORALL批量处理并清空集合,而非盲目调大UNDO_RETENTION或频繁提交。
大事务卡住或报 ORA-01555 / ORA-30036 怎么办
本质是 undo 表空间撑爆或快照过期,不是“提交慢”,而是数据库在保护一致性。别急着调大 undo_retention 或加 undo 表空间——先切分事务。
-
COMMIT不能随便加在循环里:比如FOR rec IN (SELECT ...)中每条都COMMIT,会破坏业务原子性,还可能留下中间态脏数据 - 真正安全的切分点是逻辑上可独立提交的批次,比如按主键 ID 分段、按时间范围(
WHERE create_date BETWEEN ...)、或用ROWNUM控制每次处理 1000 行 - 必须显式记录断点:用变量存最后处理的
id或MAX(rowid),下次从那里 resume,否则重跑会重复或漏数据
用 BULK COLLECT + FORALL 分批比逐行快多少
不是“快一点”,是量级差异。逐行 INSERT 走的是 SQL 引擎+PL/SQL 引擎双切换,而 BULK COLLECT 把数据一次拉进内存数组,FORALL 批量发给 SQL 引擎执行,省掉 90% 以上上下文切换开销。
- 数组大小设
1000是经验值:太小(如 10)起不到批量效果;太大(如 100000)可能触发 PGA 内存告警或 GC 延迟 -
FORALL i IN INDICES OF emp_tab比FORALL i IN 1..emp_tab.COUNT更安全,能跳过被DELETE掉的稀疏索引项 - 记得在
FORALL后加SAVE EXCEPTIONS,否则某条失败整批回滚,你根本不知道哪条错了
UNDO 表空间不够时,COMMIT 频率和 UNDO_RETENTION 怎么配
COMMIT 越频繁,单次事务占 UNDO 的时间越短,但总 UNDO 消耗未必减少——因为每次 COMMIT 后旧镜像才能被覆盖,高频提交反而让 UNDO 块更碎片化。
- 优先调
UNDO_RETENTION:设为略大于最长单批执行时间(比如一批跑 8 秒,设UNDO_RETENTION=12),避免 ORA-01555 - 不要盲目增大 UNDO 表空间:先查
V$UNDOSTAT的UNXPSTEALCNT(非到期块被偷走次数),>0 就说明真不够;如果是 0,问题大概率出在批大小或锁等待上 - 用
DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER记起点 SCN,比用SYSDATE更准——时间可能不准,SCN 不会骗人
为什么加了 COMMIT 还是 OOM 或 PGA 超限
因为 COMMIT 只释放 UNDO 和锁,不释放 PL/SQL 变量内存。你用 BULK COLLECT 拉了 10 万行进集合,COMMIT 后那 10 万行还在内存里,等着下一轮循环再填新数据——结果就是内存只增不减。
- 每次
COMMIT后必须显式清空集合:emp_tab.DELETE,不是emp_tab := emp_tab_type() - 避免在循环里拼大字符串:
v_sql := v_sql || '...'会不断分配新内存,改用DBMS_SQL或临时表中转 - 如果数据源本身超大(比如全表扫描几千万行),别用
SELECT ... BULK COLLECT INTO——直接用游标 +FETCH ... BULK COLLECT分页取,控制内存驻留量
COMMIT,是判断“这一批”是否构成完整业务单元。UNDO 告警只是症状,背后常是设计没对齐:比如把“同步用户+更新统计+发消息”硬塞进一个事务,其实后两者完全可以异步。










