5. 千万级大表迁移(MIG_BIG_SALES_LINE)
本案例验证一套包含复杂对象和千万级数据的 Oracle 数据库向达梦 DM8 迁移的全过程。实测过程分为静态评估、常规迁移、大表装载及失败分析四个阶段:
REFERENCE PARTITION、复合触发器 COMPOUND TRIGGER 等),预先准备改写方案。MIG_BIG_SALES_LINE)从 DTS 迁移工程中取消,仅创建表结构。SQLULDR2/dmfldr-OUTORA 导出文本(4 分片并行导出耗时 17.55 秒,吞吐 107.5 MB/s)。dmfldr 直接路径(DIRECT=TRUE)导入数据,耗时 75.75 秒(平均 13.3 万行/秒)。源端基于 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、物化视图及调度任务。 |
覆盖的复杂数据库对象:
为单独测试数据导出导入吞吐极限,建立千万级大表 MIG_APP.MIG_BIG_SALES_LINE:
LINE_ID)、多租户(TENANT_ID)、订单与商品关联键、状态列、高精度金额列(NET_AMOUNT)、微秒时间戳(TIMESTAMP(6))、空值测试列(NULLABLE_TAG)和随机链路串(TRACE_TOKEN)。目标端部署在 Windows 本地(实例 DM8ORA,端口 5237),参数设计如下:
| 参数 | 参数值 | 说明 |
|---|---|---|
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。 |
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 规则
使用 DTS 工具对源端数据库进行静态评估:
评估日志中同时包含需要处理的业务不兼容项和非业务干扰项,经核实后分类处理如下:
Identity 关联序列提取异常
日志中有 10 个名称为 ISEQ$$_... 的序列提取失败,并返回 ORA-31603。此类名称通常对应 Oracle Identity 列关联的系统生成序列。应通过 DBA_TAB_IDENTITY_COLS 或 ALL_TAB_IDENTITY_COLS 核实其对应的父表、Identity 列及生成属性。如确认均为 Identity 列的关联序列,则不作为普通业务序列单独迁移,而应随父表的 Identity 定义一并转换。
Oracle Text 全文索引内部辅助表
DR$PRODUCT_MANUAL_CTX_IX$* 为 Oracle Text 在 PRODUCT 基表全文索引下自动生成的内部辅助表,不属于应用直接维护的普通业务表,因此不单独迁移其表结构和数据。迁移时应保留 PRODUCT 基表及业务数据,并根据源端全文索引定义、索引字段、分词规则、停用词、同步方式和相关查询语法,在 DM 端重新创建等价的全文索引,完成全文检索功能与结果验证。
Oracle 系统监控 SQL
日志中的 6 条 SQL 涉及 GV_$SQL_OPTIMIZER_ENV 等 Oracle 动态性能视图。经执行用户、客户端模块和 SQL 来源确认,如这些 SQL 由 Oracle 数据库、管理工具或监控组件产生,而非应用业务程序调用,则将其归类为非业务监控 SQL,不纳入应用 SQL 兼容率统计。
评估确认了 2 个真正的不兼容业务对象。分析思路、源端原SQL及目标端改写预案如下:
PKG_ORDER_APIDTS报错提示:编译包体中的 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 )
TRG_ORDER_ITEM_AMOUNT_CTDM 报错提示:编译触发器定义时报错误号 -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;
/
在 DTS 静态迁移首轮执行中,共触发 277 个结构与数据迁移子任务(1010 万行大表只迁表结构,不在此阶段搬运数据):
结合 DTS 报错日志,28 个报错节点集中在以下 7 组场景:
引用分区表语法错误(错误号 -2007)
第 18 行第 2 列 [INTERVAL] 附近出现语法分析错误SALES_ORDER_ITEM、ORDER_STATUS_HISTORYPARTITION BY REFERENCE("ORDER_ID") INTERVAL(YES)。PARTITION BY REFERENCE 与 INTERVAL(YES) 的直接转换组合,导致建表 DDL 解析失败。函数索引表达式不可用(错误号 -2169)
函数索引表达式包含非法列类型、不确定性函数、非静态方法或集函数BUSINESS_EVENT_STATUS_IX(在 BUSINESS_EVENT 上)、CHANGE_AUDIT_OBJECT_IXCREATE INDEX ... ON BUSINESS_EVENT (PUBLISH_STATUS, SYS_EXTRACT_UTC(EVENT_TIME))SYS_EXTRACT_UTC 动态时区函数作为可索引表达式。间隔分区表位图索引不支持(错误号 -2937)
间隔分区表不支持的操作FACT_SALES 表上的 CHANNEL_BIX、STATUS_BIX、TENANT_BIX 三个索引CREATE BITMAP INDEX ... ON FACT_SALES(...) PARTITION BY RANGE ... INTERVAL(...)FACT_SALES 已是 Interval 动态间隔分区表,DTS 企图给位图索引单独再复制一套动态分区规则,DM8 引擎不支持在动态分区表上创建带显式 Interval 规则的位图分区索引。物化视图快速刷新缺失日志(错误号 -2552)
表 PRODUCT 上不存在物化视图日志,无法进行快速刷新MV_ACTIVE_PRODUCTCREATE MATERIALIZED VIEW ... REFRESH ON DEMAND WITH PRIMARY KEY FAST AS ...FAST 增量刷新,但源端 PRODUCT 基表在达梦侧尚未建立物化视图日志(MLOG$),导致无法追踪增量变化。包 PKG_ORDER_API 的 JSON 语法报错(错误号 -2007)
'lab' VALUE 'true' FORMAT JSON 固有语法(已在 2.3 节专项分析)。触发器 TRG_ORDER_ITEM_AMOUNT_CT 复合触发器不兼容(错误号 -2007)
COMPOUND TRIGGER 语法(已在 2.3 节专项分析)。全局 DDL 审计触发器异常引发的连锁报错(错误号 -2007)
执行用户自定义触发器过程异常TRG_DDL_AUDIT 及其阻断的 18 个 ALTER TABLE ... ADD CONSTRAINT 任务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 审计触发器隔离与引用分区降级后,大部分级联错误将自动消除。
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);
分析思路:
ORDER_ID 继承父表 SALES_ORDER 的 Interval 分区规则。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");
DM 报错提示:DTS 创建索引时抛出错误号 -2169,提示 函数索引表达式包含非法列类型、不确定性函数、非静态方法或集函数。
源端原 SQL:
CREATE INDEX "BUSINESS_EVENT_STATUS_IX"
ON "MIG_APP"."BUSINESS_EVENT" (
"PUBLISH_STATUS",
SYS_EXTRACT_UTC("EVENT_TIME")
);
分析思路:
SYS_EXTRACT_UTC(EVENT_TIME) 计算后的 UTC 时间值存入索引树,加速按 UTC 时间过滤的 SQL。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");
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'));
分析思路:
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");
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';
分析思路与语法/物理特性对比:
REFRESH FAST 依赖基表上的物化视图日志(MLOG$)记录增量 DML。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';
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;
/
**分析思路:
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;
/
1010 万行大表(数据量 1.88 GB)的方案、SQLULDR2 4 进程并行导出、dmfldr DIRECT=TRUE 路径快速装载、控制文件(CTL)写法及数据一致性 Hash 校验,参阅独立专题文档:
为确保数据无丢失、未截断且语义一致,采取以下分层验证方法:
| 验证层次 | 校验方法 | 验证目标 |
|---|---|---|
| 表级行数 | 执行全表 COUNT(*) 计数 |
确认两端表记录总数一致,无遗漏或重复 |
| 边界数值 | 统计主键 MIN/MAX 及数值列 SUM |
确认数值字段无截断、精度无损且范围对齐 |
| 日期时间 | 显式格式串转换比对 DATE 与 TIMESTAMP |
排除默认文本格式差异,确认底层时间戳一致 |
| 全表 Hash | 按固定主键排序并计算结果集 SHA-256 | 批量校验全表每行每列数据的保持一致性 |
对 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;
DTS 工具自动对比报告中提示90处“不一致”,分析确认底层存储数据保持一致,属于工具按默认字符串比对时格式渲染不同的不同:
DATE 格式差异(32 处)
2026-08-04 11:22:33,DM8 驱动默认转换追加毫秒后缀 2026-08-04 11:22:33.000。.000 尾数引发文本不匹配。TIMESTAMP WITH TIME ZONE 格式差异(58 处)
Z(如 ...123456 Z),DM8 默认输出数字偏移 +00:00(如 ...123456 +00:00)。Z 与 +00:00 的时区文本表示形态不同。在相同数据下,通过 Java JDBC 统一测试工具在 Oracle 与 DM8 侧同步运行 9 组核心业务 SQL/函数及 1 组存储过程链。将结果集在显式排序与格式规范化后计算 SHA-256 哈希值,比对语义一致性。
业务含义:订单中心批量查询指定主键区间(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】。
业务含义:计算租户(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】。
业务含义:按月份输出 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】。
业务含义:递归输出商品分类层级、完整路径、叶子节点标记及分类下的商品数量,覆盖 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】。
业务含义:把商品 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】。
业务含义:模拟 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】。
业务含义:对 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】。
业务含义:对全部 60,005 条订单明细批量调用 Package 函数 pkg_pricing.line_amount 与 pkg_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】。
业务含义:对全部 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】。
业务含义:模拟订单创建后的库存预占(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;
执行过程与校验逻辑:
INVENTORY.VERSION_NO 版本号在事务内递增 2;INVENTORY_TXN 自动插入一条 RESERVE 与一条 RELEASE 流水记录;INVENTORY 更新同时触发 TRG_INVENTORY_AU,以自治事务形式向 MIG_AUDIT.CHANGE_AUDIT 写入 2 条审计日志;ROLLBACK。比对结果:
INVENTORY 库存数、版本号及 INVENTORY_TXN 流水记录均 100% 恢复至执行前初始状态;MIG_AUDIT.CHANGE_AUDIT 表中均准确保留了 2 次自治事务产生的审计记录(7 轮测试共生成 18 条审计日志);梳理报错逻辑,优先解决核心报错:
复杂的工程迁移最好按四层顺序推进:
仅将大表单独拆出来迁移是不够的,还应该继续解耦:
第一层(基础对象与表结构): 创建 Schema、用户、自定义 TYPE 等基础依赖对象,然后创建空表。表阶段通常不创建主键、唯一约束、外键、索引和触发器,但保留字段级属性(NOT NULL、DEFAULT 等)。
第二层(数据装载): 集中资源完成全量数据迁移,普通表采用 DTS,大表采用并行分片、快速装载等方式。
第三层(性能对象): 数据装载完成后创建主键、唯一约束和索引,避免导入过程中持续维护索引结构。
第四层(业务逻辑对象): 最后迁移外键、CHECK 约束、视图、函数、过程、包、触发器等依赖复杂的高级对象。
DTS支持在一个迁移工程下,建立一个迁移组,可以在这个组下建四个迁移工程,进行分层迁移。处理起来也更清晰一些,迁移报错的分析也更容易。
区分语法兼容与对象改造:
文章
阅读量
获赞
