应使用bulk collect + forall分批处理增量数据,因其避免逐行上下文切换、复用执行计划、降低pga内存压力;错误用loop会导致性能骤降、锁表及ora-04030等异常。
直接用 forall + bulk collect 做增量同步,比逐行 update 快 10–100 倍,但必须配合合理的分批大小和事务控制,否则容易触发 ora-04030 或锁表。
为什么不能用简单 LOOP 处理增量数据
常见错误是写一个 FOR emp IN (SELECT * FROM source WHERE ...) 然后在循环里单条 INSERT/UPDATE。这种写法每行都产生一次上下文切换和 SQL 解析,10 万行可能耗时几分钟,且极易被其他会话阻塞。
- 每次
UPDATE都要走完整执行计划,无法复用 - 未显式控制事务边界,一旦中断,回滚代价高
- 没有内存限制机制,大结果集直接撑爆 PGA
正确做法:BULK COLLECT 分批 + FORALL 批量提交
核心是把“查”和“写”解耦,并控制每次处理的数据量(推荐 5000–10000 行)。下面是一个典型增量同步模板:
DECLARE
TYPE t_row IS TABLE OF target_table%ROWTYPE;
v_rows t_row;
CURSOR c_delta IS
SELECT /*+ CARDINALITY(t 1000) */ t.*
FROM source_table@dblink t
WHERE t.last_modified > (SELECT NVL(MAX(last_modified), DATE '1970-01-01') FROM target_table);
BEGIN
OPEN c_delta;
LOOP
FETCH c_delta BULK COLLECT INTO v_rows LIMIT 5000;
EXIT WHEN v_rows.COUNT = 0;
<pre class="brush:php;toolbar:false;">FORALL i IN 1..v_rows.COUNT
INSERT INTO target_table VALUES v_rows(i)
ON DUPLICATE KEY UPDATE /* Oracle 12c+ 用 MERGE,11g 用 MERGE 或先 DELETE 后 INSERT */
last_modified = v_rows(i).last_modified,
col2 = v_rows(i).col2;
COMMIT;END LOOP; CLOSE c_delta; END;
-
LIMIT 5000是关键,避免 PGA 内存溢出 - 游标加
CARDINALITY提示,帮优化器预估返回行数 - WHERE 条件必须走索引(如
last_modified字段建索引),否则全表扫源库 - Oracle 11g 不支持
ON DUPLICATE KEY UPDATE,得用MERGE或拆成DELETE+INSERT
增量识别字段选错会导致数据丢失
用 ROWID 或 ROWNUM 做增量标记是危险的——源表 UPDATE 后 ROWID 可能变,ROWNUM 每次查询都重排。唯一可靠的是业务时间戳或自增序列(如 id)。
- 优先选
last_modified,但必须确保应用层严格更新该字段 - 若无时间字段,可用
ORA_ROWSCN(需开启行级依赖),但精度为块级,可能漏更新 - 绝对不要用
MAX(id)做条件:并发插入时可能跳过新 ID - 首次同步建议用全量初始化,再切到增量逻辑
真正难的不是写对语法,而是确认源端数据变更是否可精确捕获、目标端冲突如何消解、失败后能否断点续传——这些不在 PL/SQL 范围内,得靠外部日志或变更表兜底。











