应采用rowid分批删除,即查一批rowid→删一批→提交→再查下一批,批次建议5000–10000行;直接delete几百万行会引发undo膨胀、锁争用、ora-01555/01562错误、回滚代价高、hwm不降、索引分裂等问题。

直接用单条 UPDATE 或 DELETE 语句处理几十万行,大概率会触发 ORA-01555、ORA-01562,甚至撑爆 UNDO 表空间——这不是性能问题,是稳定性风险。
为什么不能直接写大范围 UPDATE/DELETE
Oracle 对每一行更新/删除都生成 UNDO 记录,并全程持有数据块 ITL 锁。几百万行意味着:
- UNDO 段持续膨胀,容易耗尽(尤其在低配或高并发环境)
- 其他会话读取旧版本时频繁报
ORA-01555: snapshot too old - 事务过长导致锁等待严重,阻塞业务
- 一旦失败,回滚代价极高,可能卡住数小时
- HWM 不降,后续全表扫描仍扫空块,索引分裂加剧
用 ROWID 分批 DELETE 最稳妥
不依赖业务字段,只靠物理定位,避免索引失效和执行计划漂移。核心是「查一批 ROWID → 删这批 → 提交」闭环:
DECLARE
v_batch_size NUMBER := 5000;
BEGIN
LOOP
DELETE FROM orders
WHERE ROWID IN (
SELECT ROWID FROM (
SELECT ROWID
FROM orders
WHERE status = 'PROCESSED'
AND ROWNUM
- 必须用
ROWNUM 在子查询里截断,否则 CBO 可能全表扫描再过滤 - 若原始条件含函数(如
TO_CHAR(create_time, 'YYYYMM') = '202301'),先建函数索引或改用范围条件(create_time >= DATE '2023-01-01') - 批次大小选 5000–10000:太小导致日志切换压力大;太大逼近单事务瓶颈
批量 UPDATE 推荐 FORALL + BULK COLLECT
比游标循环快 5–10 倍,减少上下文切换,且可控制提交节奏:
DECLARE
TYPE t_id IS TABLE OF employees.employee_id%TYPE;
TYPE t_sal IS TABLE OF employees.salary%TYPE;
l_ids t_id;
l_sals t_sal;
BEGIN
SELECT employee_id, new_salary
BULK COLLECT INTO l_ids, l_sals
FROM emp_updates
WHERE processed = 'N';
FORALL i IN 1..l_ids.COUNT
UPDATE employees
SET salary = l_sals(i)
WHERE employee_id = l_ids(i);
COMMIT;
END;
- 不要用
CURRENT OF配合FORALL,它不支持;必须显式用主键或唯一键定位 -
BULK COLLECT默认无上限,大数据量需加LIMIT防内存溢出:BULK COLLECT INTO ... LIMIT 10000 - 若更新逻辑复杂(如需查关联表),优先考虑改写为单条带
EXISTS的UPDATE,而非在 PL/SQL 中嵌套查询
动态表名/条件的批量操作必须用 EXECUTE IMMEDIATE
存储过程参数化表名或 WHERE 条件时,无法静态编译,只能拼接字符串执行:
CREATE OR REPLACE PROCEDURE batch_del_by_cond(
p_table_name VARCHAR2,
p_where_cond VARCHAR2,
p_batch_size NUMBER DEFAULT 5000
) AS
v_sql VARCHAR2(4000);
BEGIN
LOOP
v_sql := 'DELETE FROM ' || p_table_name ||
' WHERE ROWID IN (SELECT ROWID FROM ' || p_table_name ||
' WHERE ' || p_where_cond || ' AND ROWNUM
- 表名、列名、WHERE 片段都不能做绑定变量,只能字符串拼接——务必校验输入,防 SQL 注入
- 拼接前用
DBMS_ASSERT.SQL_OBJECT_NAME校验表名合法性 - 错误处理必须加
EXCEPTION块,否则一条错整个过程中断
真正难的不是写出来,而是判断该用哪一种:简单条件、小数据量,直接 SQL 就够;中等规模、需事务控制,用 FORALL;超大规模、UNDO 敏感,必须 ROWID 分批;而动态场景,拼接 + 校验是唯一路径。漏掉 COMMIT、忘设 LIMIT、或对函数条件不做索引适配,都会让脚本在线上突然失速。











