
半夜两点机房的风扇声比平时更刺耳。我盯着屏幕上刚退回的impdp日志一行ORA-39083下面跟着十几张表创建失败而客户给的停机窗口只剩四个小时。那次是我第一次在生产环境里独立完成一次 Oracle 数据泵全库迁移之前我只会用老式的exp/imp结果几百 GB 的数据跑了一宿没跑完还因为序列没跟着走导致应用报错。从那以后我把 Oracle 数据泵 expdp/impdp 这套工具彻底啃了一遍前后在十几个项目里做过库间迁移、单表抽取、跨版本升级、异地容灾演练踩过的坑能写满两页纸。这篇东西就是把我这些年攒下来的实操经验摊开来讲。如果你是要给老系统做一次整库搬迁或者只是想把几张业务表从生产库同步到测试库又或者刚装完 Oracle 连DIRECTORY都不知道怎么写下面这些内容基本能覆盖你 90% 的场景。我会从为什么现在都推荐数据泵讲起一路讲到具体命令、参数取舍、报错排查中间会穿插我自己真实跑过的参数和踩过的坑命令你直接复制改改就能用。1. 先搞清楚数据泵解决的是什么问题1.1 exp/imp 和数据泵的本质差异老式的exp/imp是客户端工具数据要经过客户端这条链路数据库把数据读出来通过网络吐给 exp 客户端进程客户端再写成本地文件。这条链路里客户端进程是瓶颈而且它是一次会话单线程跑导出大表时你只能干等着。更麻烦的是 exp 出来的文件可移植性差字符集、版本、平台稍有不同就出各种 ORA 错误。数据泵不一样它是数据库内部的服务器端工具。你在客户端敲一条expdp命令真正干活的是数据库里一个叫 Data Pump 的后台作业数据在服务器本地内存和磁盘之间流动只有命令控制和日志通过网络传回客户端。这个设计带来的直接好处是可以并行、可以断点续跑、可以只导元数据、可以跨库直连搬运。我实测过一个 200GB 左右的库老 exp 跑到天亮才出 60%换成 expdp 加parallel8四十多分钟收工。还有一点经常被忽略数据泵的元数据是基于 Oracle 自己的 XML 对象描述生成的比老 exp 的 DDL 拼接方式更完整。分区表、约束、索引、权限、同义词、甚至审计策略这些东西只要参数配对基本能原样搬过去。老 exp 经常丢东西比如序列的当前值、外键的延迟属性迁完应用就出问题。1.2 目录对象绕不开的第一道门槛很多人第一次用数据泵被卡住都卡在同一个地方DIRECTORY。数据泵作业跑在数据库服务器上它读写的路径必须是数据库服务器视角下的路径而不是你客户端机器的路径。你没法像 exp 那样把 dump 文件往自己笔记本上一扔了事。所以数据库里引入了一个叫目录对象的东西它本质上是一条指向服务器某个绝对路径的别名。你建好之后expdp里写directoryMY_DIR数据库就知道该去哪个物理目录找文件和写文件。如果目录对象指向的路径在服务器上不存在或者 Oracle 进程账号没有写权限你会直接收到ORA-39070作业根本起不来。提示目录对象名是数据库对象大小写不敏感但物理路径是操作系统级别的Linux 区分大小写Windows 不区分。跨平台迁移时这一点必须提前确认。这里有个常见误解以为建了目录对象就万事大吉。实际上还得给用户授READ、WRITE权限否则一样报ORA-39002。我在一个客户现场见过 DBA 反复重建目录对象折腾两小时最后发现是权限没授。2. 环境准备与目录对象配置实操2.1 谁才有资格跑数据泵数据泵作业创建时数据库会在当前用户下建一张作业主表作业过程中还会用到临时段、读数据字典。所以跑数据泵的账号至少需要CREATE TABLE权限实际生产里我一般直接用一个拥有EXP_FULL_DATABASE/IMP_FULL_DATABASE角色的账号或者干脆用 DBA。因为整库、多 schema 导出必须要有这些角色单 schema 导出你用自己的业务账号加CREATE TABLE也能跑但一旦涉及导出别人 schema 的对象就会报权限不足。-- 给普通用户授最小权限组合 grant create session, create table to APPUSER; grant read, write on directory DP_DIR to APPUSER; -- 整库导出还需要 grant exp_full_database to APPUSER; grant imp_full_database to APPUSER;别图省事把所有权限一股脑全给业务账号这在等保场景下是硬伤。导出权限本身很敏感能把整库数据拖走所以生产环境我通常单独建一个迁移专用账号用完就锁。2.2 创建目录对象并验证读写先在操作系统层面建目录再进数据库建目录对象顺序不能反# 服务器上用 oracle 账号操作 mkdir -p /data/dump chown oracle:oinstall /data/dump chmod 750 /data/dump-- 数据库里执行 create or replace directory DP_DIR as /data/dump; select * from dba_directories where directory_name DP_DIR;验证能不能写最笨但最有效的办法是拿它跑一次最小作业expdp APPUSER/passwordorcl directoryDP_DIR dumpfiletest.dmp logfiletest.log tablesDUAL contentmetadata_onlyDUAL表存在于任何库里用contentmetadata_only只导元数据几秒钟就能跑完。如果这一步过了后面的整库导出就稳了一半。2.3 开工前必须确认的几个参数真正动手前我会先查三件事数据库版本、字符集、可用空间。select banner_full from v$version; select * from nls_database_parameters where parameter in (NLS_CHARACTERSET,NLS_NCHAR_CHARACTERSET); select tablespace_name, sum(bytes)/1024/1024/1024 as gb from dba_data_files group by tablespace_name;版本决定了你能不能加某些参数比如VERSION参数是 10g 之后才有的TRANSFORM里的某些选项也是分版本的。字符集决定了导出文件和目标库能不能对齐这个后面单独讲。空间则是被最多人低估的expdp默认会把数据落到目录路径你可以用ESTIMATE_ONLYY先估算体积而不真正导出expdp APPUSER/passwordorcl directoryDP_DIR schemasAPPUSER estimate_onlyy它会告诉你大致需要多少字节把这个数乘以 1.2 作为目录路径的预留空间比较稳妥因为日志、临时文件和%U多文件都会额外占用。3. 导出expdp全流程拆解3.1 四种导出模式怎么选数据泵的导出模式基本覆盖了所有需求但选错模式是最常见的低效来源。模式对应参数适用场景注意点全库fully整库搬迁、灾备演练需要EXP_FULL_DATABASE会带上系统 schema 对象SchemaschemasAPPUSER单业务系统迁移最常用可多 schema 逗号分隔表空间tablespacesAPP_TS按存储维度抽取需要EXP_FULL_DATABASE元数据耦合多表tablesT1,T2单表同步、临时抽数不会自动带依赖的索引和序列我个人的习惯是整库迁移用fully业务系统搬迁用schemas临时取数才用tables。用tables的时候一定记得把indexes、constraints、grants都带上或者干脆用exclude反向排除避免导出来的表是光秃秃的。3.2 PARALLEL、COMPRESSION、CONTENT 三个关键参数parallel是数据泵的加速核心它控制并发的工作进程数。设多少合适不是越大越好。我的经验值是对齐两个数字CPU 核心数的一半以及数据文件的数量。假设一台机器 16 核主表空间的 datafile 有 8 个那parallel8就很合适。设成 32 反而会互相抢 I/O性能不升反降。compression有两个常用取值all和data_only。all连元数据一起压缩文件更小data_only只压数据。压缩会消耗 CPU导出慢一点但省磁盘也省网络。如果你后面要通过网络把文件搬走压缩非常值得。我一般生产用compressionall。content决定导出内容all是数据和元数据都导data_only只导数据metadata_only只导结构。做结构比对、DDL 审查的时候用metadata_only特别方便文件小、跑得快还能直接看 XML 内容。3.3 一条可以直接抄的导出命令这是我常年用的模板多文件名用%U自动编号配合parallel会生成多个文件单文件用filesize控制大小expdp APPUSER/passwordorcl \ directoryDP_DIR \ dumpfileappuser_%U.dmp \ logfileappuser_exp.log \ schemasAPPUSER \ parallel8 \ filesize2G \ compressionall \ contentall \ excludestatistics \ flashback_timesystimestamp \ job_nameexp_appuser逐条解释一下我的取舍。excludestatistics是因为统计信息可以在目标库重新收集导过去反而可能因为数据分布不同导致执行计划异常。flashback_time让整个导出基于一个一致性时间点避免了A 表导出时是 100 行B 表导出时 A 表已经涨到 200 行这种不一致。job_name显式命名是为了后面能attach上去看进度。跑完后日志里会有一段汇总你会看到导出的对象数、行数、耗时、每秒吞吐。养成看日志的习惯我见过不少人导出报了一堆ORA-31684的警告却不看结果导入时才发现少了几十张表。3.4 导出体积估算与空间规划除了estimate_only我还会用字典表做交叉验证select sum(bytes)/1024/1024/1024 as gb from dba_segments where owner APPUSER;这个是段占用的实际空间包含索引和碎片通常比实际数据大。把两个数放在一起看取个中间值来规划目录空间。还有一个细节filesize2G并不是硬上限数据泵会以接近这个值为界切分文件所以最后可能生成一个 1.7G 的文件加一个 0.4G 的文件不要觉得奇怪。如果目标存储上有单文件大小限制比如某些对象存储filesize是必须设的。注意parallel和filesize一起用时生成的 dump 文件个数不会刚好等于 parallel 数文件是动态分配的。别用脚本按文件名去拼文件个数容易出错。4. 导入impdp全流程拆解4.1 REMAP_SCHEMA 与 REMAP_TABLESPACE 的典型用法导入最大的坑不是命令写错而是把数据直接灌回了源 schema把生产库覆盖了。所以只要做迁移我一定会先加remap_schemaimpdp SYSTEM/passwordtarget \ directoryDP_DIR \ dumpfileappuser_%U.dmp \ logfileappuser_imp.log \ remap_schemaAPPUSER:APPUSER_NEW \ remap_tablespaceAPP_TS:APP_TS_NEW \ table_exists_actionskip \ parallel8 \ job_nameimp_appuserremap_schema源:目标会把所有对象的归属改写。remap_tablespace源:目标同理处理表空间名字不一样的情况。这两个参数可以写多组用逗号隔开。有一个容易踩的点如果目标 schema 在目标库里已经存在导入不会自动建用户你得先手工建好并授好权限否则会报用户不存在或者权限不足。4.2 导入前的对象清理顺序导入到已有数据的库顺序很关键。正确的做法是先删干净再导而不是硬覆盖-- 以 APPUSER_NEW 为例 drop user APPUSER_NEW cascade; create user APPUSER_NEW identified by Passw0rd#2024 default tablespace APP_TS_NEW; grant connect, resource to APPUSER_NEW; grant unlimited tablespace to APPUSER_NEW;drop user ... cascade会把该用户下所有对象包括表、索引、视图、序列全部清掉比一个个删省事。4.3 表已存在时 TABLE_EXISTS_ACTION 怎么选这个参数有四个取值行为差异很大取值行为适用场景skip跳过已存在的表不导数据补差量、只想导新表append保留原数据追加导入增量合并注意主键冲突truncate先截断再插数据保留表结构全量覆盖数据replace删表重建连索引约束一起重建结构也要更新replace看起来最省事但它会重建表如果表上有其他对象依赖比如物化视图、外键操作会失败或者依赖失效。我一般在测试库用replace在生产库用truncate或者干脆先drop user cascade。4.4 大数据量导入的加速技巧导入阶段能做的优化比导出多。第一把索引和约束先排掉导完数据再建impdp ... excludeindex,constraint \ transformdisable_archive_logging:ytransformdisable_archive_logging:y是 11g 之后的一个利器能在导入期间关闭重做日志写入前提是数据库开了归档但你有权限大数据量导入能快三到五成。第二parallel同样适用于导入且导入的并行更容易吃满 I/O。第三如果目标库就是源库的两份拷贝直接开sqlfilecheck.sql只生成 SQL 不执行先审查一遍 DDL 再决定跑不跑impdp ... sqlfilecheck.sql这个技巧在我们做敏感系统迁移时帮了大忙生成的 SQL 可以先过一遍人工和合规审查确认没有意外对象再执行。5. 版本兼容与跨平台迁移的坑5.1 版本兼容矩阵要背下来数据泵有一条铁律导出的 dump 文件只能被同版本或更高版本的数据库导入不能导入到更低版本。11.2.0.4 导出的文件能进 12c、19c但进不了 10.2。反过来19c 导出的文件进不了 11g。如果你非要从高版本往低版本搬就得在导出时加version参数expdp ... version11.2.0.4这个参数会让导出的元数据按照指定版本生成但要注意如果源库里用了低版本不具备的新特性比如 12c 的隐藏列、19c 的 JSON 类型version参数也救不了这些对象会在导入时报错。-- 检查库里有没有高版本特性对象 select owner, object_name, object_type from dba_objects where object_type in (TYPE,PROCEDURE) and owner not in (SYS,SYSTEM) order by owner, object_type;5.2 字符集不一致导致的乱码字符集问题通常不报错但数据会悄悄变样。源库ZHS16GBK目标库AL32UTF8导入时中文字符可能变成问号或者被截断。根本原因是同一个中文字符在 GBK 里占 2 字节在 UTF8 里占 3 字节原本varchar2(10)的字段在目标库就放不下了。判断方法很简单导入前比对两边select * from nls_database_parameters where parameter NLS_CHARACTERSET;如果目标库字符集是源库的超集一般没问题如果源是 GBK 目标是 UTF8字段宽度最好按 1.5 倍以上预留在目标库的 DDL 里或者用transformsegment_attributes之类的参数配合手工调整 DDL。我处理过一个案例客户库字段全是varchar2(20)从 GBK 迁到 UTF8 后原本 9 个汉字的地址被截成了 6 个直接成了脏数据最后靠手工改 DDL 重建表才解决。5.3 导入完成后怎么验证导入成功不等于数据正确。我固定做三层校验-- 第一层对象数量对比 select object_type, count(*) from dba_objects where ownerAPPUSER group by object_type; -- 第二层表行数对比对行数有统计信息的表 select table_name, num_rows from dba_tables where ownerAPPUSER and num_rows is not null; -- 第三层空间占用对比 select sum(bytes)/1024/1024 as mb from dba_segments where ownerAPPUSER;对象数量对不上说明有对象没导过来行数对不上可能有表被跳过空间差太多可能有索引没建。我还会对几个核心业务表跑一次count(*)做抽样确认这些表通常不大但最关键值得花几分钟。6. 常见报错与排查速查表6.1 目录与权限类报错报错含义处理方式ORA-39002操作无效通常和目录权限、参数拼写有关先看完整堆栈ORA-39070无法打开日志文件目录对象路径在服务器不存在或没写权限ORA-39087目录名无效目录对象名拼错或未创建ORA-39145目录对象未指定命令里漏了directory这几个错误高度集中在目录这一环。我的排查顺序是先select * from dba_directories确认目录对象在不在再登上服务器ls -ld /data/dump看权限最后确认 Oracle 进程账号能不能写进去。这三步走完99% 的目录问题都能定位。6.2 作业相关的报错ORA-31626 job does not exist一般出现在attach一个已经结束或被清理的作业或者作业主表被误删。ORA-31684是常见的警告表示某个对象类型已存在被跳过通常不影响结果但如果是关键对象就要留意。ORA-39083是对象创建失败日志里会跟着一条更具体的错误比如表空间不存在、字段类型不支持需要往下一步看。作业异常中断后经常会在字典里留下残留主表select job_name, state from dba_datapump_jobs;如果有NOT RUNNING状态的记录对应的主表可以手工删掉否则下次同名作业起不来drop table SYSTEM.SYS_EXPORT_SCHEMA_01;6.3 长事务与一致性读相关导出时间过长时可能撞上ORA-01555 snapshot too old。原因是导出作业基于一致性读如果 UNDO 空间或undo_retention不足以支撑这么长的读事务旧数据就被覆盖了。解决办法是导出前调大 UNDO 表空间或者临时提高undo_retentionalter system set undo_retention 10800 scopeboth;如果是 11g 及以上还可以用flashback_time把导出绑到一个短时间窗口减少长时间一致性读的压力但根本解法还是给足 UNDO。6.4 字段长度与类型相关ORA-12899 value too large for column是最典型的字符集迁移后遗症前面讲过。还有一种是源库用了LONG类型高版本导入时可能转换成CLOB如果业务代码里对类型做了硬编码判断就会出问题。遇到这种导入前可以用sqlfile生成 DDL 先看一眼把有疑问的字段挑出来提前评估。7. 生产环境里的实战经验7.1 用参数文件 Shell 做定时导出命令太长直接写在 crontab 里容易出错我习惯把参数写进 parfile# /home/oracle/scripts/exp_appuser.par directoryDP_DIR dumpfileappuser_%U.dmp logfileappuser_exp_%DATE%.log schemasAPPUSER parallel6 filesize2G compressionall excludestatisticsShell 脚本里按日期生成文件名跑完检查日志关键字#!/bin/bash export ORACLE_SIDorcl DATE$(date %Y%m%d) expdp APPUSER/passwordorcl parfile/home/oracle/scripts/exp_appuser.par if grep -q successfully completed appuser_exp_${DATE}.log; then echo 导出成功 else echo 导出失败请检查日志 | mail -s expdp告警 dbaexample.com fi这套组合我在好几个客户那儿跑了两年多稳定性比想象中好。关键是日志要按日期归档不然几天就堆满了目录。7.2 停机窗口内的迁移节奏真正做整库迁移时间安排比参数更重要。我一般的节奏是迁移前一周做一次全量演练把每个阶段耗时记下来窗口开始后先停应用、锁写、跑一次最终增量导出导出完成后立刻在目标库导入导入期间源库保持只读导入完成做数据比对比对通过再切流量。这里有个我踩过的坑第一次做迁移时我导完就没管源库结果窗口里业务又写了几万条数据进去导入完成后两边对不上只能回滚重来。后来学乖了导出前先alter system enable restricted session或者把应用连接断开确保导出期间数据静止。7.3 网络链路直接搬运如果源库和目标库之间网络条件好其实可以省掉 dump 文件落地这一步用network_link直接搬impdp SYSTEM/passwordtarget \ directoryDP_DIR \ network_linkSRC_LINK \ remap_schemaAPPUSER:APPUSER_NEW \ logfilenet_imp.log \ parallel4SRC_LINK是目标库上一个指向源库的数据库链路。这种方式不需要中间存储但完全依赖网络稳定性网络一抖动作业就可能挂掉。所以我只在同机房、网络质量可控的场景下用跨机房还是老老实实落地再传文件可控性更强。这些年做下来我对数据泵最深的一点体会是它对参数的容忍度很高但对环境的容忍度很低。参数写错了顶多慢一点可目录、权限、字符集、版本这些东西一旦对不齐它会用一堆 ORA 报错把你堵在门口。所以每次动手前花二十分钟做环境体检比事后排查三小时划算得多。另外养成先估量、再演练、后正式的习惯我见过太多人第一次就敢在生产库上敲impdp不加remap_schema那个画面我现在想起来还替他们捏把汗。