注册
复杂Oracle迁移DM8:对象兼容改造总结
技术分享/ 文章详情 /

复杂Oracle迁移DM8:对象兼容改造总结

悬铃木 2026/08/14 120 0 0

复杂Oracle迁移DM8:对象兼容改造总结


目录

1. 迁移概述

2. DTS 迁移评估

3. DTS 首轮迁移:多个任务报错分析

4. 复杂Oracle对象兼容性改造

5. 千万级大表迁移(MIG_BIG_SALES_LINE)

6. 数据一致性验证

7. 业务功能与 SQL 语义一致性验证

8. 迁移总结


1. 迁移概述

1.1 总体迁移策略

本案例验证一套包含复杂对象和千万级数据的 Oracle 数据库向达梦 DM8 迁移的全过程。实测过程分为静态评估、常规迁移、大表装载及失败分析四个阶段:

  1. 迁移前评估
    • 使用 DTS 工具对源端 Oracle 数据库进行评估。
    • 筛选出评估阶段直接报错的对象(如引用分区 REFERENCE PARTITION、复合触发器 COMPOUND TRIGGER 等),预先准备改写方案。
  2. 常规表与结构迁移(排除大表)
    • 使用 DTS 执行数据库对象与普通表数据的自动化迁移。
    • 将 1010 万行大表的数据(MIG_BIG_SALES_LINE)从 DTS 迁移工程中取消,仅创建表结构。
    • 实际迁移:除评估已发现的问题外,还会出现实际迁移中才触发的问题(如模式级 DDL 审计触发器在后续创建约束时激活导致报错,引发下游连锁DTS任务取消,比如表结构都迁移失败,后续数据迁移任务,索引任务都会连带取消)。
  3. 大表快速装载
    • 源端使用 SQLULDR2/dmfldr-OUTORA 导出文本(4 分片并行导出耗时 17.55 秒,吞吐 107.5 MB/s)。
    • 目标端使用 dmfldr 直接路径(DIRECT=TRUE)导入数据,耗时 75.75 秒(平均 13.3 万行/秒)。
    • 实测对比显示:DTS 直接搬运大表耗时 272 秒;采取大表单独装载的策略后耗时缩短约 72%。
  4. 失败分析与经验总结
    • 失败分析:区分评估阶段发现的语法问题与实际执行中暴露的问题。
    • 经验总结:基于本次实践感悟,总结出推荐的迁移工程顺序为:
      image.png

1.2 源端 Oracle 系统构建

源端基于 Oracle 26ai Free(FREEPDB1)构建,划分为业务(MIG_APP)、审计(MIG_AUDIT)和报表(MIG_RPT)三个 Schema:

Schema 业务职责 核心数据库对象与数据规模
MIG_APP OLTP 核心业务 客户(508行)、商品(40行)、订单(20,003行)、明细(60,005行)、库存、支付(14,001行)、发货(12,001行)、业务事件(200,008行);含 PKG_ORDER_API 等 3 个业务 Package。
MIG_AUDIT 审计与记录 变更审计表、验证结果表;包含跨 Schema 自治事务审计触发器与模式级 DDL 审计触发器。
MIG_RPT 报表与 ETL 销售事实表 FACT_SALES(42,002行)、ETL 包 PKG_ETL、物化视图及调度任务。

覆盖的复杂数据库对象:

  • 分区类型:Interval 范围分区、Range+Hash 复合分区、引用分区。
  • 高级索引:位图索引、带 UTC 函数的表达式索引、Oracle Text 全文索引。
  • 数据类型:Object Type、VARRAY、CLOB/BLOB、JSON 字段及约束。
  • 过程代码:6 个业务 Package(程序体超 700 行)、行级触发器、复合触发器及 DDL 触发器。

1.3 千万级大表(MIG_BIG_SALES_LINE)构建

为单独测试数据导出导入吞吐极限,建立千万级大表 MIG_APP.MIG_BIG_SALES_LINE

  • 数据规模:10,100,000 行,无 LOB 字段,文本大小约 1.88 GB。
  • 字段设计:共 16 列,包含主键流水(LINE_ID)、多租户(TENANT_ID)、订单与商品关联键、状态列、高精度金额列(NET_AMOUNT)、微秒时间戳(TIMESTAMP(6))、空值测试列(NULLABLE_TAG)和随机链路串(TRACE_TOKEN)。
  • 设计目的:将大表与常规迁移拆离,专门用于压测 SQLULDR2 并发导出与 dmfldr 直接路径导入吞吐,避免千万级数据装载占用 DTS 迁移资源。

1.4 目标端 DM8 的兼容性参数设计方案

目标端部署在 Windows 本地(实例 DM8ORA,端口 5237),参数设计如下:

1. 初始化参数(dminit)

参数 参数值 说明
PAGE_SIZE 32KB 数据页提升至 32 KB,扩大单页可存储行空间,降低 Oracle 迁移过程中因复杂表结构、大字段组合导致的 ROW TOO BIG 风险
EXTENT_SIZE 32 扩展簇大小,提高大块写入效率。
CASE_SENSITIVE Y 标识符区分大小写,对齐 Oracle 默认行为。
CHARSET 1 (UTF-8) 数据库字符集设为 UTF-8。(源库Oracle为AL32UTF8)
BLANK_PAD_MODE 1 尾部空格比较规则兼容 Oracle。

2. dm.ini Oracle 兼容参数

COMPATIBLE_MODE = 2 # 开启部分 Oracle 兼容模式 ORA_DATE_FMT = 1 # 对齐 Oracle 风格日期格式 ORA_REVERSE_MODE = 0 # 对齐 REVERSE 函数处理规则(按字节反转) JSON_MODE = 0 # 兼容 Oracle JSON 解析 DATETIME_FMT_MODE = 1 # 对齐日期时间隐式转换 CASE_COMPATIBLE_MODE = 1 # CASE/DECODE 数据类型推导兼容 PL_SQLCODE_COMPATIBLE = 1 # PL/SQL 异常返回 Oracle 风格 SQLCODE NUMBER_MODE = 1 # NUMBER、FLOAT 精度推导兼容 NVARCHAR_LENGTH_IN_CHAR = 1 # NCHAR/NVARCHAR2 按字符理解长度 LEGACY_SEQUENCE = 0 # 启用兼容版 Sequence 规则

2. DTS 迁移评估

2.1 评估结果与数据对比

使用 DTS 工具对源端数据库进行静态评估:

  • 评估指标:评估进度 100%,已评估对象 80 个,兼容率 97.5%。
  • 转换结果:完全兼容 10 个,转换后兼容 68 个,明确提示不兼容对象 2 个。
  • 综合兼容率:加入业务与系统 SQL 评估后综合兼容率 95.3%。

2.2 报错日志分析

评估日志中同时包含需要处理的业务不兼容项和非业务干扰项,经核实后分类处理如下:

  1. Identity 关联序列提取异常

    日志中有 10 个名称为 ISEQ$$_... 的序列提取失败,并返回 ORA-31603。此类名称通常对应 Oracle Identity 列关联的系统生成序列。应通过 DBA_TAB_IDENTITY_COLSALL_TAB_IDENTITY_COLS 核实其对应的父表、Identity 列及生成属性。如确认均为 Identity 列的关联序列,则不作为普通业务序列单独迁移,而应随父表的 Identity 定义一并转换。

  2. Oracle Text 全文索引内部辅助表

    DR$PRODUCT_MANUAL_CTX_IX$* 为 Oracle Text 在 PRODUCT 基表全文索引下自动生成的内部辅助表,不属于应用直接维护的普通业务表,因此不单独迁移其表结构和数据。迁移时应保留 PRODUCT 基表及业务数据,并根据源端全文索引定义、索引字段、分词规则、停用词、同步方式和相关查询语法,在 DM 端重新创建等价的全文索引,完成全文检索功能与结果验证。

  3. Oracle 系统监控 SQL

    日志中的 6 条 SQL 涉及 GV_$SQL_OPTIMIZER_ENV 等 Oracle 动态性能视图。经执行用户、客户端模块和 SQL 来源确认,如这些 SQL 由 Oracle 数据库、管理工具或监控组件产生,而非应用业务程序调用,则将其归类为非业务监控 SQL,不纳入应用 SQL 兼容率统计。

2.3 不兼容对象及改写方案

评估确认了 2 个真正的不兼容业务对象。分析思路、源端原SQL及目标端改写预案如下:

1. 包 PKG_ORDER_API

  • DTS报错提示:编译包体中的 capture_payment 过程时报错误号 -2007,提示“第 191 行第 64 列,FORMAT 附近语法分析出错”。因 DTS 未自动转换 Oracle 固有子句,导致残留代码解析失败。

  • 源端原SQL

    JSON_OBJECT( 'result' VALUE 'SUCCESS', 'lab' VALUE 'true' FORMAT JSON RETURNING CLOB )
  • 目标端改写思路

    • Oracle 语法:Oracle 的 'true' FORMAT JSON 表示虽然字面量写了单引号 'true',但显式声明对其作为 JSON 原生布尔值 true 渲染,输出为 {"result":"SUCCESS","lab":true}。但是达梦 JSON_OBJECT 语法不支持 Oracle 的 VALUE ... FORMAT JSON 子句,编译直接报 -2007 错。

    • DM 等价语法:采用 DM 原生成对参数写法,直接传入布尔字面量 TRUE 并转换为 CLOB:

      CAST( JSON_OBJECT( 'result', 'SUCCESS', 'lab', TRUE ) AS CLOB )

2. 复合触发器 TRG_ORDER_ITEM_AMOUNT_CT

  • DM 报错提示:编译触发器定义时报错误号 -2007,提示“第 3 行第 1 列,COMPOUND 附近语法分析出错”。根因是 DM8 不支持 Oracle COMPOUND TRIGGER 语法结构。

  • 源端原代码

    CREATE OR REPLACE TRIGGER "MIG_APP"."TRG_ORDER_ITEM_AMOUNT_CT" FOR INSERT OR UPDATE OR DELETE ON "MIG_APP"."SALES_ORDER_ITEM" COMPOUND TRIGGER TYPE order_set_t IS TABLE OF PLS_INTEGER INDEX BY VARCHAR2(50); g_order_ids order_set_t; AFTER EACH ROW IS BEGIN IF INSERTING OR UPDATING THEN g_order_ids(TO_CHAR(:NEW.ORDER_ID)) := 1; END IF; IF DELETING OR UPDATING THEN g_order_ids(TO_CHAR(:OLD.ORDER_ID)) := 1; END IF; END AFTER EACH ROW; AFTER STATEMENT IS BEGIN -- 循环遍历 g_order_ids 批量去重并更新 SALES_ORDER 金额 END AFTER STATEMENT; END TRG_ORDER_ITEM_AMOUNT_CT;
  • 目标端改写预案(语法分析与等价实现)

    • Oracle 语法简介:Oracle COMPOUND TRIGGER 允许在单一触发器内定义语句级和行级等多个阶段,并共享会话内存。此设计实现在批量更新订单明细时,先在行级收集订单 ID,在语句结束后一次性统一重算金额,既避免了重复计算开销,也避免了变异表(Mutating Table)冲突。

    • DM 兼容性说明:DM8 不解析 COMPOUND TRIGGER 结构。若简单改成行级触发器,每次更新一行就会重算一次整单金额,批量插入时不仅开销极大,还易触发变异表错误。

    • DM 等价代码实现:拆分为“1 个上下文状态包 + 3 个普通触发器”实现等价解耦:

      -- 1. 创建上下文状态包(包头与包体) CREATE OR REPLACE PACKAGE "MIG_APP"."PKG_ORDER_ITEM_TRG_CTX" AS PROCEDURE RESET_IDS; PROCEDURE ADD_ID(p_order_id NUMBER); PROCEDURE RECALC_ALL; END; / CREATE OR REPLACE PACKAGE BODY "MIG_APP"."PKG_ORDER_ITEM_TRG_CTX" AS TYPE order_set_t IS TABLE OF PLS_INTEGER INDEX BY VARCHAR2(50); g_order_ids order_set_t; PROCEDURE RESET_IDS IS BEGIN g_order_ids.DELETE; END; PROCEDURE ADD_ID(p_order_id NUMBER) IS BEGIN IF p_order_id IS NOT NULL THEN g_order_ids(TO_CHAR(p_order_id)) := 1; END IF; END; PROCEDURE RECALC_ALL IS l_key VARCHAR2(50) := g_order_ids.FIRST; BEGIN WHILE l_key IS NOT NULL LOOP UPDATE "MIG_APP"."SALES_ORDER" o SET (goods_amount, discount_amount, tax_amount, updated_at, lock_version) = (SELECT NVL(SUM(i.quantity * i.unit_price), 0), NVL(SUM(i.quantity * i.unit_price * i.discount_rate), 0), NVL(SUM(i.line_amount * i.tax_rate), 0), SYSTIMESTAMP, o.lock_version + 1 FROM "MIG_APP"."SALES_ORDER_ITEM" i WHERE i.order_id = o.order_id) WHERE o.order_id = TO_NUMBER(l_key); l_key := g_order_ids.NEXT(l_key); END LOOP; g_order_ids.DELETE; END; END; / -- 2. 语句前触发器:清空上下文状态 CREATE OR REPLACE TRIGGER "MIG_APP"."TRG_SOI_AMOUNT_BS" BEFORE INSERT OR UPDATE OR DELETE ON "MIG_APP"."SALES_ORDER_ITEM" FOR EACH STATEMENT BEGIN "MIG_APP"."PKG_ORDER_ITEM_TRG_CTX".RESET_IDS; END; / -- 3. 行级触发器:收集受影响的订单 ID CREATE OR REPLACE TRIGGER "MIG_APP"."TRG_SOI_AMOUNT_AR" AFTER INSERT OR UPDATE OR DELETE ON "MIG_APP"."SALES_ORDER_ITEM" FOR EACH ROW BEGIN IF INSERTING OR UPDATING THEN "MIG_APP"."PKG_ORDER_ITEM_TRG_CTX".ADD_ID(:NEW.ORDER_ID); END IF; IF DELETING OR UPDATING THEN "MIG_APP"."PKG_ORDER_ITEM_TRG_CTX".ADD_ID(:OLD.ORDER_ID); END IF; END; / -- 4. 语句后触发器:统一批量去重重算 CREATE OR REPLACE TRIGGER "MIG_APP"."TRG_SOI_AMOUNT_AS" AFTER INSERT OR UPDATE OR DELETE ON "MIG_APP"."SALES_ORDER_ITEM" FOR EACH STATEMENT BEGIN "MIG_APP"."PKG_ORDER_ITEM_TRG_CTX".RECALC_ALL; END; /

3. DTS 首轮迁移:多个任务报错分析

3.1 首轮迁移执行结果

在 DTS 静态迁移首轮执行中,共触发 277 个结构与数据迁移子任务(1010 万行大表只迁表结构,不在此阶段搬运数据):

  • 任务总数:277 个
  • 成功执行:238 个
  • 运行报错:28 个
  • 自动取消:11 个
  • 总耗时:59.115 秒

3.2 报错现象分析

结合 DTS 报错日志,28 个报错节点集中在以下 7 组场景:

  1. 引用分区表语法错误(错误号 -2007

    • 报错信息第 18 行第 2 列 [INTERVAL] 附近出现语法分析错误
    • 涉及对象SALES_ORDER_ITEMORDER_STATUS_HISTORY
    • 错误 SQL:DTS 导出的 DDL 中含有 PARTITION BY REFERENCE("ORDER_ID") INTERVAL(YES)
    • 分析:Oracle 的引用分区依赖外键从父表继承分区。DM8 暂不支持 PARTITION BY REFERENCEINTERVAL(YES) 的直接转换组合,导致建表 DDL 解析失败。
  2. 函数索引表达式不可用(错误号 -2169

    • 报错信息函数索引表达式包含非法列类型、不确定性函数、非静态方法或集函数
    • 涉及对象BUSINESS_EVENT_STATUS_IX(在 BUSINESS_EVENT 上)、CHANGE_AUDIT_OBJECT_IX
    • 错误 SQLCREATE INDEX ... ON BUSINESS_EVENT (PUBLISH_STATUS, SYS_EXTRACT_UTC(EVENT_TIME))
    • 分析:DM8 优化器在当前版本的函数索引列类型组合中,不接受 SYS_EXTRACT_UTC 动态时区函数作为可索引表达式。
  3. 间隔分区表位图索引不支持(错误号 -2937

    • 报错信息间隔分区表不支持的操作
    • 涉及对象FACT_SALES 表上的 CHANNEL_BIXSTATUS_BIXTENANT_BIX 三个索引
    • 错误 SQLCREATE BITMAP INDEX ... ON FACT_SALES(...) PARTITION BY RANGE ... INTERVAL(...)
    • 分析:基表 FACT_SALES 已是 Interval 动态间隔分区表,DTS 企图给位图索引单独再复制一套动态分区规则,DM8 引擎不支持在动态分区表上创建带显式 Interval 规则的位图分区索引。
  4. 物化视图快速刷新缺失日志(错误号 -2552

    • 报错信息表 PRODUCT 上不存在物化视图日志,无法进行快速刷新
    • 涉及对象MV_ACTIVE_PRODUCT
    • 错误 SQLCREATE MATERIALIZED VIEW ... REFRESH ON DEMAND WITH PRIMARY KEY FAST AS ...
    • 分析:物化视图声明了 FAST 增量刷新,但源端 PRODUCT 基表在达梦侧尚未建立物化视图日志(MLOG$),导致无法追踪增量变化。
  5. PKG_ORDER_API 的 JSON 语法报错(错误号 -2007

    • 包含 'lab' VALUE 'true' FORMAT JSON 固有语法(已在 2.3 节专项分析)。
  6. 触发器 TRG_ORDER_ITEM_AMOUNT_CT 复合触发器不兼容(错误号 -2007

    • 包含 COMPOUND TRIGGER 语法(已在 2.3 节专项分析)。
  7. 全局 DDL 审计触发器异常引发的连锁报错(错误号 -2007

    • 报错信息执行用户自定义触发器过程异常
    • 涉及对象TRG_DDL_AUDIT 及其阻断的 18 个 ALTER TABLE ... ADD CONSTRAINT 任务
    • 分析:DTS 较早创建了模式级 DDL 审计触发器 TRG_DDL_AUDIT。当 DTS 接着给其他已创建成功的表追加外键和 CHECK 约束时,触发器自动被激活;因触发器内部使用了源端特有事件接口或权限缺失,触发器异常终止并导致当前约束 DDL 回滚。

通过将报错日志按数据库依赖链重新整理,发现:

28 个报错节点 + 11 个取消节点
  ├── 18 个 DDL 报错:是 TRG_DDL_AUDIT 触发器在后台报错导致约束 DDL 整体回滚
  ├── 11 个取消任务:是 SALES_ORDER_ITEM 建表失败引起的下游主外键/约束自动取消
  └── 10 个独立结构错误:引用分区(2)、函数索引(2)、位图分区索引(3)、物化视图(1)、JSON包(1)、复合触发器(1)

结论:需要人工干预重构的结构与对象仅有 8 组。优先处理 DDL 审计触发器隔离与引用分区降级后,大部分级联错误将自动消除。


4. 复杂Oracle对象兼容性改造

4.1 引用分区降级与显式分区重构

  • DM 报错提示:DTS 执行建表 DDL 时抛出错误号 -2007,提示 第 18 行第 2 列 [INTERVAL] 附近出现语法分析错误

  • 源端原 SQL

    CREATE TABLE "MIG_APP"."SALES_ORDER_ITEM" ( "ORDER_ID" NUMBER NOT NULL, "LINE_NO" NUMBER(6,0) NOT NULL, "PRODUCT_ID" NUMBER NOT NULL, ... ) PARTITION BY REFERENCE("ORDER_ID") INTERVAL(YES);
  • 分析思路

    • Oracle 机制:Oracle 引用分区允许子表不显式保存分区键列,通过外键 ORDER_ID 继承父表 SALES_ORDER 的 Interval 分区规则。
    • DM 兼容性与改造思路:DM8 暂不支持 PARTITION BY REFERENCE 语法。风险最低的做法是先将子表降级为普通表,完全保留业务数据、主外键参照完整性及 DML 语义;若后续大表有按月分区裁剪或删除要求,再通过给子表显式增加 ORDER_DATE 分区列重构为 Range/Interval 分区。
  • 目标端等价改写代码

    -- 降级为普通表:保留结构、约束与数据完整性 CREATE TABLE "MIG_APP"."SALES_ORDER_ITEM" ( "ORDER_ID" NUMBER NOT NULL, "LINE_NO" NUMBER(6,0) NOT NULL, "PRODUCT_ID" NUMBER NOT NULL, "SKU_SNAPSHOT" VARCHAR2(40) NOT NULL, "NAME_SNAPSHOT" NVARCHAR2(200 CHAR) NOT NULL, "QUANTITY" NUMBER(18,4) NOT NULL, "UNIT_PRICE" NUMBER(18,4) NOT NULL, "DISCOUNT_RATE" NUMBER(7,6) DEFAULT 0 NOT NULL, "LINE_AMOUNT" NUMBER(20,4) AS (ROUND("QUANTITY" * "UNIT_PRICE" * (1 - "DISCOUNT_RATE"), 4)), "TAX_RATE" NUMBER(7,6) DEFAULT 0.13 NOT NULL, "ATTRIBUTES_JSON" CLOB ); ALTER TABLE "MIG_APP"."SALES_ORDER_ITEM" ADD CONSTRAINT "PK_SALES_ORDER_ITEM" PRIMARY KEY ("ORDER_ID", "LINE_NO"); ALTER TABLE "MIG_APP"."SALES_ORDER_ITEM" ADD CONSTRAINT "SOI_ORDER_FK" FOREIGN KEY ("ORDER_ID") REFERENCES "MIG_APP"."SALES_ORDER" ("ORDER_ID");

4.2 UTC 时间函数索引改写

  • DM 报错提示:DTS 创建索引时抛出错误号 -2169,提示 函数索引表达式包含非法列类型、不确定性函数、非静态方法或集函数

  • 源端原 SQL

    CREATE INDEX "BUSINESS_EVENT_STATUS_IX" ON "MIG_APP"."BUSINESS_EVENT" ( "PUBLISH_STATUS", SYS_EXTRACT_UTC("EVENT_TIME") );
  • 分析思路:

    • Oracle 机制:Oracle 函数索引在写入时将 SYS_EXTRACT_UTC(EVENT_TIME) 计算后的 UTC 时间值存入索引树,加速按 UTC 时间过滤的 SQL。
    • DM 兼容性与改造思路:DM8 暂不接受 SYS_EXTRACT_UTC 该类动态时区转换函数用于函数索引表达式。方案 A 为直接对 EVENT_TIME 原始时间列建复合索引,依靠DM 优化器评估范围;若必须针对 UTC 值做高频索引,方案 B 为向表增加物理列 EVENT_TIME_UTC 并使用触发器在写入时预计算。
  • 目标端等价改写代码

    -- 方案 A:直接对原时间列建普通索引 CREATE INDEX "BUSINESS_EVENT_STATUS_IX" ON "MIG_APP"."BUSINESS_EVENT" ("PUBLISH_STATUS", "EVENT_TIME"); -- 方案 B:物理派生列 + 触发器预计算 + 普通索引(适合UTC过滤) ALTER TABLE "MIG_APP"."BUSINESS_EVENT" ADD "EVENT_TIME_UTC" TIMESTAMP(6); CREATE OR REPLACE TRIGGER "MIG_APP"."TRG_BUSINESS_EVENT_UTC" BEFORE INSERT OR UPDATE OF "EVENT_TIME" ON "MIG_APP"."BUSINESS_EVENT" FOR EACH ROW BEGIN :NEW."EVENT_TIME_UTC" := SYS_EXTRACT_UTC(:NEW."EVENT_TIME"); END; / CREATE INDEX "BUSINESS_EVENT_STATUS_IX" ON "MIG_APP"."BUSINESS_EVENT" ("PUBLISH_STATUS", "EVENT_TIME_UTC");

4.3 间隔分区表位图索引改写

  • DM 报错提示:DTS 创建索引时抛出错误号 -2937,提示 间隔分区表不支持的操作

  • 源端原 SQL

    CREATE BITMAP INDEX "FACT_SALES_CHANNEL_BIX" ON "MIG_RPT"."FACT_SALES" ("CHANNEL_ID") PARTITION BY RANGE("SALE_DATE") INTERVAL(NUMTOYMINTERVAL(1, 'MONTH'));
  • 分析思路

    • Oracle 机制:Oracle 支持在 Interval 分区表场景下创建部分类型的位图索引结构。
    • DM 兼容性与改造思路:DM8 引擎不支持在动态分区表上创建带显式 PARTITION BY ... INTERVAL 规则的位图索引。由于该表涉及写入与高频 OLAP 查询,位图索引容易导致写入锁定开销,改写预案将其转换为标准 B-Tree 索引,交由 DM 物理分区表机制自动管理。
  • 目标端等价改写代码

    -- 转换为标准 B-Tree 索引,去除多余的显式分区子句 CREATE INDEX "FACT_SALES_CHANNEL_IX" ON "MIG_RPT"."FACT_SALES" ("CHANNEL_ID"); CREATE INDEX "FACT_SALES_TENANT_DATE_IX" ON "MIG_RPT"."FACT_SALES" ("TENANT_ID", "SALE_DATE");

4.4 物化视图快速刷新改写

  • DM 报错提示:DTS 创建物化视图时抛出错误号 -2552,提示 表 PRODUCT 上不存在物化视图日志,无法进行快速刷新

  • 源端原 SQL

    CREATE MATERIALIZED VIEW "MIG_RPT"."MV_ACTIVE_PRODUCT" BUILD IMMEDIATE REFRESH ON DEMAND WITH PRIMARY KEY FAST AS SELECT PRODUCT_ID, SKU, PRODUCT_NAME, LIST_PRICE, STATUS FROM "MIG_APP"."PRODUCT" WHERE STATUS = 'ACTIVE';
  • 分析思路与语法/物理特性对比

    • Oracle 机制REFRESH FAST 依赖基表上的物化视图日志(MLOG$)记录增量 DML。
    • DM 兼容性与改造思路:因为 DTS 未自动迁移物化视图日志,导致新建物化视图报错。对于更新频率不高、行数较小的商品表,推荐直接改为 REFRESH COMPLETE ON DEMAND(完全刷新);若业务坚持要求增量刷新,则需先在基表建立物化视图日志并授权。
  • 目标端等价改写代码

    -- 方案 A:改为 COMPLETE 完全刷新 CREATE MATERIALIZED VIEW "MIG_RPT"."MV_ACTIVE_PRODUCT" BUILD IMMEDIATE REFRESH COMPLETE ON DEMAND AS SELECT PRODUCT_ID, SKU, PRODUCT_NAME, LIST_PRICE, STATUS FROM "MIG_APP"."PRODUCT" WHERE STATUS = 'ACTIVE'; -- 方案 B:先建 MLOG 日志,再建 FAST 增量物化视图 CREATE MATERIALIZED VIEW LOG ON "MIG_APP"."PRODUCT" WITH PRIMARY KEY; GRANT SELECT ON "MIG_APP"."PRODUCT" TO "MIG_RPT"; CREATE MATERIALIZED VIEW "MIG_RPT"."MV_ACTIVE_PRODUCT" BUILD IMMEDIATE REFRESH FAST ON DEMAND WITH PRIMARY KEY AS SELECT PRODUCT_ID, SKU, PRODUCT_NAME, LIST_PRICE, STATUS FROM "MIG_APP"."PRODUCT" WHERE STATUS = 'ACTIVE';

4.5 DDL 审计触发器隔离

  • DM 报错提示:模式级 DDL 审计触发器导致后续 18 个 ALTER TABLE ... ADD CONSTRAINT 任务报错误号 -2007执行用户自定义触发器过程异常),并使约束 DDL 产生链式回滚。

  • 源端原 SQL

    CREATE OR REPLACE TRIGGER "MIG_AUDIT"."TRG_DDL_AUDIT" AFTER DDL ON SCHEMA BEGIN INSERT INTO DDL_AUDIT (...) VALUES (ora_sysevent, ora_dict_obj_name, ...); END; /
  • **分析思路:

    • 机制:模式级 DDL 触发器在用户执行任何 CREATE/ALTER/DROP 时强行同步拦截。由于相关系统包权限未完全补齐,触发表在结构尚未稳定前频繁报错,导致正常 DDL 被一并回滚。
    • 隔离方案:在迁移初期先停用或延后创建 TRG_DDL_AUDIT;待全部表结构、主外键约束、索引及数据导入完毕后,再重构并单独启用审计触发器。
  • 目标端隔离与重构代码

    -- 阶段 1:结构迁移期先禁用审计触发器 ALTER TRIGGER "MIG_AUDIT"."TRG_DDL_AUDIT" DISABLE; -- 阶段 2:完成所有结构与数据迁移后,补齐权限并重构启用 CREATE OR REPLACE TRIGGER "MIG_AUDIT"."TRG_DDL_AUDIT" AFTER DDL ON SCHEMA BEGIN -- 增加异常捕获,确保即使审计日志写入异常也不阻断核心业务 DDL BEGIN INSERT INTO "MIG_AUDIT"."DDL_AUDIT" ( OPER_USER, DDL_TYPE, OBJECT_TYPE, OBJECT_NAME, DDL_TIME ) VALUES ( SYS_CONTEXT('USERENV', 'SESSION_USER'), IS_ALTERING_OR_CREATING(), DICTIONARY_OBJ_TYPE, DICTIONARY_OBJ_NAME, SYSTIMESTAMP ); EXCEPTION WHEN OTHERS THEN NULL; -- 生产隔离:防止审计写失败影响主 DDL END; END; /

5. 千万级大表迁移(MIG_BIG_SALES_LINE)

1010 万行大表(数据量 1.88 GB)的方案、SQLULDR2 4 进程并行导出、dmfldr DIRECT=TRUE 路径快速装载、控制文件(CTL)写法及数据一致性 Hash 校验,参阅独立专题文档:

Oracle 到 DM8 大表高速装载


6. 数据一致性验证

6.1 分层验证策略

为确保数据无丢失、未截断且语义一致,采取以下分层验证方法:

验证层次 校验方法 验证目标
表级行数 执行全表 COUNT(*) 计数 确认两端表记录总数一致,无遗漏或重复
边界数值 统计主键 MIN/MAX 及数值列 SUM 确认数值字段无截断、精度无损且范围对齐
日期时间 显式格式串转换比对 DATETIMESTAMP 排除默认文本格式差异,确认底层时间戳一致
全表 Hash 按固定主键排序并计算结果集 SHA-256 批量校验全表每行每列数据的保持一致性

6.2 大表校验 SQL

对 1010 万行大表执行组合校验 SQL,核对计数、主键范围、数值汇总及时间边界:

SELECT COUNT(*) AS row_count, MIN(line_id) AS min_id, MAX(line_id) AS max_id, SUM(quantity) AS sum_quantity, SUM(net_amount) AS sum_net_amount, SUM(CASE WHEN nullable_tag IS NULL THEN 1 ELSE 0 END) AS null_tag_rows, MIN(event_time) AS min_event_time, MAX(event_time) AS max_event_time FROM MIG_APP.mig_big_sales_line;

6.3 DTS 数据对比报告

DTS 工具自动对比报告中提示90处“不一致”,分析确认底层存储数据保持一致,属于工具按默认字符串比对时格式渲染不同的不同

  1. DATE 格式差异(32 处)
    • 现象:Oracle 转换为文本 2026-08-04 11:22:33,DM8 驱动默认转换追加毫秒后缀 2026-08-04 11:22:33.000
    • 分析:两者存储的时间值完全相同(精确到秒),仅为 DM8 驱动默认输出格式带 .000 尾数引发文本不匹配。
  2. TIMESTAMP WITH TIME ZONE 格式差异(58 处)
    • 现象:Oracle 表示零时区输出字母 Z(如 ...123456 Z),DM8 默认输出数字偏移 +00:00(如 ...123456 +00:00)。
    • 分析:两者代表的时区和绝对时间点保持一致,仅为 Z+00:00 的时区文本表示形态不同。

7. 业务功能与 SQL 语义一致性验证

在相同数据下,通过 Java JDBC 统一测试工具在 Oracle 与 DM8 侧同步运行 9 组核心业务 SQL/函数及 1 组存储过程链。将结果集在显式排序与格式规范化后计算 SHA-256 哈希值,比对语义一致性。

7.1 核心查询与函数验证 (Q01 ~ Q09)

1. Q01:订单详情多表关联

  • 业务含义:订单中心批量查询指定主键区间(100000 ~ 100500)的订单头、客户邮箱、发货仓库、明细汇总及成功支付金额。

  • 测试语句

    SELECT o.order_id, o.order_no, o.order_status, o.customer_id, c.email, w.warehouse_name, COUNT(i.line_no) AS line_count, SUM(i.quantity) AS item_quantity, SUM(i.line_amount) AS line_net_amount, MAX(p.payment_amount) AS payment_amount FROM MIG_APP.sales_order o JOIN MIG_APP.customer c ON c.customer_id = o.customer_id JOIN MIG_APP.warehouse w ON w.warehouse_id = o.warehouse_id JOIN MIG_APP.sales_order_item i ON i.order_id = o.order_id LEFT JOIN ( SELECT order_id, SUM(amount) AS payment_amount FROM MIG_APP.payment WHERE payment_status = 'SUCCESS' GROUP BY order_id ) p ON p.order_id = o.order_id WHERE o.order_id BETWEEN 100000 AND 100500 GROUP BY o.order_id, o.order_no, o.order_status, o.customer_id, c.email, w.warehouse_name ORDER BY o.order_id;
  • 比对结果:两端均返回 3 行,结果集 SHA-256(12e80f...)保持一致【PASS】。

2. Q02:租户客户价值排行

  • 业务含义:计算租户(tenant_id=1)内客户订单数、生命周期价值、最近下单时间及价值排名,覆盖视图、聚合与 DENSE_RANK() 窗口函数。

  • 测试语句

    SELECT customer_id, tenant_id, email, order_count, lifetime_value, TO_CHAR(last_order_date, 'YYYY-MM-DD HH24:MI:SS') AS last_order_time, tenant_value_rank FROM MIG_APP.v_customer_order_kpi WHERE tenant_id = 1 ORDER BY tenant_value_rank, customer_id FETCH FIRST 100 ROWS ONLY;
  • 比对结果:两端均返回 100 行,结果集 SHA-256 保持一致【PASS】。

3. Q03:月度渠道销售看板

  • 业务含义:按月份输出 APP、WEB、门店和合作伙伴四个渠道的有效订单金额,覆盖分组和 PIVOT 行转列语法。

  • 测试语句

    SELECT order_month, app_amount, web_amount, store_amount, partner_amount FROM MIG_APP.v_month_channel_pivot ORDER BY order_month;
  • 比对结果:两端均返回 30 行,结果集 SHA-256(a55ab0...)保持一致【PASS】。

4. Q04:商品分类树与商品数

  • 业务含义:递归输出商品分类层级、完整路径、叶子节点标记及分类下的商品数量,覆盖 CONNECT BY 树状查询视图与子查询。

  • 测试语句

    SELECT t.category_id, t.category_code, t.category_name, t.tree_level, t.full_path, t.is_leaf, (SELECT COUNT(*) FROM MIG_APP.product p WHERE p.category_id = t.category_id) AS product_count FROM MIG_APP.v_category_tree t ORDER BY t.full_path, t.category_id;
  • 比对结果:两端均返回 10 行,结果集 SHA-256 保持一致【PASS】。

5. Q05:JSON 商品标签展开

  • 业务含义:把商品 JSON 属性中的标签数组展开为关系结构,覆盖 JSON_TABLE 函数、数组索引号及中文 JSON 字符串解析。

  • 测试语句

    SELECT product_id, sku, product_name, tag_no, tag_name FROM MIG_APP.v_product_json_tag ORDER BY product_id, tag_no;
  • 比对结果:两端均返回 120 行,结果集 SHA-256 保持一致【PASS】。

6. Q06:待发布业务事件批量拉取

  • 业务含义:模拟 Outbox 事件发布后台任务,按事件生成时间排序抓取前 1000 条状态为 NEW 的待处理事件。

  • 测试语句

    SELECT event_id, aggregate_type, aggregate_id, event_type, publish_status FROM MIG_APP.business_event WHERE publish_status = 'NEW' ORDER BY event_time, event_id FETCH FIRST 1000 ROWS ONLY;
  • 比对结果:两端均返回 1,000 行,结果集 SHA-256(801e61...)保持一致【PASS】。

7. Q07:千万行销售明细全量聚合

  • 业务含义:对 1010 万行大表 MIG_BIG_SALES_LINE 按租户和订单状态分组进行全表统计,覆盖大表全表扫描、SUM/COUNT 聚合与高精度时间边界计算。

  • 测试语句

    SELECT tenant_id, order_status, COUNT(*) AS row_count, SUM(quantity) AS quantity_sum, SUM(net_amount) AS net_amount_sum, TO_CHAR(MIN(event_time), 'YYYY-MM-DD HH24:MI:SS.FF6') AS min_event_time, TO_CHAR(MAX(event_time), 'YYYY-MM-DD HH24:MI:SS.FF6') AS max_event_time FROM MIG_APP.mig_big_sales_line GROUP BY tenant_id, order_status ORDER BY tenant_id, order_status;
  • 比对结果:两端均返回 60 行,结果集 SHA-256(fbf86f...)保持一致【PASS】。

8. Q08:定价包函数批量计算

  • 业务含义:对全部 60,005 条订单明细批量调用 Package 函数 pkg_pricing.line_amountpkg_pricing.tax_rate,验证金额计算与跨表关联函数逻辑。

  • 测试语句

    SELECT COUNT(*) AS row_count, SUM(MIG_APP.pkg_pricing.line_amount(unit_price, quantity, discount_rate)) AS amount_sum, SUM(MIG_APP.pkg_pricing.tax_rate(product_id)) AS tax_rate_sum FROM MIG_APP.sales_order_item;
  • 比对结果:两端均返回 1 行,金额与税率汇总 SHA-256 保持一致【PASS】。

9. Q09:库存包函数批量查询

  • 业务含义:对全部 40 条库存记录调用 Package 函数 pkg_inventory.available_qty,验证包内单行逻辑计算与结果累加。

  • 测试语句

    SELECT COUNT(*) AS row_count, SUM(MIG_APP.pkg_inventory.available_qty(warehouse_id, product_id)) AS available_sum FROM MIG_APP.inventory;
  • 比对结果:两端均返回 1 行,可用库存汇总 SHA-256 保持一致【PASS】。


7.2 存储过程、触发器与事务一致性测试 (P01)

  • 业务含义:模拟订单创建后的库存预占(reserve_stock)与订单取消后的库存释放(release_stock),验证包过程、行级排他锁、库存流水写入、触发器跨 Schema 自治事务审计以及最外层事务回滚能力。

  • 测试代码

    BEGIN MIG_APP.pkg_inventory.reserve_stock(11, 10001, 100000, 1, SYSTIMESTAMP); MIG_APP.pkg_inventory.release_stock(11, 10001, 100000, 1, SYSTIMESTAMP); END;
  • 执行过程与校验逻辑

    1. 过程内调用行锁扣减库存,INVENTORY.VERSION_NO 版本号在事务内递增 2;
    2. INVENTORY_TXN 自动插入一条 RESERVE 与一条 RELEASE 流水记录;
    3. INVENTORY 更新同时触发 TRG_INVENTORY_AU,以自治事务形式向 MIG_AUDIT.CHANGE_AUDIT 写入 2 条审计日志;
    4. 测试程序主动触发外层事务 ROLLBACK
  • 比对结果

    • 事务回滚效果:两端 INVENTORY 库存数、版本号及 INVENTORY_TXN 流水记录均 100% 恢复至执行前初始状态;
    • 自治事务生效:两端 MIG_AUDIT.CHANGE_AUDIT 表中均准确保留了 2 次自治事务产生的审计记录(7 轮测试共生成 18 条审计日志);
    • 结论:DM8 在存储过程、自治事务与外层回滚组合场景下的行为与 Oracle 保持一致【PASS】。

8. 迁移总结

  1. 梳理报错逻辑,优先解决核心报错

    • DTS 迁移过程中常出现大量报错和任务取消。这些大多是上游对象缺失或失败引发的连锁反应。
    • 应当先梳理报错之间的依赖关系,找出最核心的报错点(如全局触发器、模式依赖或基表缺失)。核心对象修复后,部分由依赖关系导致的级联失败任务可恢复执行。
  2. 复杂的工程迁移最好按四层顺序推进

    • 仅将大表单独拆出来迁移是不够的,还应该继续解耦:

      • 第一层(基础对象与表结构): 创建 Schema、用户、自定义 TYPE 等基础依赖对象,然后创建空表。表阶段通常不创建主键、唯一约束、外键、索引和触发器,但保留字段级属性(NOT NULL、DEFAULT 等)。

        第二层(数据装载): 集中资源完成全量数据迁移,普通表采用 DTS,大表采用并行分片、快速装载等方式。

        第三层(性能对象): 数据装载完成后创建主键、唯一约束和索引,避免导入过程中持续维护索引结构。

        第四层(业务逻辑对象): 最后迁移外键、CHECK 约束、视图、函数、过程、包、触发器等依赖复杂的高级对象。

    • DTS支持在一个迁移工程下,建立一个迁移组,可以在这个组下建四个迁移工程,进行分层迁移。处理起来也更清晰一些,迁移报错的分析也更容易。

  3. 区分语法兼容与对象改造

    • 数据库兼容模式主要解决常见 SQL 语法,无法自动处理 Oracle 特有的物理特性(如特殊分区、位图索引)或复杂 PL/SQL 语法(如复合触发器)。对这类对象要明确改造成本,以“保证业务逻辑一致”为原则进行手工改造。
评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服