最近刚做完一套 Oracle 11.2.0.4 核心库到 19c 的迁移,总算能坐下来把这套流程整理清楚。数据库迁移这活看上去就是“搬数据”,实际上从版本兼容、对象盘点、工具选型,到夜里的回切验证,每一环都可能翻车。这篇内容主要写给准备做 Oracle 迁移的运维和 DBA 同学,也适合需要配合迁移的研发看看:迁移到底迁什么、用哪种工具、怎么准备、踩坑怎么排查。
我先说个结论:Oracle 迁移本身不难,难的是把一个运行多年的老库无差别地搬到新环境。老系统里那些没人记得的 DB Link、定时 JOB、自定义角色、ACL 授权、奇怪字符集,才是迁移过程中真正会卡住你的东西。这篇文章会完整讲一遍从评估到上线的实操思路,并附上我这次实际用到的命令和排查链路。
1. 先搞清楚“迁”的是什么:版本、平台还是机房
很多人一上来就问“怎么把 Oracle A 库迁到 B 库”,但“迁移”这个词在不同场景下含义差别很大,选错方向后面全乱。我习惯把它拆成三个维度看:版本、平台、位置。
1.1 版本迁移与平台迁移不能混为一谈
版本迁移最常见,比如从 Oracle 11.2.0.4 升到 19c。这类迁移核心是“跨版本兼容性”:SQL 语法、隐含参数、数据字典结构、字符集行为都可能变化。老库上能跑的 SQL 在新版本不一定能跑,或者执行计划变了。更麻烦的是,一些老版本里被保留的隐藏功能,到了新版本可能直接失效。
平台迁移则是换操作系统或换硬件架构,比如从 AIX 迁移到 Linux,从 Solaris 迁移到国产化服务器,或者从物理机迁到虚拟化平台。平台迁移往往伴随版本迁移一起做,但如果没有版本变化,只是同版本换操作系统,通常更适合用物理迁移手段,比如 RMAN 恢复或者可传输表空间,因为这些工具不关心字符集和 SQL 兼容性,直接搬数据文件。
还有一种最容易让人混淆的是“机房迁移 / 云迁移”。表面上你只是换了一个机房或者把库迁到云上,但网络延迟、存储性能、主机命名规范、监听配置、备份策略全都要重新适配。我之前见过一个项目,数据导过去只用了两个小时,结果应用连不上,原因仅仅是云环境的安全组没放通数据库端口,这种问题不在数据库本身,却在迁移项目里占了很大时间。
所以接到迁移任务,先别急着导数据。第一步应该问三个问题:目标库版本是多少?目标平台是不是同一套?网络和部署位置有没有变化?答案不一样,后面的工具选型完全不同。
1.2 逻辑迁移与物理迁移的边界
从技术实现上,Oracle 迁移就两条路:逻辑迁移和物理迁移。
逻辑迁移是“把对象和数据重新生成”。典型代表就是 Data Pump 的 expdp 导出、impdp 导入,它读取源库的数据和 DDL,在目标库重新执行,生成一套新的数据文件。优点是灵活,可以按 schema 迁、按表迁、顺便改用户名、改表空间名;缺点是重建过程会丢失一些底层属性和性能特征,而且大表全量导出导入的速度比物理文件拷贝慢。
物理迁移是“把数据库文件原样搬过去”。典型代表是 RMAN 备份恢复、可传输表空间、Data Guard 备库切换。它直接处理数据文件、控制文件,最大程度保留原有存储布局和性能特性,速度通常更快,但对目标环境的版本、平台、字节序有严格要求。同一版本同平台,RMAN 恢复是最高效的迁移方式;跨版本跨平台,物理迁移就受限很多。
逻辑迁移和物理迁移不是互斥的,实际项目里经常混用。比如我们可以用 Data Pump 搬数据,再用可传输表空间加速某个超大分区表;或者先用 Data Guard 做在线同步,最后短停切换完成迁移。明白了这两种边界,再看后面的工具对比就清晰多了。
2. 动手前必须完成的准备工作(少一项都会翻车)
Oracle 迁移最怕的不是迁移本身,而是“以为自己准备好了”。我这次迁移前花了两天专门做盘点,最后发现源库里有 17 个 DB Link、3 个自定义角色、一个加了 FGA 审计策略的业务 schema、还有一堆没人维护的自建 JOB。这些东西如果漏掉,数据导过去应用照样能起,但业务会在一周内陆续报故障。
2.1 盘点家底:从表空间到 DB Link 一项不漏
盘点范围不能只盯着表和索引。我把老库的家底分成五层:数据层、对象层、权限层、运行层、外部依赖层。
数据层包括表空间、数据文件、表、分区、LOB 段、索引、约束、触发器、序列。要重点关注大表和大分区。超过 100GB 的表,expdp 导出虽然能跑,但并行度和网络传输都要单独规划。序列是特别容易忽略的,迁移后应用如果继续插入数据,序列值不对就会主键冲突。
对象层包括视图、物化视图、同义词、包、存储过程、函数、类型定义。物化视图要特别注意,它本质上有底层表和定时刷新机制,Data Pump 导入后往往需要重新编译或重建。同义词容易造成“数据看不见”的假象,尤其是 public 同义词,导入后如果对应用户不存在,应用查询就会报 ORA-00942。
权限层包括用户、角色、系统权限、对象权限、表空间配额、profile、密码策略。expdp 导出时如果用 FULL 模式,角色和授权通常会带上,但带上也不代表导入后完全一致,很多坑出在默认表空间不同、临时表空间未指定、权限对象不存在导致授权时报错。
运行层包括 DB Link、JOB、队列、调度程序、审计策略、VPD 行级安全策略、ACL 网络访问控制。这一层是最少被盘点的。比如 DB Link 导入后指向的还是源库的连接串,如果你把源库下掉,相关视图和存储过程就会开始报错;ACL 不重建,应用调用 UTL_HTTP 会直接抛权限异常。
外部依赖层比较杂,包括 tnsnames.ora、监听配置、环境变量、启动脚本、备份脚本、监控脚本。这些不属于数据库对象,但迁移后切库时少了任何一项都会手忙脚乱。
盘点建议直接查数据字典,而不是翻文档。我常用的查询大概是这样:
-- 用户与默认表空间 SELECT username, default_tablespace, temporary_tablespace, profile FROM dba_users ORDER BY username; -- 表空间与数据文件 SELECT tablespace_name, file_name, bytes/1024/1024 AS size_mb FROM dba_data_files; -- 对象分类型统计 SELECT owner, object_type, COUNT(1) FROM dba_objects GROUP BY owner, object_type ORDER BY owner, object_type; -- DB Link 清单 SELECT owner, db_link, host FROM dba_db_links; -- JOB 清单 SELECT job, owner, what, enabled FROM dba_jobs;做完这些查询后,把结果整理成一张迁移清单表,每类对象对应一个“是否处理”的列。这张表后面做验证时也要用到,所以不留死角。
2.2 评估停机窗口与 RTO/RPO
迁移必须有明确的时间目标。RTO 是业务最多能容忍恢复多久,RPO 是允许丢多少数据。大多数核心库迁移要求 RPO 为 0,也就是源库切掉之前,所有已提交数据必须完整落到新库。
如果 RPO 为 0,通常要么做一次完整停业务导出导入,要么用 Data Guard/GoldenGate 这类同步工具先拉齐增量再切换。如果业务允许 5 到 10 分钟停机,Data Pump 全量导出导入往往是最简单可控的方案;但如果源库数据量很大(比如几个 TB),停机时间内根本导不完,就得考虑物理迁移加增量日志应用,或者先把流量切到新库再反向同步回源库。
这里有个我常用的估算法:先拿一个 schema 做全量 expdp,记录吞吐量,再根据总数据量推算真实迁移时间。计算公式很简单:
预估时间 ≈ 总数据量 / 实测导出吞吐量 × 冗余系数冗余系数我一般取 1.5 到 2,因为生产环境的存储并发、网络带宽、目标库导入并行度都会影响最终速度。比如实测 expdp 吞吐量是 200GB/小时,数据总量 500GB,那预计导出需要 2.5 小时,导入再加一倍,整体可能要到 8 小时以上。这个数据不提前测,等到迁移当天才知道跑不完,那就被动了。
还有一个很容易被低估的是“切换前最后一次增量导出”。全量迁移完成后,从开始导出到业务停止这段时间里源库还有新数据进来,切换时通常还需要补一次增量。如果遗忘这一步,RPO 直接变成全量导出时间点,数据丢失不可接受。
2.3 兼容性检查:版本、字符集、参数
兼容性检查主要看三块:版本升级路径、字符集、初始化参数。
Oracle 升级路径有官方规则。数据库迁移不是所有版本都能任意直连,需要确认目标版本是否支持从源版本直接迁移。11.2.0.4 迁到 19c 是常见的受支持路径,但如果源库是很老的 9i 或 10g,就得考虑先升级到中间版本,再升到目标版本。这一步最好在项目初期确认,而不是等到导出导入中途才发现目标库不认老对象。
字符集是最容易出隐蔽问题的地方。源库的 NLS_CHARACTERSET 和目标库不一致时,纯英文数据可能没感觉,但中文数据导入后会出现乱码或 ORA-02374 转换错误。最稳妥的做法是目标库使用与源库相同或兼容的字符集,比如源库 ZHS16GBK,目标库也建 ZHS16GBK;如果目标库用了 AL32UTF8,ZHS16GBK 数据通常能无损迁移,但导入过程中必须保证 Data Pump 客户端的环境变量 NLS_LANG 正确,否则客户端和服务器之间的转换可能导致数据损坏。
进入新环境后,最好先查一下源库和目标库的核心参数差异:
SELECT name, value FROM v$parameter WHERE name IN ( 'db_block_size', 'compatible', 'processes', 'sessions', 'open_cursors', 'memory_target', 'sga_target', 'pga_aggregate_target', 'db_files', 'undo_tablespace', 'nls_length_semantics' ) ORDER BY name;重点看compatible参数。源库 compatible 老,目标库 compatible 新,导入一般没问题;反过来如果目标库 compatible 设置过低,部分对象可能建不出来。另外nls_length_semantics如果从 BYTE 改成 CHAR,涉及 CHAR/VARCHAR2 字段长度的行为会变化,可能导致导入时字段长度不够。
2.4 演练计划不能省
大型迁移一定要做演练。演练不是重新做一遍导出导入那么简单,而是要完整走一遍“切流、启动应用、验证、回退”的流程。我见过太多团队做完数据迁移演练,却从来没演练过应用启动脚本,结果真正切换那晚数据库没问题,应用起不来,白白浪费停机窗口。
演练还能帮你发现一些环境层面的问题,比如目标库服务器的 ulimit 限制、系统时区、/etc/hosts配置、磁盘 mount 权限。这些内容在导出导入阶段根本不会暴露,但等应用一启动就原形毕露。
3. 选哪条路:expdp/impdp、RMAN、Data Guard 的适用边界
Oracle 迁移工具很多,真正常用的就五类:Data Pump、RMAN、可传输表空间、Data Guard、GoldenGate。很多人选型只看“哪个快”,忽略了自己要迁移的环境限制,结果选了不能用的方案。
| 工具/方案 | 迁移维度 | 停机要求 | 跨版本 | 跨平台/字节序 | 难度 | 典型适用场景 |
|---|---|---|---|---|---|---|
| expdp/impdp | 逻辑 | 需停机 | 支持 | 支持 | 中 | 中小库、按用户迁移、可调整表空间/用户 |
| RMAN 备份恢复 | 物理 | 需停机 | 有限 | 不支持字节序变化 | 低 | 同版本同平台克隆、容灾恢复 |
| 可传输表空间 | 物理+逻辑 | 较短 | 支持 | 支持字节序转换 | 高 | 大库迁移、跨平台迁移 |
| Data Guard 备库切换 | 物理+日志 | 分钟级 | 需在线升级配合 | 不支持跨平台 | 高 | 大库在线迁移、容灾切换 |
| GoldenGate | 逻辑复制 | 秒级 | 支持 | 支持异构 | 很高 | 在线迁移、异构迁移、双活架构 |
Data Pump 是我用得最多的,因为它的测试成本低、可控性强。导错了重新导就行,目标库不会存在半残的数据文件。缺点是整体速度受 SQL 重建限制,大对象和索引较多时导入时间会比较长。对于几十 GB 到一两百 GB 的库,Data Pump 是首选。
RMAN 的优势是快。它是纯物理拷贝,不需要把每条记录重新插一遍。但用 RMAN 做跨版本迁移限制很多,主流做法是在同版本同平台环境先物理恢复,再用升级工具把数据库版本升上去。等于说“RMAN 负责快,升级工具负责版本”。如果你只是要在一个新服务器上克隆一套同版本库,RMAN 最合适。
可传输表空间特别适合大库跨平台迁移。它把数据文件直接搬过去,再用 Data Pump 只导元数据。相比全量 Data Pump,它不需要重建数据,速度优势明显。但操作链路复杂:先要自包含检查,再转换字节序,再把数据文件注册到目标库,最后导元数据。做完一次后你会有种“其实也不难”的感觉,但第一次做一定要充分演练。
Data Guard 迁移的核心思路不是“搬数据”,而是“同步状态”。先在目标环境搭一套备库,源库的日志不断应用到备库,迁移窗口到来时激活备库完成切换。它天然支持接近零停机的迁移,适合 TB 级大库。但要求源库和目标库必须同平台同字节序,版本也要满足 Data Guard 兼容要求,否则日志应用会报错。
GoldenGate 是能力最强的,可以异构复制,也可以做双向复制,但部署和运维成本都高。除非业务要求秒级切换或者必须做异构迁移,否则我不建议在普通迁移项目里引入 GoldenGate,复杂度不值得。
综合来看,我的选型建议是:数据量不超过 500GB 且停机窗口允许,优先 Data Pump;同版本同平台要快速克隆,用 RMAN;大库跨平台,先用可传输表空间方案评估;TB 级大库且要求短停机,考虑 Data Guard 加在线升级;只有真正的异构或双活需求才上 GoldenGate。
4. 实操:一套 Oracle 11g 到 19c 的 Data Pump 迁移完整过程
下面用一套实际项目流程展开。源库是 Oracle 11.2.0.4,业务 schema 叫 CRM,目标库是 Oracle 19c,目标 schema 也叫 CRM,表空间从 CRM_DATA 改名到 APP_DATA。数据量大约 180GB,停机窗口给了 6 小时。
4.1 目标库基础准备
先在目标库创建表空间和用户。Data Pump 导入时如果用户不存在,会报 ORA-01918;如果表空间不存在,会报 ORA-00959。与其让导入过程反复失败,不如一开始就建好。
-- 目标库 19c 执行 CREATE TABLESPACE app_data DATAFILE '/u01/app/oracle/oradata/NEWDB/app_data01.dbf' SIZE 50G AUTOEXTEND ON NEXT 1G MAXSIZE UNLIMITED LOGGING EXTENT MANAGEMENT LOCAL SEGMENT SPACE MANAGEMENT AUTO; CREATE TABLESPACE app_idx DATAFILE '/u01/app/oracle/oradata/NEWDB/app_idx01.dbf' SIZE 20G AUTOEXTEND ON NEXT 1G MAXSIZE UNLIMITED LOGGING EXTENT MANAGEMENT LOCAL SEGMENT SPACE MANAGEMENT AUTO; CREATE USER crm IDENTIFIED BY "YourStrongPwd" DEFAULT TABLESPACE app_data TEMPORARY TABLESPACE temp QUOTA UNLIMITED ON app_data QUOTA UNLIMITED ON app_idx; GRANT CONNECT, RESOURCE TO crm;这里我给目标库建了两个表空间,一个放表数据,一个放索引。生产环境建议表和索引分开表空间,方便管理。Data Pump 导入时能不能自动把索引放到另一个表空间?可以靠remap_tablespace加上transform=segment_attributes=N来做,下面会讲到。
4.2 在源库创建 Directory 并导出
Data Pump 的 directory 是数据库对象,指向服务器本地路径。需要注意的是,导出文件必须写到源库服务器能访问的目录,而不是随便指定客户端路径。
-- 源库 11g 执行 CREATE OR REPLACE DIRECTORY dpump_dir AS '/u01/app/oracle/backup/dpump'; GRANT READ, WRITE ON DIRECTORY dpump_dir TO system;导出命令建议直接写在 shell 脚本里,避免交互式输入密码暴露在历史记录中。我习惯用并行参数和通配符 dumpfile:
expdp system/password@orcl \ directory=dpump_dir \ schemas=crm \ parallel=4 \ compression=all \ dumpfile=crm_%U.dmp \ logfile=exp_crm.log \ exclude=statistics几个参数我解释一下:
parallel=4表示开 4 个并行进程。并不是越大越好,受服务器 CPU 和 IO 限制,一般先按 CPU 核数的一半试,再用日志观察是否成为瓶颈。compression=all用来压缩导出文件。11g 之后把 COMPRESSION 设置为 ALL,可以压缩表数据和元数据。经验是压缩率能达到 50% 以上,传输时间能省很多,代价是导出时 CPU 会升上去。dumpfile=crm_%U.dmp配合parallel,个位数字%U会生成多个分片文件,比如 crm_01.dmp、crm_02.dmp。如果没有%U,并行导出会报错或者只有一个文件写入,达不到并行效果。exclude=statistics先不导优化器统计信息。老库的统计信息有可能基于旧版执行计划算法,直接带到 19c 反而容易生成糟糕的执行计划。导入完成后重新收集统计信息更稳。
导出完看日志末尾。出现类似Job "SYSTEM"."SYS_EXPORT_SCHEMA_01" successfully completed字样才算成功。如果没有成功,不要进入下一步。
对于大库,导出过程中要持续关注磁盘空间。180GB 库压缩后大概 80GB 左右,导出目录至少要预留 100GB,防止中途写满。导出日志里如果看到ORA-01691这类错误,通常是目录路径权限不对,先排查 Linux 目录的写权限和属主,再排查 Oracle 的 directory 对象权限。
4.3 文件传输与导入
导出完成后把 dump 文件传到目标库服务器。传输工具看网络条件,我常用 rsync,因为它支持断点续传和限速:
rsync -avP --bwlimit=100000 \ /u01/app/oracle/backup/dpump/crm_*.dmp \ oracle@newserver:/u01/app/oracle/backup/dpump/这里--bwlimit=100000把带宽限制在 100MB/s 左右,避免占满业务网段带宽。传输完立即做一次 md5 校验,防止文件在大流量传输中出现静默损坏:
md5sum /u01/app/oracle/backup/dpump/crm_*.dmp > source.md5 md5sum -c source.md5目标库上同样需要建好 directory 并授权,然后执行导入:
impdp system/password@newdb \ directory=dpump_dir \ dumpfile=crm_%U.dmp \ schemas=crm \ remap_schema=crm:crm \ remap_tablespace=crm_data:app_data,crm_idx:app_idx \ transform=segment_attributes=N \ parallel=4 \ logfile=imp_crm.logremap_schema=crm:crm看着好像没变化,但它的含义是把源 schema 映射到目标 schema。如果不想改用户名,可以省略;但如果你想把 CRM 正式库导到测试库并改名,就必须用这个参数。
remap_tablespace用来改表空间。这里把源库的CRM_DATA映射到目标库的APP_DATA,把CRM_IDX映射到APP_IDX。
transform=segment_attributes=N是比较关键的一个参数。它等于告诉 Data Pump:导入时忽略原有对象的段属性,比如物理属性、存储子句、默认表空间等,全部落到目标用户的默认表空间上。这样即使源表指定了别的表空间,导入后也会统一落在CRM用户默认表空间APP_DATA上,配合 remap 能让物理布局干净很多。
导入最怕“干到一半报错退出”。遇到单对象错误时,先不要慌,Data Pump 默认会把错误记到日志里,并继续尝试导其他对象。等日志跑完,再统一看错误列表。不要一出现 ORA 错误就 Ctrl+C,那往往会把本来能完整导入的库搞到半成品状态。
4.4 导入后立即处理无效对象
目标库导入完成后,第一步不是启动应用,而是编译无效对象。大量存储过程、函数、视图导入后处于 INVALID 状态,这是正常的,因为对象依赖的对象还没有全部建好。
sqlplus / as sysdba @$ORACLE_HOME/rdbms/admin/utlrp.sqlutlrp.sql 会自动重编译所有无效对象。跑完后再查一次:
SELECT owner, object_type, status, COUNT(1) FROM dba_objects WHERE status <> 'VALID' GROUP BY owner, object_type, status ORDER BY owner, object_type;如果仍有无效对象,基本就是源库本身就存在无效对象,或者是依赖的 DB Link、对象权限没有补齐。逐个打开错误信息即可。
4.5 重新收集统计信息
因为导出时exclude=statistics,导入后所有对象没有统计信息,或者在导入最近一次用DBMS_STATS收集。19c 下建议用默认参数收集 schema 统计信息:
EXEC DBMS_STATS.GATHER_SCHEMA_STATS(ownname => 'CRM', cascade => TRUE, degree => 8);收集时间取决于对象数量和数据量。180GB 的库大概需要 30 分钟到 1 小时。统计信息不收集完,不要急着上线,否则应用第一次查询可能选出极差的执行计划。
5. 迁移过程中的高频故障与排查链路
无论准备多充分,迁移中都可能报错。下面这几个是我这次以及过往项目中遇到的高频问题,排查链路讲得细一些,遇到同类问题可以少走弯路。
5.1 ORA-39083 / ORA-01918:目标用户或表空间不存在
这个错误几乎是每个第一次用 Data Pump 的人都会遇到的。日志里主错误是ORA-39083: Error creating object C##TYPE_BODY,后面跟着具体 ORA-01918: user 'CRM' does not exist。
排查链路很简单:
- 用目标库查询
dba_users,确认用户是否存在。 - 如果用户不存在,新建用户并赋予必要的角色和权限。
- 如果用户存在,再看默认表空间是否存在,用户有没有对应表空间配额。
- 导入完成后如果权限对象报错,通常是在导入用户对象之前,授权的目标对象还没建好。这时不要盲目重导,等全部导入完成后用
GRANT语句补齐即可。
一个常见误区是以为 Full 模式导出导入会自动建用户。实际上 Data Pump 在全库导入时确实会尝试创建用户,但 schema 级导出导入不会,所以schemas=crm这种方式必须预先确保目标用户存在。
5.2 ORA-31684:对象已存在
如果目标库之前做过多轮导入,或者用户下已经残留同名表,就会报ORA-31684: Object "CRM"."T_ORDER" already exists。
排查链路:
- 先判断残留对象是否有数据。如果只是空表结构,可以留着,导入时用
table_exists_action=skip跳过。 - 如果残留对象里有脏数据,且你想让导入覆盖它,用
table_exists_action=replace。这个参数会先 drop 再建,代价是可能丢失表上的依赖对象,比如触发器、外键,需要后续重新处理。 - 如果只更新部分数据,可以用
table_exists_action=truncate,它会清空数据再导入。
我一般不建议直接设置table_exists_action=replace跑完整库,因为可能导致大量外键约束重建,也容易掩盖别的问题。更稳妥的做法是先清空目标 schema,再重新导入。比如:
-- 目标库确认可以清空后执行 BEGIN FOR r IN (SELECT table_name FROM user_tables) LOOP EXECUTE IMMEDIATE 'DROP TABLE ' || r.table_name || ' CASCADE CONSTRAINTS'; END LOOP; END; /5.3 ORA-02374 与字符集转换问题
ORA-02374 这类错误通常出现在源库字符集和目标库字符集不一致时,报错信息里还会带一句 “conversion error” 相关的说明。
排查链路:
- 先查源库和目标库的 NLS 参数:
SELECT parameter, value FROM nls_database_parameters WHERE parameter IN ('NLS_CHARACTERSET','NLS_NCHAR_CHARACTERSET','NLS_LANGUAGE','NLS_TERRITORY');- 如果源库是 ZHS16GBK,目标库是 AL32UTF8,正常是兼容的。问题是执行 impdp 的客户端进程必须有正确的
NLS_LANG。比如我在 Linux 服务器上执行导入,就要设置:
export NLS_LANG="SIMPLIFIED CHINESE_CHINA.ZHS16GBK"不要以为目标库是 AL32UTF8,客户端 NLS_LANG 就一定要设成 UTF8。Data Pump 导出文件里已包含了字符集元数据,导入时客户端 NLS_LANG 的主要作用是告诉进程如何解释外部数据。设错了,中文数据就可能在导入时发生二次转换,轻则乱码,重则 ORA-02374。
- 如果目标库字符集和源库不兼容,比如源库 AL32UTF8 里有一堆 emoji,目标库却是 ZHS16GBK,那么先不要强制导入。要么改源数据,要么重新评估字符集,否则导入后的数据已经损坏,后期修复成本极高。
5.4 ORA-01653 / ORA-01658:导入时表空间空间不足
导入大表时报ORA-01653: unable to extend table CRM.T_LOG by 128 in tablespace APP_DATA,或者建表时报 ORA-01658,说明目标表空间容量不够。
排查链路:
- 查看目标表空间使用率:
SELECT tablespace_name, SUM(bytes)/1024/1024 AS used_mb FROM dba_segments WHERE tablespace_name IN ('APP_DATA','APP_IDX') GROUP BY tablespace_name; SELECT file_name, bytes/1024/1024, autoextensible, maxbytes/1024/1024 FROM dba_data_files WHERE tablespace_name IN ('APP_DATA','APP_IDX');- 如果表空间没有开启自动扩展,就需要手工加数据文件;如果开启了自动扩展但 maxsize 设置太小,也需要扩容。
最容易踩的坑是,你把表数据 remap 到了一个新表空间,但源库表定义里有STORAGE子句或者INITIAL参数特别大,导入后每个段都申请了超大初始区。遇到这种情况,用transform=segment_attributes=N能一定程度上抹掉存储子句,但已有的 extent 已经产生,还是要把表空间容量预留足。
5.5 导入后 JOB、DB Link、ACL 缺失
这类问题最隐蔽,因为它不在日志里报 ORA 错误,而是上线几天后才暴露。比如定时任务没有在目标库重建,或者应用调用 UTL_HTTP 时权限不足。
如果要在 Data Pump 导入后重建 JOB,先查源库的 JOB 定义,再在目标库执行 DBMS_SCHEDULER 脚本。Data Pump 对 DBMS_SCHEDULER 的兼容性不如老式 DBMS_JOB,经常出现导入后 job 被 disabled 的情况。导入完成后要主动检查:
SELECT owner, job_name, enabled, state FROM dba_scheduler_jobs ORDER BY owner, job_name;如果 job 被 disabled,用DBMS_SCHEDULER.ENABLE重新启用就好。但如果 job 里引用的是源库表或源库 DB Link,哪怕 enable 了也会执行失败。
DB Link 导入后需要确认它指向的目标是否还是原来的库。如果源库停机后要废弃,源库上的 DB Link 就应该重新创建指向新库,或者用目标库的新连接串替换。这条链接如果忘记改,很多报表查询还是会跑到老库上去,DBA 调了半天才发现查的是旧连接。
ACL 是 11g 之后访问外部网络必须配置的。如果老库应用有 UTL_HTTP 请求第三方接口,迁移后必须重建 ACL:
BEGIN DBMS_NETWORK_ACL_ADMIN.APPEND_HOST_ACE( host => 'api.example.com', ace => xs$ace_type(privilege_list => xs$name_list('http'), principal_name => 'CRM', principal_type => xs_acl.ptype_db)); END; /5.6 执行计划变差
有时数据导入完成,应用也能连上,但业务查询突然变慢。最常见原因是统计信息缺失,或者 19c 优化器对老 SQL 的估算方式和 11g 不同。
排查链路:
- 先看目标库有没有统计信息,一般
DBA_TAB_STATISTICS里 LAST_ANALYZED 为 NULL 就说明没有收集。 - 重新执行
DBMS_STATS.GATHER_SCHEMA_STATS。 - 如果依旧慢,用
DBMS_XPLAN.DISPLAY_CURSOR看实际执行计划,对比源库。重点看表的基数估算是否偏差巨大,有没有出现FULL TABLE SCAN而源库走的是索引范围扫描。 - 如果只有个别 SQL 异常,可以用 SQL Plan Baseline 固定原执行计划,或者加查询提示。我建议先收集完统计信息再观察,直接上 Baseline 有时会掩盖更深的问题。
6. 迁移完成后的核对清单和上线切换建议
导入完成、无效对象编译完、统计信息收集完,不代表可以切业务。我习惯在正式切换前再做一轮“双库核对”,从数据完整性到应用功能逐项验证。
6.1 数据完整性核对
核对数据不是只数行数,而是分层验证:
- 对象数量核对:按 owner + object_type 对比源库和目标库,确认表、视图、索引、触发器、包等对象数量一致。
- 行数核对:对核心表执行 count。大表 count 很耗时,可以抽样重要表,或者用
DBMS_STATS.GET_TABLE_STATS里的 num_rows 对比,但最终还是要抽查几张核心表做全量 count。 - 序列核对:源库中每个序列取
LAST_NUMBER,目标库也取LAST_NUMBER,确保一致或目标库值大于源库。应用插入新数据时,序列值如果小于现有主键最大值,会立刻出现主键冲突。
其中行数核对我推荐用如下方式快速生成脚本:
SELECT 'SELECT ''' || table_name || ''' AS tbl, COUNT(*) AS cnt FROM ' || table_name || ';' AS sql_text FROM user_tables ORDER BY table_name;把生成的 SQL 在源库和各目标库跑一遍,把结果导出文件对比,差异一望即知。
6.2 应用与业务验证
DBA 能做的数据验证只是基础,业务验证必须由应用测试人员介入。我这次迁移中,应用侧验证清单包括:
- 登录验证:关键业务账号能否正常登录。
- 增删改验证:在业务测试账号下做几条事务,确认能成功提交和回滚。
- 报表验证:跑几份日结或月结报表,和数据比对是否一致。
- 定时任务验证:第二天早上观察 JOB 是否按计划执行。
- 外部接口验证:调用支付、短信、邮件等接口,确认 ACL、UTL_HTTP 权限正常。
这里我踩过一次教训:迁移后应用能登录,但所有工单号都显示为空,查到最后是 package body 里一个公共同义词指向了旧库的视图,源库一停,查询就报错。这种问题只有靠业务验证才能发现,DBA 的 SQL 统计验证完全看不到。
6.3 切换与回退方案
切换前设计好回退方案,并且回退方案要提前演练过。Data Pump 迁移的天然回退手段是保留源库,不着急下电。切换窗口内如果目标库应用验证发现问题,直接把应用指回源库即可。但这里有条前提:源库不能停写。如果切换时业务已经发到源库,源库要继续对外服务,那么回退很容易;如果源库已经停止服务并处于只读状态,回退后业务会丢失切换窗口到回退前的新数据。
所以我的建议是分两步走:
- 第一步:源库保持可读可写,目标库完成数据全量同步 + 最后一次增量导入,应用先切到目标库。
- 第二步:观察期(一般 24 到 72 小时)内,如果新库稳定,再让源库进入只读归档状态;如果新库出问题,应用立即回切到源库,几乎无损。
观察期结束后,记得在目标库做一次全量备份。很多团队迁移完就放松了,结果新库跑了三天没备份,一旦出问题连恢复介质都没有。备份完成后,再考虑下线源库,源库数据文件可以保存一段时间作为最后兜底。
6.4 我自己的收尾习惯
等应用完全跑稳,我会再花半天把这次迁移的文档补完,包括源库和目标库版本信息、迁移命令、导入日志、DB Link 清单、ACL 配置、回退步骤。信息不全的迁移文档等于没有文档,下一个人接手时还得重新猜一遍。
另外我会主动清理迁移产物,比如源库空目录下的 dump 文件、临时导入日志、测试用账号。这类文件看似无害,但会占用空间,也可能被安全扫描标记为敏感数据。数据文件该压缩归档的归档,该删除的删除,别留一摊烂摊子。
数据库迁移这事情,难就难在“没人替你踩过的坑,你大概率还要踩一遍”。工具原理就那么几件事,真正区分好坏的是准备够不够细、验证够不够狠、回退够不够快。希望这篇内容能让你在动手前把该想到的都想到,少走几段夜路。