本文方案如下:
Oracle 持续写入期间,用达梦 DRS 完成全量与增量同步后割接至 DM8;DM8 运行期的新事务经反向 DRS 回传 Oracle;一段时间后回退到 Oracle。
生产系统从 Oracle 迁移至 DM8 时,主要目标是缩短停机窗口:迁移期间源库继续写入,同步工具先完成全量装载,再从一个一致点继续增量;割接窗口内停止源库写入、追平、校验,切换业务入口。
本文目标:
约束:任意时刻只允许一个数据库写入业务。
| 项目 | Oracle 源端 | DM8 目标端 |
|---|---|---|
| 产品 | Oracle AI Database 26ai Free 23.26.2.0.0 | DM8(Oracle 兼容模式,COMPATIBLE_MODE=2) |
| 部署 | KylinSP3 Linux 虚拟机 Docker 容器 oracle-free-26ai |
Windows 本机实例 DM8ORA |
| 地址 | 192.168.79.143:1521/FREEPDB1 |
127.0.0.1:5237 |
| 业务模式 | MIG_APP、MIG_AUDIT、MIG_RPT |
同名兼容结构 |
| 同步技术模式 | MIG_SYNC |
MIG_SYNC |
| 字符集 | AL32UTF8 | UTF-8 |
Oracle 端日志要求:
LOG_MODE = ARCHIVELOG
SUPPLEMENTAL_LOG_DATA_MIN = YES
SUPPLEMENTAL_LOG_DATA_ALL = YES
ENABLE_GOLDENGATE_REPLICATION = TRUE
DRS V5.2.5.4 配置两条链路:
| 链路 | 进程 | 端口 | 用途 |
|---|---|---|---|
| 正向 Oracle→DM | Linux CPT → Windows EXEC | Windows 5345 | 全量装载与增量同步 |
| 反向 DM→Oracle | Windows CPT → Linux EXEC | Linux 5346 | 回传 DM 新事务 |
两端均使用 DRS 专用账户 DMDRS。流程图如下:
flowchart LR
APP["业务写入(可切换)"]
ORA["Oracle FREEPDB1<br>192.168.79.143:1521"]
FCPT["正向 CPT(Linux)"]
FEXEC["正向 EXEC(Windows)"]
DM["DM8ORA<br>127.0.0.1:5237"]
RCPT["反向 CPT(Windows)"]
REXEC["反向 EXEC(Linux)"]
APP -->|"正向阶段写 Oracle"| ORA
ORA --> FCPT -->|"5345"| FEXEC --> DM
APP -.->|"割接后写 DM"| DM
DM -.-> RCPT -.->|"5346"| REXEC -.-> ORA
业务范围共 32 张表。MIG_APP.MIG_BIG_SALES_LINE 保留现有 10,100,000 行,不重新全量;其余业务表按接收态清空并重新装载初始全量数据,DM 端已验证的兼容结构与程序对象全部保留。另建 5 张影子表,用于复制 4 张自定义类型业务表;影子表是同步基础设施,不计入 32 张业务表。
同步范围使用显式白名单,排除以下对象:
DR$PRODUCT_MANUAL_CTX_IX$*;DMFLDR_*、TEST_*、*_PROBE、*_BENCH 等测试表;MTAB$*。DM8 目标结构分为两个可重复状态:
| 状态 | 内容 | 使用阶段 |
|---|---|---|
| 接收态 | 表结构完整;主键、唯一键等用于增量行定位的索引保留;普通业务触发器、外键关闭;LOB 非空约束临时放宽;影子表“业务表→影子表”生产组件禁用;全量批量期间先关闭“影子表→业务表”接收组件并用批处理重建,切入增量前再启用接收组件 | DRS 全量与增量 |
| 生产态 | 约束与索引完整;业务触发器启用;业务序列校准;影子表方向切换为“业务表→影子表”生产组件启用,“影子表→业务表”应用组件禁用 | DM 开放写入前 |
接收态:禁用 6 个触发器、20 个业务外键;4 个 LOB 列临时允许 NULL;
| 类别 | 处理方式 |
|---|---|
| 普通表 | DRS 全量装载与增量同步 |
| LOB 表 | DRS 两阶段写入,接收期放宽 NOT NULL,割接前校验后恢复 |
| VPD/RLS 表 | 对迁移账户授予 EXEMPT ACCESS POLICY,保证读取完整源数据 |
| 自定义类型表 | 原表不直接进入 DRS;完整行拆成 5 张普通影子表,源端事务内维护,目标端由接收组件重建原类型;正反向均由 DRS 同步影子表 |
| 序列 | 不随表数据复制;未启用序列同步时,割接/回退前手动推进 |
| 约束与触发器 | 接收期禁用,割接/回退恢复;DDL 审计触发器保持禁用 |
需要重新全量装载的普通表与影子表均逐表使用 INSERT|FLASHBACK。每张表按 FLASHBACK 的一致点读取,全量完成后从该点继续增量,覆盖全量读取期间产生的新事务。千万行大表已确认现有数据完整,不参与本轮全量装载。影子表必须在 DRS 建链前与源业务表校验一致,之后由同一业务事务持续维护;否则即使全量结果一致,复制的也只是错误存量数据。
Oracle 端运行持续写入脚本 oracle_realtime_workload.sql,覆盖下单、订单明细、支付、状态变更、业务事件与审计写入,同时覆盖主从表、事务提交顺序与 LOB。脚本在虚拟机后台启动:
cd /drs/work/oracle_to_dm/source
: > oracle_workload.log
nohup sh -c 'sudo docker exec -i oracle-free-26ai sqlplus -s / as sysdba \
< /drs/work/oracle_to_dm/source/oracle_realtime_workload.sql' \
> oracle_workload.log 2>&1 < /dev/null &
echo $! > oracle_workload.pid
脚本:(循环调用业务包,每轮一笔完整订单事务,交替提交与取消):
-- oracle_realtime_workload.sql
for i in 1..7200 loop
pkg_order_api.create_order(
1, 1003, 11,
case mod(i,4) when 0 then 'APP' when 1 then 'WEB'
when 2 then 'STORE' else 'PARTNER' end,
order_line_nt(
order_line_ot(10003, 1 + mod(i,3), null),
order_line_ot(10004, 1 + mod(i,2), null)
),
trunc(sysdate), systimestamp, l_order_id
);
if mod(i,5) = 0 then
pkg_order_api.cancel_order(l_order_id, 'DRS workload cancellation');
else
pkg_order_api.submit_order(l_order_id);
pkg_order_api.capture_payment(
l_order_id, 'DRS-PAY-' || l_order_id, 'WECHAT', systimestamp
);
end if;
commit;
dbms_session.sleep(1);
end loop;
上面的现有脚本只覆盖普通订单链路。实施影子表方案时,还要增加一条 UDT 业务流,直接通过正常业务入口修改电话号码数组与三类地址对象,并与订单脚本同时运行;
现有演练首次把 31 张表一次性以 INSERT|GROUP|FLASHBACK|SAME 加入全量。Windows 目标端剩余内存约 1 GB,DRS 内存用到约 2 GB 后主动停止。原因是源端分组读取与目标端并行接收超出测试机可用内存。
目标端配置调整为(两个线程参数取最小值 1;装载缓冲从 1024 MB 修改到 128 MB):
<load_thr>1</load_thr>
<load_buf_limit>128</load_buf_limit>
<work_thr>1</work_thr>
随后改为表级串行,逐表提交并等待完成:
alter cpt_oracle_drsuser add table
"sch.name='MIG_APP' and tab.name='APP_CONFIG'" INSERT|FLASHBACK
串行脚本为 run_forward_serial.sh。Oracle 分区表即使不显式指定 GROUP,DRS 仍可能按分区自动分组,因此资源控制主要靠表级串行,再加上目标端装载线程和缓存限额。
脚本主要逻辑(逐表提交 add table,确认该表装载完成后再提交下一张):
# run_forward_serial.sh 主要逻辑
while read -r schema_name table_name; do
before=$(grep -c '所有表都已经装载结束,全部成功' "$log_file" || true)
printf "alter cpt_oracle_drsuser add table \"sch.name='%s' and tab.name='%s'\" INSERT|FLASHBACK\n" \
"$schema_name" "$table_name" >> "$input_file"
deadline=$((SECONDS + 900))
while true; do
after=$(grep -c '所有表都已经装载结束,全部成功' "$log_file" || true)
(( after > before )) && break # 计数增加,确认当前表装载完成
pgrep -f 'drsvr.*cpt_oracle.xml' >/dev/null || exit 3
(( SECONDS >= deadline )) && exit 4 # CPT 退出或超时则终止
sleep 2
done
done
VPD/RLS 表。 迁移账户 DMDRS 受源库 VPD 策略影响,部分表查询结果为零行或不完整。为使迁移账户读取与业务策略无关的完整源数据:
GRANT EXEMPT ACCESS POLICY TO DMDRS;
该权限风险较高,仅用于受控迁移账户,生产应同时限制账户登录来源、密码托管与生命周期。
LOB 两阶段写入。 本版本 DRS 对 LOB 先插入普通列、再回填 LOB。若目标列保持 NOT NULL,第一阶段临时空值会被拒绝。接收期放宽四列:
ALTER TABLE MIG_APP.APP_CONFIG MODIFY CONFIG_JSON NULL;
ALTER TABLE MIG_APP.BUSINESS_EVENT MODIFY PAYLOAD_JSON NULL;
ALTER TABLE MIG_APP.CUSTOMER_PRIVATE_NOTE MODIFY NOTE_TEXT NULL;
ALTER TABLE MIG_APP.PROMOTION MODIFY RULE_JSON NULL;
割接前校验空值为 0 后恢复非空约束,这只是临时放宽。
VARRAY/OBJECT 类型。 当前 DRS 版本不能可靠完成下列 Oracle/DM 自定义类型的异构绑定;DM 作为反向日志源时,甚至会在装载整表字典阶段因自定义类型失败:
MIG_APP.CUSTOMER.PHONES:PHONE_VARRAY_T;MIG_APP.CUSTOMER_ADDRESS.ADDRESS_VALUE:ADDRESS_OT;MIG_APP.SHIPMENT.DELIVERY_ADDR:ADDRESS_OT;MIG_APP.WAREHOUSE.WAREHOUSE_ADDR:ADDRESS_OT。Oracle 端补充类型 EXECUTE 权限只能让 DRS 读取类型字典,不能保证目标驱动可以绑定复杂值。DRS 虽提供 CVT 数据清洗转换,但 V5.2.5.4 配套手册明确将数据库自定义类型排除在 CVT 支持的数据类型之外。
为 4 张自定义类型业务表建立 5 张不含任何 UDT、LOB 或数据库专有对象的普通关系表:
| 影子表 | 内容 | 主键 |
|---|---|---|
MIG_SYNC.CUSTOMER_FLAT |
CUSTOMER 除 PHONES 外的全部普通列 |
CUSTOMER_ID |
MIG_SYNC.CUSTOMER_PHONE_FLAT |
一个电话号码一行,包含 CUSTOMER_ID、PHONE_SEQ、PHONE_NO |
(CUSTOMER_ID, PHONE_SEQ) |
MIG_SYNC.CUSTOMER_ADDRESS_FLAT |
地址表全部普通列,并把 ADDRESS_VALUE 展开为省、市、区、街道、邮编 |
ADDRESS_ID |
MIG_SYNC.SHIPMENT_FLAT |
发货表全部普通列,并把 DELIVERY_ADDR 展开为 5 个标量属性 |
SHIPMENT_ID |
MIG_SYNC.WAREHOUSE_FLAT |
仓库表全部普通列,并把 WAREHOUSE_ADDR 展开为 5 个标量属性 |
WAREHOUSE_ID |
影子表保存业务表的完整可复制语义,不只保存复杂列。这样一行数据不会拆成“普通列由 DRS 同步、复杂列由另一条链路同步”,反向链路也不会丢失普通列的变化。
MIG_SYNC 应归独立账户所有,不能让高权限迁移账户 DMDRS 成为影子表所有者。两端影子表必须同名、同列序、同标量类型,并为 DRS 行定位建立主键或唯一键。权限按方向最小化授予:正向时 Oracle 的 DMDRS 对源影子表具有 SELECT,DM 的 DMDRS 对目标影子表具有 INSERT/UPDATE/DELETE;反向时权限方向相反。源端还必须按相应数据库和 DRS 手册要求为影子表启用可捕获的主键/唯一键日志及补充日志。切换方向时只调整必要权限,不把影子表开放给业务账户直接写入。
其中 PHONE_SEQ 必须稳定保存 VARRAY 的顺序。更新电话号码数组时,生产组件可在同一事务中删除该客户原明细,再按数组下标重新插入;事务提交前,影子表不能停留在中间状态。
Oracle 和 DM 两端各部署两类数据库内组件:
INSERT/UPDATE/D ELETE 时,在同一数据库事务中展开并维护影子表;任何影子维护失败都必须使业务事务一起回滚,禁止组件内部独立 COMMIT。MERGE/DELETE,使用 PHONE_VARRAY_T(...)、ADDRESS_OT(...) 重建目标业务表。为避免“业务表→影子表→DRS→业务表→影子表”回环,同一数据库同一时刻只启用一个方向:
| 数据库角色 | 生产组件 | 接收组件 |
|---|---|---|
| 当前业务主库 | 启用 | 禁用 |
| 当前 DRS 接收库 | 禁用 | 启用 |
组件开关应由专用配置表或会话上下文判断,不能靠人工临时删除触发器。普通业务触发器在接收期仍然禁用;影子表接收组件是复制基础设施,须单独管理。
正向阶段不把上述 4 张 UDT 原表加入 DRS 白名单,只加入 5 张普通影子表。实施顺序为:
INSERT|FLASHBACK;千万行大表保留现有数据并记录增量起点;全量期间 Oracle 的新事务继续产生日志,由增量捕获;CUSTOMER_FLAT、CUSTOMER_PHONE_FLAT 的同一源事务在 DM 是否仍保持为一个目标事务;全量与增量都只经过同一条 DRS 影子表链路。
割接前除普通表校验外,必须完成以下检查:
持续写入期间,普通关系表链路的校验结果:
| 表 | Oracle 行数 | DM 行数 | 最大业务键 |
|---|---|---|---|
SALES_ORDER |
22,732 | 22,732 | 1009607 |
SALES_ORDER_ITEM |
65,463 | 65,463 | ORDER_ID=1009607 |
ORDER_STATUS_HISTORY |
24,918 | 24,918 | HISTORY_ID=2013093 |
PAYMENT |
16,185 | 16,185 | PAYMENT_ID=1511485 |
BUSINESS_EVENT |
207,650 | 207,650 | EVENT_ID=20051701 |
CHANGE_AUDIT |
9,296 | 9,296 | AUDIT_ID=22066 |
INVENTORY_TXN |
4,376 | 4,376 | TXN_ID=1018971 |
LOB 完整性检查与定向修复。 增量追平后,BUSINESS_EVENT 行数与最大 EVENT_ID 相等,但校验发现 DM 最新增量区间有 588 行 PAYLOAD_JSON IS NULL,Oracle 对应值全部非空。
使用 CPT 的 LOB 定向修复:
cp cpt_oracle_drsuser \
"sch.name='MIG_APP' and tab.name='BUSINESS_EVENT'" \
UPDATE|LOB|WHERE|"EVENT_ID>=20051114 AND EVENT_ID<=20051701"
修复后两端空值数均为 0,随后恢复 PAYLOAD_JSON NOT NULL。LOB 校验至少包含空值数、长度和抽样内容。
顺序为:停止 Oracle 写入 → 正向链路保持运行追平 → 普通表、影子表和两端 UDT 语义全部校验通过 → 先停 CPT、再停 EXEC → 切换两端影子组件角色 → DM 开始接受写入。用 DRS 控制台命令停止,不要直接杀进程:
stop cpt_oracle_drsuser
stop exec_dm8_drsuser
影子组件角色切换。 正向链路完全停止后执行以下状态转换:
| 数据库 | 切换前 | 切换后 |
|---|---|---|
| Oracle | 业务表→影子表启用;影子表→业务表禁用 | 两者暂时均禁用,待反向链路建立前启用影子表→业务表 |
| DM | 影子表→业务表启用;业务表→影子表禁用 | 影子表→业务表禁用;业务表→影子表启用 |
角色切换必须在业务停写窗口完成,并检查配置表和触发器/过程的实际状态。两端生产组件不能同时启用,否则会形成双向回环。
序列推进。 DRS 回放业务数据时应用显式主键值。本环境未启用序列同步(EXEC 参数 seq_sync_mode 默认 0,序列同步操作只保存不应用),DM 序列如果不推进,首次业务写入可能取到与迁移数据冲突的旧值:
ALTER SEQUENCE MIG_APP.SEQ_ORDER_ID CURRENT VALUE 1010000;
ALTER SEQUENCE MIG_APP.SEQ_EVENT_ID CURRENT VALUE 20055000;
ALTER SEQUENCE MIG_APP.SEQ_HISTORY_ID CURRENT VALUE 2014000;
ALTER SEQUENCE MIG_APP.SEQ_INV_TXN_ID CURRENT VALUE 1019000;
ALTER SEQUENCE MIG_APP.SEQ_PAYMENT_ID CURRENT VALUE 1512000;
ALTER SEQUENCE MIG_APP.SEQ_SHIPMENT_ID CURRENT VALUE 1604000;
ALTER SEQUENCE MIG_AUDIT.SEQ_AUDIT_ID CURRENT VALUE 23001;
ALTER SEQUENCE MIG_AUDIT.SEQ_VALIDATION_ID CURRENT VALUE 501;
取值原则:高于当前最大键并按缓存大小取整的安全上界。序列允许空洞,不允许倒退碰撞。
约束与触发器恢复。 4 个 LOB 列空值校验为 0 后恢复 NOT NULL;恢复 20 个业务外键;启用 5 个业务触发器;TRG_DDL_AUDIT 迁移前即禁用,保持禁用。
反向链路需要 DM 提供归档与逻辑日志。修改 D:\DM8\DM8SERVER\data\DM8ORA\dm.ini:
ARCH_INI = 1
RLOG_APPEND_LOGIC = 1
RLOG_APPEND_SYSTAB_LOGIC = 1
新建 dmarch.ini:
[ARCHIVE_LOCAL1]
ARCH_TYPE = LOCAL
ARCH_DEST = D:\DM8\DM8SERVER\data\DM8ORA\arch
ARCH_FILE_SIZE = 128
ARCH_SPACE_LIMIT = 10240
静态参数修改后必须重启 DM。
重启后以视图与端口为准验证:
SELECT PARA_NAME, PARA_VALUE, FILE_VALUE
FROM V$DM_INI
WHERE PARA_NAME IN ('ARCH_INI','RLOG_APPEND_LOGIC','RLOG_APPEND_SYSTAB_LOGIC');
实测三个参数内存值与文件值均为 1,本地归档目录已产生首个 128 MB 归档文件。
Oracle 接收态。 禁用 6 个普通业务触发器、18 个普通外键;4 个 LOB 非空列临时允许 NULL;为 Oracle 端 DMDRS 逐表授予 DML。影子表组件单独切换为“业务表→影子表禁用、影子表→业务表启用”,不计入这里禁用的普通业务触发器。
两条引用分区外键 OSH_ORDER_FK、SOI_ORDER_FK 无法单独禁用,DISABLE 返回 ORA-14650。处理方式:保持启用,反向 EXEC 设为单工作线程,按父表到子表的顺序回放并校验。
EXEC 首次启动缺 GV_$LOCK、GV_$SESSION 权限,报 DRS-9011/ORA-00942,补充后消失:
GRANT SELECT ON SYS.GV_$LOCK TO DMDRS;
GRANT SELECT ON SYS.GV_$SESSION TO DMDRS;
Linux EXEC 启动需指定 DRS 与 Oracle Instant Client 动态库路径:
cd /drs/work/dm_to_oracle/target
export LD_LIBRARY_PATH=/drs/dmdrs5/release:\
/home/dmdba/instantclient19_31/usr/lib/oracle/19.31/client64/lib
tail -n 0 -f exec_cmd.input | \
/drs/dmdrs5/release/drsvr \
/drs/work/dm_to_oracle/target/exec_oracle.xml
DM CPT 首次失败:DRS-5125 LOAD DICT。
DRS-5125 LOAD DICT
auxiliary table triggers is missing, number: 0, total: 4
源 DM 未安装 4 个 DRS DDL 辅助触发器。本轮不复制 DDL,选择系统表逻辑日志模式,设置 RLOG_APPEND_SYSTAB_LOGIC=1 后重启 DM,清理未使用的 CPT 运行目录,重启后出现:
DDL mode: SYS TABLES
CPT ready: dm8 cpt
判断 CPT 是否可用应以 ready/running 状态为准,不能只看管理进程存在。
网络连通。 CPT 启动后 Windows 连接长期处于:
192.168.79.1 -> 192.168.79.143:5346 SYN_SENT
Linux EXEC 已监听 0.0.0.0:5346,但防火墙只放行 5901/5236/5237。补充:
sudo firewall-cmd --permanent --add-port=5346/tcp sudo firewall-cmd --reload
此后连接变为 Established,EXEC 日志出现"来自源DMDRS的连接已建立"。
装载字典(指定 LSN 掩码)。 切换前普通表、5 张影子表和两端 UDT 语义已经一致,因此反向链路不再做全量。对 28 张不含 UDT 的普通业务表及 5 张普通影子表逐表装载字典并指定当前 LSN 掩码,只回传该 LSN 之后的 DM 事务:
alter cpt_dm8_drsuser add dict \
"sch.name='MIG_APP' and tab.name='SALES_ORDER'" LSN
alter cpt_dm8_drsuser add dict \
"sch.name='MIG_SYNC' and tab.name='CUSTOMER_FLAT'" LSN
现有演练验证了 28 张普通业务表可以生成字典;修订方案还需为 MIG_SYNC 下 5 张影子表生成字典,因此反向可捕获范围应为 33 张普通表。新增的 5 张表尚未实际验证,必须以实际字典清单和日志为准。
UDT 原表不进入反向 CPT。 4 张含 DM 自定义类型的表在现有演练的装载字典阶段直接返回 DRS-5539 不支持的自定义类型:
| 表 | DM 自定义类型列 |
|---|---|
CUSTOMER |
PHONES PHONE_VARRAY_T |
CUSTOMER_ADDRESS |
ADDRESS_VALUE ADDRESS_OT |
SHIPMENT |
DELIVERY_ADDR ADDRESS_OT |
WAREHOUSE |
WAREHOUSE_ADDR ADDRESS_OT |
反向验证还发现 WAREHOUSE 也会失败:DM 作为日志源装载字典时检查表的完整类型定义。列黑名单解决不了,因为失败发生在装载字典阶段,早于普通列映射。
(**问题根因:**DRS本身不支持DM—>Oracle 的自定义类型,但是支持Oracle到DM的自定义类型,因此前面Oracle—>DM同步时,加载字典没报错,到这里才报错)
修订方案不捕获这 4 张 UDT 原表,改由 DM 当前主库的生产组件在同一业务事务中维护 5 张影子表。反向链路如下:
Oracle 接收组件必须使用幂等逻辑:普通对象行按主键 MERGE/DELETE;电话号码数组按 CUSTOMER_ID 和 PHONE_SEQ 重建。DRS 重放同一事务时不能产生重复电话、顺序变化或对象属性残留。接收组件不得自行提交,必须随 DRS 当前目标事务一起提交或回滚。
脚本主要逻辑(每轮一个事务,覆盖订单、明细、库存、支付、事件六类写入):
// DmCutoverWorkload.java:一轮事务
long orderId = nextOrderId(connection); // SEQ_ORDER_ID.NEXTVAL
insertOrder(connection, orderId, channel, goods, tax, i);
insertItem(connection, orderId, 1, 10003, "SKU-00003", ..., qty1, PRICE_10003);
insertItem(connection, orderId, 2, 10004, "SKU-00004", ..., qty2, PRICE_10004);
reserveInventory(connection, orderId, 10003, qty1, i); // 更新 INVENTORY,写 INVENTORY_TXN
reserveInventory(connection, orderId, 10004, qty2, i);
updateOrderStatus(connection, orderId, status, goods, tax);
if (!"CANCELLED".equals(status)) {
insertPayment(connection, orderId, goods.add(tax));
}
insertEvent(connection, orderId, status, i); // 非空 JSON CLOB 事件
connection.commit();
现有演练先提交 1 笔验证事务,Oracle 收到完整关联数据(2 明细、1 历史、1 支付、2 库存流水、1 事件、空 LOB 为 0);随后再提交 19 笔,共 20 笔,订单范围 1010000~1010019。每笔事务覆盖订单插入与状态更新、两条明细、两次库存更新、两条库存流水、状态历史与审计、支付或取消分支、非空 JSON CLOB 事件。
修订后的影子表方案还需要补一组 UDT 专项事务,至少覆盖:
CUSTOMER.PHONES,不修改其他普通列;CUSTOMER_ADDRESS.ADDRESS_VALUE;SHIPMENT.DELIVERY_ADDR 与 WAREHOUSE.WAREHOUSE_ADDR;停止 DM 写入并等待追平后校验:
| 表 | 切换前 | DM 写入后 | Oracle 回传后 | 增量 |
|---|---|---|---|---|
SALES_ORDER |
22,732 | 22,752 | 22,752 | +20 |
SALES_ORDER_ITEM |
65,463 | 65,503 | 65,503 | +40 |
ORDER_STATUS_HISTORY |
24,918 | 24,938 | 24,938 | +20 |
PAYMENT |
16,185 | 16,202 | 16,202 | +17 |
BUSINESS_EVENT |
207,650 | 207,670 | 207,670 | +20 |
CHANGE_AUDIT |
9,296 | 9,356 | 9,356 | +60 |
INVENTORY_TXN |
4,376 | 4,416 | 4,416 | +40 |
新订单范围的两端聚合结果一致:
orders=20 goods_sum=9196.38 tax_sum=1195.5294
paid=17 cancelled=3 items=40 item_qty=71
payments=17 inventory_txns=40 inventory_qty=-71
events=20 event_null_lobs=0 event_lob_len=970
停止 DM 写入,等待反向链路追平,并完成普通业务表、5 张影子表和 4 张 UDT 业务表的语义校验。全部通过后停止反向链路,顺序仍为先 CPT、后 EXEC:
stop cpt_dm8_drsuser
stop exec_oracle_drsuser
反向链路完全停止后切换影子组件角色:Oracle 禁用“影子表→业务表”、启用“业务表→影子表”,恢复为主库角色;DM 禁用“业务表→影子表”,如需重新建立正向同步链路,再启用“影子表→业务表”。必须先切角色再开放 Oracle 写入,否则回切后的第一笔 UDT 业务不会进入 Oracle 影子表。
恢复 Oracle 写入前,序列按 DM DBA_SEQUENCES.LAST_NUMBER 的缓存上界推进:
ALTER SEQUENCE MIG_APP.SEQ_ORDER_ID RESTART START WITH 1011000;
其余序列目标值:SEQ_EVENT_ID 20060000、SEQ_HISTORY_ID 2015000、SEQ_INV_TXN_ID 1020000、SEQ_PAYMENT_ID 1513000、SEQ_SHIPMENT_ID 1604000、SEQ_AUDIT_ID 24001、SEQ_VALIDATION_ID 501。
随后恢复 4 个 LOB 列 NOT NULL、18 个外键 ENABLE VALIDATE(全部成功)、5 个业务触发器;DDL 审计触发器保持禁用。最终状态:
ENABLED_BUSINESS_TRIGGERS = 5
DDL_AUDIT_STILL_DISABLED = 1
DISABLED_RECEIVE_FKS = 0
NULLABLE_LOB_COLUMNS = 0
最后验证时,先调用原 MIG_APP.PKG_ORDER_API 完成下单、提交与支付,再执行一笔 UDT 修改,确认 Oracle 主库的“业务表→影子表”生产组件已经恢复。普通订单脚本主要逻辑(oracle_post_cutback_smoke.sql):
MIG_APP.PKG_ORDER_API.CREATE_ORDER(
p_tenant_id => 1,
p_customer_id => 1003,
p_warehouse_id => 11,
p_channel_code => 'WEB',
p_items => MIG_APP.ORDER_LINE_NT(
MIG_APP.ORDER_LINE_OT(10003, 1, null),
MIG_APP.ORDER_LINE_OT(10004, 2, null)
),
p_order_date => trunc(sysdate),
p_order_time => systimestamp,
p_order_id => l_order_id
);
MIG_APP.PKG_ORDER_API.SUBMIT_ORDER(l_order_id);
MIG_APP.PKG_ORDER_API.CAPTURE_PAYMENT(
p_order_id => l_order_id,
p_payment_ref => 'ORACLE-CUTBACK-' || l_order_id,
p_payment_method => 'WECHAT',
p_paid_at => systimestamp
);
commit;
执行结果:
ORACLE_CUTBACK_WRITE_OK order_id=1011000
order_status=PAID item_count=2 hist_count=2
pay_count=1 inv_txn_count=2 event_count=3
| 问题 | 现象 | 处理 |
|---|---|---|
| 全量内存超限 | 目标端内存约 1 GB,DRS 使用约 2 GB 后停止 | 表级串行,限制装载线程与缓存 |
| VPD 过滤 | 迁移账户读取结果为零行或不完整 | GRANT EXEMPT ACCESS POLICY,受控使用 |
| LOB 两阶段写入 | 临时空值被 NOT NULL 拒绝 |
4 列接收期放宽,校验后恢复 |
| UDT 绑定失败 | 正向 VARRAY/OBJECT 绑定失败;反向 UDT 原表无法装载字典 | 4 张 UDT 原表不直接进入 DRS;改为 5 张语义影子表,正反向均同步普通 DML; |
| CVT 适用范围 | CVT 支持普通 DML 清洗,但配套手册排除数据库自定义类型 | CVT 只清洗普通字段,不直接处理 VARRAY/OBJECT |
| 空 LOB | 行数与最大键相等,588 行 LOB 为空 | LOB 定向修复后恢复约束 |
| 触发器不兼容 | DM 无法解析 STANDARD_HASH |
改写为 DBMS_CRYPTO.HASH_SH256 |
| 反向字典失败 | DRS-5125,DDL 辅助触发器缺失 |
RLOG_APPEND_SYSTAB_LOGIC=1,系统表逻辑日志 |
| 引用分区外键 | DISABLE 报 ORA-14650 |
保持启用,单工作线程(避免子表抢先导致链路报错),父→子顺序校验 |
| 反向连接不通 | Windows 连接停在 SYN_SENT |
防火墙放行 5346 |
| EXEC 权限不足 | DRS-9011/ORA-00942 |
授予 GV_$LOCK、GV_$SESSION |
文章
阅读量
获赞
