ORA-30036错误表明UNDO表空间物理空间耗尽或被长事务锁死,需立即验证:查DBA_TABLESPACE_USAGE_METRICS确认used_percent≥95%即空间告急;关联V$TRANSACTION与V$SESSION识别USED_UBLK>10000且START_TIME超30分钟的长事务;优先添加新数据文件扩容,慎用MAXSIZE UNLIMITED;批量DML须分批提交,避免单事务UNDO暴涨。
为什么100–1000行是常见提交阈值
oracle中单次事务过大,会撑爆回滚段(undo),触发ora-01555快照过旧或ora-30036无法扩展undo表空间;太小又导致频繁commit引发大量redo日志切换和lgwr争用。实测在oltp生产环境,100–1000行/次是较稳的平衡点——既避免长事务锁住rowid范围,又不让lgwr忙不过来。
这个范围不是固定值,需结合:
• 表的平均行宽(影响undo生成量)
• undo_retention设置(决定undo保留时长)
• 是否启用了AUTO UNDO MANAGEMENT(Oracle 9i+默认启用)
• delete语句是否命中索引(索引维护本身也消耗undo)
用ROWNUM控制每次删除数量,但别写死WHERE ROWNUM
直接在delete里写WHERE ROWNUM 看似简单,实际可能漏删:Oracle对子查询+<code>ROWNUM的优化行为不稳定,尤其当where条件涉及函数或绑定变量时,执行计划可能跳过部分匹配行。
更可靠的做法是先取ROWID进临时表,再分批删:
CREATE TABLE temp_del_rowid AS SELECT ROWID rid FROM target_table WHERE your_condition;
然后在PL/SQL块中循环:
- 每次
DELETE FROM target_table WHERE ROWID IN (SELECT rid FROM temp_del_rowid WHERE ROWNUM - 立刻
DELETE FROM temp_del_rowid WHERE ROWNUM - 立刻
COMMIT
注意:必须用PRAGMA AUTONOMOUS_TRANSACTION包装存储过程,否则COMMIT会提前结束外层事务上下文。
避免在循环里拼接动态SQL并反复解析
像EXECUTE IMMEDIATE 'DELETE ... WHERE id = ' || v_id这种写法,在循环中每轮都硬解析,CPU开销陡增。Oracle 12c+支持BULK COLLECT + FORALL,但FORALL DELETE不支持ROWID数组直接删除(语法不合法),所以仍得走ROWID IN (subquery)路径。
真正该省掉的是“查一遍再删一遍”的重复扫描。正确顺序是:
- 第一步:用
INSERT /*+ APPEND */ INTO temp_del_rowid SELECT ROWID ...一次性捞出所有待删ROWID - 第二步:只对
temp_del_rowid做ROWNUM切片,原表target_table全程只被DELETE ... WHERE ROWID IN访问,走索引快速定位
这样原表全表扫描仅发生1次,而非每批都扫一次。
别忽略DBMS_STATS和索引重建的时机
大批量delete后,表的NUM_ROWS统计信息不会自动更新,后续执行计划可能继续走索引范围扫描,而实际上数据已稀疏——这会让下一轮delete更慢。应在最后补一句:
DBMS_STATS.GATHER_TABLE_STATS(ownname => 'YOUR_SCHEMA', tabname => 'TARGET_TABLE');
另外,如果原表有4个以上索引,且delete条件未覆盖全部索引字段,建议在delete完成后立刻执行:
ALTER INDEX idx_name REBUILD ONLINE;
否则下次查询可能因索引分支变深、leaf block空洞多,反而拖慢业务SQL。
真正容易被跳过的点是:没人检查temp_del_rowid表是否建了索引。它至少要对rid列建普通索引,否则DELETE FROM temp_del_rowid WHERE ROWNUM 每次都是全表扫描——这个临时表反而成了性能瓶颈。











