数据库基础体系 · 第 113/139 篇。文章以各产品官方稳定版本的公开语义为准;示例会明确引擎、事务与部署边界。

Oracle Data Pump 与迁移:导出导入、字符集、校验和停机窗口

Oracle Data Pump 是 Oracle 提供的逻辑数据移动工具,主要由 expdp(Export Data Pump)和 impdp(Import Data Pump)组成。它适合在数据库、Schema、表、表空间等逻辑边界上导出和导入对象及数据。

它解决的是“如何把逻辑数据库对象搬到另一个 Oracle 数据库”,而不是所有形式的备份、复制或高可用问题。因此,判断 Data Pump 是否适合一次迁移,至少要同时回答以下问题:

  1. 导出结果在什么事务视图上保持一致?
  2. 导入时对象、数据、索引、约束和权限如何重建?
  3. 源库和目标库的字符集是否兼容?
  4. dump 文件传输过程中如何校验?
  5. 校验通过是否足以证明迁移正确?
  6. 源库在导出期间继续写入时,停机窗口到底发生在哪里?
  7. Data Pump 与 RMAN、Data Guard、RAC、在线变更和 SQLite 的边界分别是什么?

一、Data Pump 搬运的到底是什么

1. 逻辑对象,而不是数据库物理副本

Data Pump 导出的内容通常包括:

  • 表、分区表、索引组织表;
  • 表数据;
  • 索引定义;
  • 约束、触发器、序列、同义词;
  • 视图、存储过程、函数、包;
  • 用户、角色、系统权限和对象权限;
  • 表空间配额等元数据;
  • 某些数据库对象附带的统计信息。

这些内容最终以逻辑描述和数据记录的形式写入 dump 文件。导入时,impdp 在目标库中执行相应的 DDL、插入数据、创建索引和恢复约束。

因此,导出文件不是以下任何一种东西:

  • 数据文件的物理副本;
  • RMAN backup piece;
  • redo 或 archived log;
  • Data Guard 的 standby 数据库;
  • 可被 SQLite 直接读取的数据库文件;
  • 可以直接挂载为目标 Oracle 数据库的数据文件。

Data Pump 会重建对象,但不会把源库的数据库身份、数据文件布局、DBID、实例参数和所有物理状态原样复制过去。

2. Data Pump 的基本数据流

一个典型的迁移流程如下:

源库对象和数据
      │
      ▼
expdp 数据库服务器进程
      │
      ├── 读取元数据
      ├── 通过一致性读读取数据
      ├── 生成 dump 文件
      └── 写入 Data Pump master table 和日志
                  │
                  ▼
             dump 文件
                  │
             文件传输与校验
                  │
                  ▼
目标库 impdp 数据库服务器进程
      │
      ├── 读取 master table
      ├── 创建或匹配目标对象
      ├── 装载表数据
      ├── 创建索引和约束
      └── 记录错误、跳过项和作业状态

expdpimpdp 是客户端命令,但真正执行大量工作的通常是数据库服务器端的 Data Pump worker。DIRECTORY 参数指向的不是客户端当前目录,而是数据库中的 DIRECTORY 对象。该对象映射到数据库服务器能够访问的文件系统路径。

例如:

CREATE OR REPLACE DIRECTORY dp_dir AS '/u01/app/oracle/dpump';

GRANT READ, WRITE ON DIRECTORY dp_dir TO APP_ADMIN;

这里的 /u01/app/oracle/dpump 必须由 Oracle 数据库服务器进程访问,而不是仅仅由执行 expdp 的客户端访问。

在 RAC 中,若 Data Pump worker 可能运行在多个实例上,目录对应的文件系统必须被相关实例一致访问。把目录建在单个节点的本地磁盘上,而 Data Pump worker 被调度到其他节点,是常见的失败原因。共享文件系统、集群文件系统或明确的实例绑定策略都必须纳入设计。


二、导出模式和导入边界

Data Pump 有多个导出模式,模式决定了对象范围和权限要求。

1. Schema 模式

Schema 模式适合迁移一个或多个业务 Schema:

expdp app_admin/password@SRCDB \
  directory=DP_DIR \
  dumpfile=app_%U.dmp \
  logfile=app_export.log \
  schemas=APP \
  filesize=10G \
  parallel=4

这里:

  • schemas=APP 表示导出 APP Schema;
  • %U 让 Data Pump 生成多个文件名,例如 app_01.dmpapp_02.dmp
  • filesize=10G 限制单个 dump 文件大小;
  • parallel=4 允许多个 worker 工作,但通常需要多个 dump 文件才能有效并行写入;
  • logfile 是数据库服务器端目录中的日志文件。

导入到目标库:

impdp app_admin/password@DESTDB \
  directory=DP_DIR \
  dumpfile=app_%U.dmp \
  logfile=app_import.log \
  schemas=APP

若目标 Schema 名称不同:

impdp system/password@DESTDB \
  directory=DP_DIR \
  dumpfile=app_%U.dmp \
  logfile=app_import.log \
  remap_schema=APP:APP_NEW

REMAP_SCHEMA 只改变对象归属,不会自动解决表空间、字符集、外部系统配置或应用连接串问题。

2. Table 模式

只迁移部分表时可以使用:

expdp app_admin/password@SRCDB \
  directory=DP_DIR \
  dumpfile=orders.dmp \
  logfile=orders_export.log \
  tables=APP.ORDERS,APP.ORDER_ITEMS

需要注意,单独导出表可能无法自然携带整个应用所需的依赖对象。表上的索引和约束可以导出,但关联的类型、序列、包、同义词或外部依赖可能需要额外处理。

3. Full 模式

完整数据库导出使用:

expdp system/password@SRCDB \
  directory=DP_DIR \
  dumpfile=full_%U.dmp \
  logfile=full_export.log \
  full=y \
  parallel=4

FULL=Y 并不意味着它是 RMAN 意义上的“物理完整备份”。它仍然是逻辑导出,不能替代数据库级物理恢复方案。

4. 表空间和可传输表空间

对于大规模数据库,Data Pump 还可以与可传输表空间能力结合。其基本思想不是逐行导出所有表数据,而是传输表空间数据文件,再用 Data Pump 导出和导入对象元数据。

这类方案的速度和停机特征可能优于普通逻辑导出,但前置条件更多,包括:

  • 表空间是否自包含;
  • 源、目标平台和字节序是否支持;
  • 版本是否支持目标传输方式;
  • 只读或冻结表空间的安排;
  • 跨平台转换是否需要 RMAN CONVERT
  • 用户、权限和非表空间对象如何迁移。

因此,不能因为“使用了 Data Pump”就把普通 Schema 导出和可传输表空间当成同一种迁移。


三、导出的事务一致性:一个 SCN 视图,而不是停机快照

1. SCN 和一致性读

Oracle 的 SCN(System Change Number)可以理解为数据库逻辑时间线上的位置。对某一时刻的查询,Oracle 通过一致性读使用当前数据块和 undo 信息重建该时刻可见的数据版本。

设导出开始时选择的 SCN 为 SS,某一行在 SCN SS 时已经提交,则该行应当对一致性读可见;在 SS 之后才提交的修改,则不应出现在该一致性视图中。

可以把 Data Pump 的目标抽象为:

Dexport=VisibleData(S)D_{\text{export}} = \operatorname{VisibleData}(S)

其中:

  • SS 是导出使用的 SCN;
  • VisibleData(S) 是在该 SCN 上可见的已提交数据;
  • 导出过程持续时间可以大于一个事务,但逻辑上使用同一时间点的可见性。

使用显式 SCN 的例子:

SELECT current_scn FROM v$database;

假设得到:

CURRENT_SCN
-----------
1234567890

然后:

expdp app_admin/password@SRCDB \
  directory=DP_DIR \
  dumpfile=app_%U.dmp \
  logfile=app_export.log \
  schemas=APP \
  flashback_scn=1234567890

FLASHBACK_TIME 也可以按时间指定,Oracle 会将其转换为相应的 SCN:

expdp app_admin/password@SRCDB \
  directory=DP_DIR \
  dumpfile=app_%U.dmp \
  logfile=app_export.log \
  schemas=APP \
  flashback_time=\"to_timestamp('2025-01-20 22:00:00','YYYY-MM-DD HH24:MI:SS')\"

生产脚本中更容易审计和复现的是显式记录 SCN。时间转换还会受到数据库时区、客户端引号和参数文件解析方式影响。

2. 导出时源库可以继续写入吗

通常可以。Data Pump 的一致性读机制并不要求普通表在整个导出期间都被锁住。导出开始后,应用提交的新事务不应出现在指定 SCN 的一致性数据中。

但这不表示导出期间没有风险:

  1. undo 必须保留足够长时间
    如果导出运行很久,而源表的旧版本被覆盖,可能出现类似 ORA-01555: snapshot too old 的错误。

  2. 对象定义可能发生变化
    DDL、分区操作、表重定义和对象依赖变化可能使导出失败、产生警告或导致对象不完整。

  3. 跨表业务一致性不等于数据库一致性
    Data Pump 看到的是数据库 SCN 上的已提交状态,但它不会理解“订单和支付记录必须一起完成”这类业务语义。如果应用在一个业务流程中跨多个系统提交,单个 Oracle SCN 也无法覆盖外部系统。

  4. 导出完成时间不是数据截止时间
    例如导出在 22:00 开始、01:00 完成,若使用 FLASHBACK_SCN,数据截止点可能是 22:00 附近,而不是 01:00。导出日志的完成时间不能作为数据时间点。

3. 长事务与 undo 的关系

假设导出需要读取一个大表 3 小时,而源库对该表持续更新。为了重建旧版本,Oracle 需要在 undo 中保留导出所需的版本。

如果所需旧版本已经被回收,读取可能失败。提高 UNDO_RETENTION 只是配置意图,是否真正保留还取决于 undo 表空间空间和自动管理行为。不能把它当成无限期保证。

迁移前应通过测试估计:

  • 导出耗时;
  • 期间的更新量;
  • undo 使用峰值;
  • 目标环境的 I/O 和并行度;
  • 是否存在长时间运行的批处理事务。

四、导出、传输、导入与“校验”的三个层次

迁移校验不能只依赖一个数字。至少要区分三层。

1. 文件完整性

文件完整性回答:

dump 文件在生成、复制和读取过程中,字节是否发生变化或损坏?

外部哈希

Linux 上可以使用:

sha256sum app_01.dmp app_02.dmp app_export.log > app_export.sha256

在目标服务器传输后:

sha256sum -c app_export.sha256

预期输出类似:

app_01.dmp: OK
app_02.dmp: OK

这能检测文件复制、截断和普通位翻转,但它只证明“目标文件与源文件相同”,不证明源导出本身正确。

Data Pump 内置 checksum

部分 Oracle 版本提供 Data Pump 的 CHECKSUM 和校验相关参数,用于在 dump 文件块层面生成或验证校验值。具体参数、默认值、可用算法和适用版本应以目标版本的命令帮助和官方文档为准:

expdp help=y
impdp help=y

不要在未确认版本的环境中直接假设某个 CHECKSUM_ALGORITHM 或验证参数一定存在。即使 Data Pump 内置校验成功,它通常也只能说明 dump 文件块通过了相应检查,不能证明表行数、业务关系和目标对象状态正确。

2. 导入过程完整性

导入日志可以发现:

  • 对象创建失败;
  • 表已存在;
  • 权限不足;
  • 表空间不存在;
  • 无法创建索引;
  • 约束启用失败;
  • 某些对象被跳过;
  • 字符转换或对象版本不兼容。

导入结束后不能只看命令退出码,还要检查日志中的:

ORA-
UDI-
ORA-39083
ORA-39126
ORA-31684
ORA-39151

不同错误的含义不同。例如:

  • ORA-31684 常见于对象已存在,未必表示迁移整体失败;
  • ORA-39151 表示对象已存在且被跳过等特定情况;
  • ORA-39083 常表示某个对象 DDL 执行失败;
  • 数据表导入成功但索引创建失败,应用可能仍然能查,但性能和唯一性语义可能已经改变。

可以先用 SQLFILE 预览导入将要执行的 DDL:

impdp system/password@DESTDB \
  directory=DP_DIR \
  dumpfile=app_%U.dmp \
  sqlfile=app_preview.sql \
  schemas=APP

SQLFILE 的作用是生成 SQL 文件而不执行这些 DDL,适合审核 CREATE USERALTER USER、表空间、权限、触发器等内容。它不能替代真正导入,因为数据装载、内部处理和部分对象恢复并不会通过这一个 SQL 文件完整表达。

3. 数据语义完整性

文件哈希和导入日志都不能证明:

  • 每张表的行数一致;
  • 主键范围一致;
  • 子表没有丢失父表引用;
  • 字符没有发生不可逆转换;
  • 触发器、权限和应用行为一致;
  • 统计信息适合目标数据分布。

至少应进行表级核对:

SELECT
    COUNT(*) AS row_count,
    MIN(order_id) AS min_order_id,
    MAX(order_id) AS max_order_id
FROM app.orders;

源库和目标库分别执行并比较结果。对于大表,COUNT(*) 本身可能很昂贵,且只比较行数不能发现“同样多但内容不同”。

可以增加按主键范围分桶的校验:

SELECT
    TRUNC(order_id / 100000) AS bucket_id,
    COUNT(*) AS row_count,
    MIN(order_id) AS min_id,
    MAX(order_id) AS max_id
FROM app.orders
GROUP BY TRUNC(order_id / 100000)
ORDER BY bucket_id;

若主键不是连续数字,应使用日期分桶、哈希分桶或业务分区。ORA_HASH 可以用于快速抽样或分组比较,但它不是无碰撞的密码学证明,不能单独作为强一致性校验。

业务上重要的金额字段还应分别聚合:

SELECT
    COUNT(*) AS row_count,
    SUM(amount) AS amount_sum,
    MIN(created_at) AS min_created_at,
    MAX(created_at) AS max_created_at
FROM app.orders;

这仍然不是数学意义上的全量证明。例如,两行金额互换可能保持总和不变。因此,对高风险表应使用稳定排序后的分片校验,或者在业务停写后进行最终全量比较。


五、字符集:导入成功不等于文本没有损失

1. Oracle 中的两个字符集概念

Oracle 数据库至少需要区分:

  • 数据库字符集:主要影响 CHARVARCHAR2CLOB 等类型;
  • 国家字符集:主要影响 NCHARNVARCHAR2NCLOB

可以查询:

SELECT parameter, value
FROM nls_database_parameters
WHERE parameter IN (
    'NLS_CHARACTERSET',
    'NLS_NCHAR_CHARACTERSET'
)
ORDER BY parameter;

典型结果可能是:

NLS_CHARACTERSET       AL32UTF8
NLS_NCHAR_CHARACTERSET AL16UTF16

AL32UTF8 是 Oracle 对 Unicode UTF-8 的数据库字符集名称。它不要与 Oracle 历史上的 UTF8 混淆;二者的 Unicode 编码语义并不相同。

2. Data Pump 如何处理字符转换

Data Pump 导出文件包含用于解释元数据和文本数据的字符集信息。导入时,Oracle 会根据源和目标数据库字符集进行转换。

理想情况是目标字符集能够表示源数据中的所有字符。例如:

源库:AL32UTF8
目标库:AL32UTF8

通常不需要发生数据库字符集之间的降级转换。

风险情况是:

源库:AL32UTF8
目标库:WE8MSWIN1252

源库可能包含中文、日文、emoji 或其他目标字符集无法表示的字符。导入可能出现警告、转换错误或不可逆替换,具体表现取决于数据类型、转换路径和 Oracle 版本。不能因为 impdp 最终返回成功,就推断每个字符都保持不变。

字符集转换的核心条件可以写成:

cCsource,Representable(c,CStarget)=true\forall c \in C_{\text{source}},\quad \operatorname{Representable}(c, CS_{\text{target}})=\text{true}

其中:

  • CsourceC_{\text{source}} 是源数据实际出现的字符集合;
  • CStargetCS_{\text{target}} 是目标数据库字符集;
  • 若存在一个字符不满足条件,就可能发生替换、报错或信息损失。

这不是简单地比较字符集名称大小就能完全判断的,因为数据中实际出现了哪些字符同样重要。

3. 字节长度和字符长度不是一回事

在多字节字符集下:

SELECT
    LENGTH(text_col)  AS char_length,
    LENGTHB(text_col) AS byte_length
FROM app.messages;
  • LENGTH 通常按字符计算;
  • LENGTHB 按字节计算。

例如,同样是 10 个字符,在单字节字符集和 AL32UTF8 中占用的字节数可能不同。迁移到目标字符集后,以下问题可能出现:

  • VARCHAR2(n BYTE) 的字节限制被触发;
  • 索引键长度变化;
  • 复合索引更接近键长度上限;
  • 应用按字节截断字符串造成数据损坏;
  • CLOB 内容虽然能保存,但下游接口编码不一致。

应区分:

VARCHAR2(100 BYTE)
VARCHAR2(100 CHAR)

前者限制字节数,后者限制字符数,但二者仍受到数据库版本和最大字符串设置等条件约束。

4. 字符集迁移前的测试

不要只查数据库参数,还要抽取真实文本边界样本:

SELECT message_id, text_col
FROM app.messages
WHERE REGEXP_LIKE(text_col, '[^ -~]')
FETCH FIRST 100 ROWS ONLY;

这只是寻找非 ASCII 字符的样本,不是完整的 Unicode 能力测试。更可靠的做法是:

  1. 从生产库抽取包含中文、组合字符、emoji、全角字符和特殊标点的代表性数据;
  2. 在与生产相同的字符集和版本上导出;
  3. 导入到目标测试库;
  4. 比较字符长度、字节长度和原始文本;
  5. 检查应用驱动、连接池和消息队列的编码;
  6. 对不可表示字符制定明确的拒绝或清洗策略。

NLS_LANG 主要影响 Oracle 客户端与服务器之间的客户端字符集协商和消息环境,不能把设置 NLS_LANG 当成改变数据库字符集或修复 Data Pump 数据转换的手段。


六、导入不是“把文件解压进去”

1. 导入中的对象依赖

一个 Schema 导入大致要处理这些阶段:

用户与表空间映射
    │
    ▼
表和基础对象
    │
    ▼
表数据
    │
    ▼
索引、约束、触发器
    │
    ▼
视图、包、过程、权限、统计信息等

实际调度由 Data Pump 控制,具体顺序会因对象类型和依赖关系变化。导入过程中,某些对象可能因为前置对象不存在而失败。日志中的错误必须结合对象名称和 DDL 内容分析。

例如目标环境没有源表空间:

impdp system/password@DESTDB \
  directory=DP_DIR \
  dumpfile=app_%U.dmp \
  logfile=app_import.log \
  remap_tablespace=APP_DATA:USERS

这会把源表空间 APP_DATA 映射到目标表空间 USERS。但如果目标表空间容量、区块大小、自动扩展策略或配额不合适,映射成功仍可能在装载阶段失败。

2. 已存在对象的处理

导入已有 Schema 时,必须明确 TABLE_EXISTS_ACTION

impdp system/password@DESTDB \
  directory=DP_DIR \
  dumpfile=app_%U.dmp \
  logfile=app_import.log \
  schemas=APP \
  table_exists_action=replace

常见行为包括:

  • SKIP:目标表存在时跳过;
  • APPEND:向已有表追加数据;
  • TRUNCATE:先清空目标表再装载;
  • REPLACE:删除并重新创建目标表。

REPLACE 不是无风险的刷新操作。它可能删除目标表上的本地修改、依赖对象或不在导出文件中的辅助结构。生产导入前应使用独立目标 Schema 或新表空间演练,而不是直接在唯一生产对象上试错。

3. 预览和实际导入分离

建议先生成 SQL 预览:

impdp system/password@DESTDB \
  directory=DP_DIR \
  dumpfile=app_%U.dmp \
  logfile=app_preview.log \
  schemas=APP \
  remap_schema=APP:APP_NEW \
  remap_tablespace=APP_DATA:APP_NEW_DATA \
  sqlfile=app_preview.sql

审核内容包括:

  • 是否错误创建了源库用户;
  • 默认表空间和临时表空间是否正确;
  • 是否包含不应迁移的目录对象、数据库链接或权限;
  • 是否引用了目标库不存在的表空间;
  • 是否有依赖源库路径的外部表、BFILE 或调度任务;
  • 是否会覆盖目标库已有对象。

七、停机窗口:Data Pump 能减少什么,不能消除什么

1. 普通 Data Pump 的停机模型

最简单的迁移方式是:

1. 导出源库
2. 传输 dump 文件
3. 导入目标库
4. 校验目标库
5. 停止应用写入
6. 做最后差异处理或确认无差异
7. 切换连接到目标库
8. 重新开放应用

如果在第 1 步导出后源库继续写入,那么目标库只包含导出一致性点之前的数据。假设:

  • 初始导出 SCN 为 S0S_0
  • 应用在 S0S_0 之后产生事务;
  • Data Pump 本身不提供持续增量复制。

则目标库缺少:

ΔD=D(Scutover)D(S0)\Delta D = D(S_{\text{cutover}}) - D(S_0)

其中 D(S)D(S) 表示 SCN SS 时已提交的数据状态。

因此,Data Pump 的导出时间可能很长,但真正的停机窗口是否很短,取决于是否有办法处理 ΔD\Delta D

2. 方案一:导出期间允许写入,切换时停机

这是最容易理解的方案:

  1. 在允许业务继续运行时做一致性导出;
  2. 创建目标库并导入;
  3. 停止应用写入;
  4. 将源库在导出点之后产生的差异补到目标;
  5. 做最终校验;
  6. 切换连接。

差异补偿可以是:

  • 应用层双写;
  • 按更新时间或递增 ID 抽取;
  • 业务日志重放;
  • 数据库变更捕获工具;
  • 在切换前重新导出变更表。

但“按更新时间补数据”并不是天然正确的增量方案。时钟精度、更新但不改变 updated_at、删除记录、回写旧时间和事务边界都会造成漏数或重复。若采用这种方案,必须定义幂等键、删除传播和重复导入行为。

3. 方案二:停写后再做完整导出

如果数据库规模允许,可以在应用停写后执行 Data Pump:

停止写入
  │
  ├── 记录停写时间和 SCN
  ├── 导出
  ├── 传输并导入
  ├── 全量校验
  └── 切换应用

它的优点是数据模型简单,目标自然对应停写后的稳定状态。缺点是停机时间等于导出、传输、导入和校验的总耗时,通常不适合大库。

4. 方案三:使用在线复制或物理切换能力

如果目标是大幅缩短停机时间,Data Pump 通常只负责初始装载,后续需要其他机制同步变化,例如逻辑复制、变更数据捕获或 Oracle 体系内的高可用能力。

这时要区分:

  • Data Pump:逻辑初始装载和对象迁移;
  • Data Guard:以 redo 传输和物理或逻辑备用机制为核心;
  • RMAN:物理备份、恢复和数据库级灾难恢复;
  • RAC:同一数据库的多实例并发访问,不等于跨数据库迁移;
  • 在线变更或 CDC:持续传播源库变更,帮助缩短切换窗口。

Data Pump 本身不是 CDC 工具,也不会自动记录并重放导出结束后的所有 DML。


八、Data Pump 与 RMAN、Data Guard、RAC 的边界

1. Data Pump 与 RMAN

RMAN 主要处理物理备份和恢复,例如:

  • 数据文件;
  • 控制文件;
  • archived redo;
  • 数据库时间点恢复;
  • 介质故障恢复。

Data Pump 主要处理逻辑对象:

  • Schema 迁移;
  • 表和数据抽取;
  • 表空间映射;
  • 版本或平台之间的逻辑转换;
  • 选择性导入和对象过滤。

如果目标是“源库磁盘损坏后恢复到某个时间点”,应考虑 RMAN,而不是 Data Pump。

如果目标是“只把一个 Schema 搬到另一套 Oracle 数据库,并修改表空间名称”,Data Pump 更符合问题边界。

2. Data Pump 与 Data Guard

Data Guard 的核心是数据库级 redo 传输和备用库应用。它适合构建物理或逻辑备用数据库并执行故障切换、切换和灾难恢复。

Data Guard 通常不适合直接表达以下迁移需求:

  • 只迁移一个 Schema;
  • 改变对象名称;
  • 改变表空间布局;
  • 清理部分对象;
  • 将数据映射到完全不同的逻辑模型。

这些是 Data Pump 或 ETL 的职责。

反过来,Data Pump 也不能替代 Data Guard 的持续 redo 保护。导出文件成功不意味着目标库能立即接替源库的所有提交。

3. Data Pump 与 RAC

RAC 是多个实例访问同一数据库。Data Pump 可以在 RAC 数据库上运行,但要注意:

  • DIRECTORY 对象是数据库对象,路径由服务器端解释;
  • 所有可能执行 worker 的实例都必须能访问 dump 文件和日志路径;
  • 并行度受 CPU、I/O、对象大小和文件数量限制;
  • 多个 worker 不会让单个不可并行对象无限加速;
  • 目标库导入期间的索引创建、约束启用和日志写入仍可能成为瓶颈。

RAC 解决的是同一数据库的实例级并发和可用性,不是迁移同步机制。


九、作业状态、断点和失败恢复

Data Pump 作业有自己的状态和 master table。导出或导入过程被中断时,不能简单地假设重新执行同一条命令一定安全。

可以附着到作业:

impdp system/password@DESTDB \
  attach=SYS_IMPORT_SCHEMA_01

进入交互界面后可以查看状态:

STATUS

请求停止:

STOP_JOB=IMMEDIATE

恢复已停止的作业时,通常应附着到现有作业并执行:

START_JOB

KILL_JOB 是更强的终止操作,可能清理作业状态,但不能撤销已经提交的目标库 DDL 或数据。导入后的恢复策略不能依赖“杀掉作业就自动回滚整个数据库”。

生产环境应提前决定:

  • 失败后是继续导入,还是删除目标 Schema 后重来;
  • 目标对象是否允许部分存在;
  • 是否使用专用目标用户和表空间;
  • 如何清理已导入数据;
  • 如何区分可接受的“对象已存在”与真正的数据错误;
  • 如何保存导出参数、SCN、文件清单和日志。

对于可重复演练的迁移,使用新的目标 Schema,例如 APP_STAGE,通常比反复覆盖正在使用的 APP 更容易诊断。


十、一个可执行的 Schema 迁移演练

以下例子假设:

  • 源库服务名为 SRCDB
  • 目标库服务名为 DESTDB
  • 源 Schema 为 APP
  • 目标 Schema 为 APP_NEW
  • 两端均已创建同名 DIRECTORY 对象;
  • 执行账号拥有对应 Schema 和 DIRECTORY 权限;
  • 目标表空间 APP_NEW_DATA 已存在。

步骤 1:记录源库环境和一致性点

SELECT current_scn FROM v$database;

SELECT parameter, value
FROM nls_database_parameters
WHERE parameter IN (
    'NLS_CHARACTERSET',
    'NLS_NCHAR_CHARACTERSET'
);

SELECT owner, object_type, COUNT(*) AS object_count
FROM dba_objects
WHERE owner = 'APP'
GROUP BY owner, object_type
ORDER BY object_type;

保存查询结果。SCN 用于解释数据截止点,字符集用于判断转换风险,对象统计用于导入后比对。

步骤 2:导出

expdp app_admin/password@SRCDB \
  directory=DP_DIR \
  dumpfile=app_%U.dmp \
  logfile=app_export.log \
  schemas=APP \
  flashback_scn=1234567890 \
  filesize=10G \
  parallel=4

预期结果是生成一个或多个 dump 文件和导出日志。日志中应检查:

  • 实际导出的对象数量;
  • 是否出现 ORA 错误;
  • 是否存在无法导出的对象;
  • 任务是否正常完成;
  • 使用的 SCN、文件名和参数是否被记录。

步骤 3:传输并校验文件

sha256sum app_01.dmp app_02.dmp > app.sha256

在目标服务器执行:

sha256sum -c app.sha256

只有所有文件显示 OK,才进入导入步骤。

步骤 4:预览 DDL

impdp system/password@DESTDB \
  directory=DP_DIR \
  dumpfile=app_%U.dmp \
  logfile=app_preview.log \
  schemas=APP \
  remap_schema=APP:APP_NEW \
  remap_tablespace=APP_DATA:APP_NEW_DATA \
  sqlfile=app_preview.sql

检查生成的 app_preview.sql,确认用户、表空间、权限和对象引用符合目标环境。

步骤 5:正式导入

impdp system/password@DESTDB \
  directory=DP_DIR \
  dumpfile=app_%U.dmp \
  logfile=app_import.log \
  schemas=APP \
  remap_schema=APP:APP_NEW \
  remap_tablespace=APP_DATA:APP_NEW_DATA \
  parallel=4

导入后检查:

SELECT owner, object_type, COUNT(*) AS object_count
FROM dba_objects
WHERE owner = 'APP_NEW'
GROUP BY owner, object_type
ORDER BY object_type;

检查无效对象:

SELECT owner, object_name, object_type
FROM dba_objects
WHERE owner = 'APP_NEW'
  AND status <> 'VALID'
ORDER BY object_type, object_name;

注意:某些对象的 STATUS、编译依赖或统计信息需要在目标环境中重新处理。无效对象列表应与源库差异解释,而不是机械地要求数量完全相同。

步骤 6:比较关键表

源库:

SELECT COUNT(*) AS row_count,
       MIN(order_id) AS min_id,
       MAX(order_id) AS max_id,
       SUM(amount) AS amount_sum
FROM app.orders;

目标库:

SELECT COUNT(*) AS row_count,
       MIN(order_id) AS min_id,
       MAX(order_id) AS max_id,
       SUM(amount) AS amount_sum
FROM app_new.orders;

如果结果不同,应继续按分区、日期、主键范围或业务批次定位,不应直接用 APPEND 重跑,因为那可能掩盖重复数据。


十一、常见误解和失败表现

误解一:导出日志显示完成,所以迁移一定正确

错误。日志完成只表示 Data Pump 作业按照自身规则完成。它不保证:

  • 所有业务对象都被包含;
  • 所有对象都在目标库成功创建;
  • 文本没有字符损失;
  • 数据行内容与源库一致;
  • 应用依赖的外部对象已迁移。

必须结合错误日志、对象状态、数据核对和应用验证。

误解二:dump 文件的 SHA-256 一致,所以目标数据一定一致

错误。SHA-256 只能证明传输后的文件字节与计算哈希时的文件一致。若源端导出时遗漏对象、使用了错误 Schema、使用了错误的 SCN,哈希仍然会一致。

误解三:导出期间应用可以写入,所以可以随时切换

错误。导出期间写入只说明源库可以继续服务;目标库仍然停留在导出一致性点。切换前必须处理导出点之后的变更,否则会丢失提交。

误解四:目标字符集更“新”,所以一定兼容

错误。字符集兼容取决于实际字符集合和目标存储能力。即使目标使用 Unicode,应用驱动、连接参数、文件接口和下游系统仍可能发生编码问题。

误解五:增加 PARALLEL 就一定更快

错误。并行度受以下因素共同限制:

  • dump 文件数量;
  • 源库读取能力;
  • 目标库写入能力;
  • 索引创建和排序;
  • undo、redo 和临时表空间;
  • 单个对象能否并行处理;
  • 存储系统吞吐量。

盲目提高并行度可能导致源库和目标库同时争用 CPU、I/O、临时表空间和日志写入。

误解六:Data Pump 可以替代 SQLite 迁移工具

错误。SQLite 没有 Oracle Data Pump 的对象模型、用户体系、表空间和数据库字符集机制。Oracle 到 SQLite 的迁移通常需要:

  1. 使用 Data Pump 或 SQL 查询从 Oracle 提取数据;
  2. 根据 SQLite 类型系统重新设计类型映射;
  3. 单独迁移主键、唯一约束、索引和触发器;
  4. 处理日期、时间、精确数字、CLOB、BLOB 和 NULL 语义;
  5. 使用 SQLite 事务批量装载;
  6. 对应用 SQL 和并发模型重新测试。

这属于 ETL 或应用级迁移,不是 impdp 的目标范围。


十二、与 Expand-Contract 和可回滚发布的关系

当数据库迁移同时包含 Schema 变更时,不应把“数据搬迁”和“应用切换”设计成一次不可逆操作。

例如将:

APP.ORDERS.amount NUMBER

迁移为新的金额结构,较安全的流程可以是:

Expand
  ├── 创建新列或新表
  ├── 部署兼容旧结构的应用版本
  ├── 回填和同步新数据
  └── 验证新旧结构一致

Contract
  ├── 切换应用只读新结构
  ├── 观察一段时间
  └── 再删除旧列或旧表

Data Pump 可以参与 Expand 阶段的初始装载,但它不能自动提供双写、实时同步或业务回滚。切换时应明确:

  • 哪个版本的应用可以读旧结构和新结构;
  • 旧结构是否仍然保留;
  • 迁移失败时如何把连接切回源库;
  • 源库在切换前是否继续保留写入能力;
  • 如何处理已经写入目标库但尚未写回源库的数据。

若目标是“失败后立即切回”,最重要的不是导入命令本身,而是切换后源库是否仍拥有完整、可继续服务的数据,以及双向差异是否会破坏回滚。


十三、如何设计停机窗口的计算

停机窗口不能只写成“预计几分钟”,应拆成可测量的阶段:

Tdowntime=Tfreeze+Tfinal-sync+Tvalidation+Tswitch+TwarmupT_{\text{downtime}} = T_{\text{freeze}} + T_{\text{final-sync}} + T_{\text{validation}} + T_{\text{switch}} + T_{\text{warmup}}

其中:

  • TfreezeT_{\text{freeze}}:停止写入、排空请求和关闭批处理的时间;
  • Tfinal-syncT_{\text{final-sync}}:补齐初始导出之后变更的时间;
  • TvalidationT_{\text{validation}}:关键表、约束、应用读写和数据一致性验证时间;
  • TswitchT_{\text{switch}}:连接串、服务名、负载均衡或路由切换时间;
  • TwarmupT_{\text{warmup}}:连接池重建、缓存预热和后台任务恢复时间。

普通完整停机导入时,初始导入时间也会计入窗口:

Tdowntime=Texport+Ttransfer+Timport+Tvalidation+TswitchT_{\text{downtime}} = T_{\text{export}} + T_{\text{transfer}} + T_{\text{import}} + T_{\text{validation}} + T_{\text{switch}}

而初始导出和文件传输如果在业务运行期间完成,就不会全部计入停机窗口,但必须为最终差异同步付出代价。

可靠的估算方式是做至少一次与生产规模接近的演练,记录:

  • 导出总耗时和吞吐;
  • dump 文件实际大小;
  • 传输速度;
  • 导入表数据耗时;
  • 索引和约束创建耗时;
  • 校验查询耗时;
  • 失败重试和清理耗时。

没有演练数据时,任何精确停机承诺都缺乏依据。


十四、迁移完成的判定条件

一次可接受的 Data Pump 迁移,至少应满足以下事实条件:

  1. 导出日志和导入日志已归档;
  2. 使用的导出模式、Schema、SCN 和参数可追溯;
  3. 所有 dump 文件在传输后通过文件级校验;
  4. 导入日志中的 ORA 错误已逐项解释;
  5. 关键对象数量与源库差异可解释;
  6. 关键表的行数、范围、聚合值或分桶校验通过;
  7. 主键、唯一约束、外键和索引状态符合预期;
  8. 字符集和代表性文本测试通过;
  9. 应用读写、事务提交、连接池和后台任务测试通过;
  10. 切换失败时有明确的连接回退和数据处理方案;
  11. 源库在回滚窗口内仍保留可恢复状态;
  12. 后续备份、监控、统计信息和维护作业已经接管目标库。

Data Pump 适合做可选择范围的逻辑迁移,尤其适用于 Schema 重建、表空间映射和跨环境装载。它的核心保证是逻辑导出与导入过程中的对象和数据搬运,不是持续复制、物理灾备或业务语义验证。

真正的迁移完成点,应当是目标库在明确一致性点上完成装载、字符数据未发生不可接受的转换、文件和数据均通过验证,并且应用可以在可回滚的切换策略下稳定运行。


系列导航与关联阅读

官方资料

本文依据数据库官方文档重新梳理;正文、示例与生产检查清单由 WR BLOG 编写。