必须用控制表存last_sync_time而非局部变量,因pl/sql变量会话级销毁且max(update_time)取值与源变更脱节;merge不处理删除,须额外delete带时间过滤的孤儿数据;空值、精度、索引、时区四者任一缺失均致同步失准。

不能靠变量存上次时间,必须用控制表;不补DELETE逻辑,目标表迟早堆满“孤儿数据”。
为什么不能在PL/SQL里用局部变量记 last_sync_time
每次存储过程执行都是独立会话,v_last_time 这类变量退出就销毁。常见错误是开头写 SELECT MAX(update_time) INTO v_last_time FROM target_table,这取的是目标表当前最新时间,和源表变更完全脱节——如果目标表刚被清空但事务未提交,MAX() 返回 NULL,后续 WHERE update_time > NULL 整个条件恒假,一条数据都拉不到。
- 正确做法:查控制表,例如
SELECT last_sync_time INTO v_last_time FROM sync_control WHERE table_name = 'orders' - 控制表必须有主键(如
table_name),字段类型推荐TIMESTAMP WITH TIME ZONE,避免时区错位 - 每次同步成功后,必须显式
UPDATE sync_control SET last_sync_time = v_current_max_time WHERE table_name = 'orders',不能只依赖查询
MERGE 只管增/改,删得另外写
Oracle 的 MERGE 语句不感知源端删除,只处理 WHEN MATCHED 和 WHEN NOT MATCHED,对已从源表消失的记录无动于衷。不加删除逻辑,目标表就会积累大量“孤儿数据”。
- 删除语句必须带时间过滤,例如:
DELETE FROM target WHERE id NOT IN (SELECT id FROM source WHERE update_time > v_last_time) AND update_time - ON 条件字段(如
id)必须建唯一索引,否则可能锁表或匹配错乱 - 源数据若含重复键值,
MERGE会报错The MERGE statement attempted to update or delete the same row more than once,需提前用ROW_NUMBER() OVER (PARTITION BY id ORDER BY update_time DESC)去重
空值、精度、索引、时区,四个点漏一个就同步失准
WHERE update_time > ? 看似简单,实际运行中崩得悄无声息。
- 空值必须显式排除:
AND update_time IS NOT NULL,否则NULL行永远进不了增量流程 - 字段定义要是
TIMESTAMP(3),Java 端用java.sql.Timestamp绑定,JDBC URL 加serverTimezone=UTC,三者精度和时区必须对齐 -
update_time不建索引?全表扫描会让同步变慢甚至拖垮源库——哪怕只有 10 万行 - 应用写入用
Asia/Shanghai,数据库设UTC,同步脚本又按本地时间解析,差 8 小时就直接漏掉一整天数据
真正难的不是写通 SQL,而是半年后还能定位出这次同步拉了哪几条
日志没写、控制表没更新、删除没加时间过滤、空值没处理、索引没建——这些点单看都不致命,但叠加起来会让同步结果不可追溯。最常被忽略的是:同步完成后没把 v_current_max_time 写回控制表,或者失败时没在 EXCEPTION 块里保留旧值并记录日志。一旦断点续传失效,重跑就得人工核对,代价远高于前期多写两行代码。











