先问一个问题:你最近一次做Oracle数据迁移或者逻辑备份,用的还是老的exp/imp吗?如果是的话,这篇值得你花五分钟看完。我在生产环境摸爬滚打这些年,见过太多从exp切到expdp(数据泵)之后一脸懵的同事——不是数据泵不好用,而是没搞明白它的运行机制和参数组合。标题里"导出用户、多用户、整个库、指定表"这四个词,恰恰是DBA日常里最常被问到的四类需求:把某个用户的对象整体迁走、把多个业务用户打包、给整个库做逻辑备份、甚至只挑几张关键表出来。这四种场景看着简单,但真用的时候,目录权限、过滤语法、版本兼容、并行策略,每一个都能让人卡半天。这篇文章就把它们完整过一遍,顺带把那些绕不开的坑也讲清楚。
1. 先搞清楚场景:expdp和老的exp/imp到底差在哪
1.1 expdp不是exp的升级版,是另一套工具
先说一个最常见的误区。很多人以为expdp就是exp的加强版,参数差不多、用法差不多,导出来的文件也能互相通用。实际上完全不是一回事。exp/imp是Oracle早期客户端工具,进程跑在客户端机器上;而数据泵(Data Pump)是Oracle 10g开始提供的服务器端工具,expdp/impdp命令虽然看起来是命令行的样子,但它真正干活的是数据库里的一组后台进程,文件也是落在数据库服务器本地的目录对象对应路径上。
这个差异直接决定了使用习惯:用expdp的时候,第一步不是写命令,而是确认你有没有directory对象的权限。这个问题后面单独讲,因为它是新手最容易翻车的地方。
再看文件格式。exp导出来的dmp,imp和impdp都能导入——虽然我不建议这么干,因为很多老的exp文件里带着版本兼容问题;但expdp导出来的dmp,老的imp是绝对读不了的。数据泵的文件格式、元数据结构都跟exp那一套不一样,它支持更细粒度的对象过滤、并行导出、压缩、加密,这些都是老工具给不了的。所以既然要写"导出"这件事,我建议你直接从expdp入门,不要再花时间学exp了。
1.2 四种导出场景的选型逻辑
标题里说的四种场景,对应到expdp其实就是几个参数的事:
| 需求描述 | 核心参数 | 典型命令片段 |
|---|---|---|
| 导出单个用户 | SCHEMAS=用户名 | expdp ... schemas=scott |
| 导出多个用户 | SCHEMAS=用户1,用户2 | expdp ... schemas=scott,hr |
| 导出整个库 | FULL=y | expdp ... full=y |
| 导出指定表 | TABLES=表名列表 | expdp ... tables=scott.emp |
参数看着简单,但组合起来学问就大了。比如"导出多个用户"的时候,这些用户之间可能有互相的约束关系;"导出整个库"的时候要不要排除系统schema;"导出指定表"的时候,你到底是只要这几张表的数据,还是报表结构、索引、触发器一起出?这些细节会直接影响命令的参数设计。
我个人做选型的时候,通常会先问三个问题:目标环境是什么版本?要迁移的对象范围是哪些?对数据一致性有没有要求?版本决定要用什么样的兼容参数,范围决定是用schemas还是tables还是full,一致性要求决定要不要加flashback相关的参数。带着这三个问题去看下面的内容,会顺很多。
2. 目录准备:directory对象和权限是数据泵的第一道门槛
2.1 先建directory对象,再谈导出
很多新手第一次敲expdp命令的时候,直接写expdp scott/tiger dumpfile=/home/oracle/backup/exp.dmp logfile=exp.log schemas=scott,然后系统哗哗报错。原因很简单:expdp不认操作系统的绝对路径,它只认识数据库里的directory目录对象。
这个directory对象是一个数据库实体,指向服务器文件系统上的某个路径,相当于给路径套了一层数据库的壳。需要通过SQL来创建:
-- 用sys或者有create directory权限的用户执行 create directory dump_dir as '/u01/backup';创建完成后还要把读写权限给到执行导出的用户:
grant read, write on directory dump_dir to scott;然后expdp命令里的directory参数填的才是dump_dir这个对象名,而不是路径:
expdp scott/tiger@orcl directory=dump_dir dumpfile=exp.dmp logfile=exp.log schemas=scott这里要特别注意:路径是数据库服务器上的本地路径,不是你自己电脑上的路径。哪怕你在本地客户端敲的expdp命令,文件最终还是落到服务器上。所以常见的工作流是:先在服务器上导好,再把文件从服务器拉回来。远程直接导出到本地的想法趁早放弃,数据泵不支持这种做法。
2.2 权限授予与安全建议
再往深一层说。directory对象创建之后,权限管理也要跟上。默认情况下,普通用户建不了directory,需要DBA授予CREATE ANY DIRECTORY权限。但实际生产环境我一般不会给业务账号授这个权限,而是统一由DBA建好目录对象,再按需授权。
-- DBA创建统一备份目录 create directory dmp_bak as '/u01/dmp_bak'; -- 给需要做导出的用户授权(只读或读写按需) grant read on directory dmp_bak to scott; grant write on directory dmp_bak to scott;如果只是需要读取别人导出的dmp文件做导入,那就只授read权限,不给write。这样即便账号被攻破,也不至于能往服务器任意写文件。
还有一个细节经常被忽略:操作系统层面的权限。directory对象指向/u01/dmp_bak,但Oracle数据库进程是以oracle操作系统用户跑的,如果这个目录属主不是oracle,或者oracle用户对目录没有写权限,数据泵还是会报错。所以建目录的时候建议用oracle用户来创建,保证属主正确:
mkdir -p /u01/dmp_bak chown oracle:oinstall /u01/dmp_bak chmod 775 /u01/dmp_bak这套组合是每次数据泵作业前的固定动作,检查完再往下走,能省掉后面90%的报错排查时间。
3. 导出单个用户和多个用户:schemas参数的高频用法与控制粒度
3.1 单用户导出的完整命令拆解
单个用户(或者说单个schema)导出,是日常用得最多的一种场景。开发给测试导数据、修复环境、准备升级,都会用到。Oracle里用户和schema基本可以划等号——一个用户登录后,它名下所有表、索引、视图、存储过程、序列合起来就是它的schema。所以"导用户"实质上就是在导这个schema里的全部对象。
最标准的命令长这样:
expdp system/manager@orcl \ directory=dump_dir \ dumpfile=scott_full.dmp \ logfile=scott_full.log \ schemas=scott \ version=19.1 \ compression=all参数拆开解释一下。schemas=scott指定要导出的用户;dumpfile是生成的文件名,建议加上用户名或业务标识,免得导多了分不清;logfile最好每次都写,不然排查问题的时候连日志都没有;compression=all压缩导出内容,能省不少磁盘空间和传输时间;version=19.1是版本兼容参数,源库是19c或更高的时候,如果目标环境是11g或者12c,务必加上对应版本号,否则导入的时候很容易报版本不兼容的错误。
生产环境导出的时候,还有两个参数值得关注。一个是flashback_scn或flashback_time,用来指定一个历史时间点或SCN,让导出期间的数据保持一致性快照。另一个是estimate_only=y,它只估算导出文件大小、不实际导出,用来判断磁盘空间够不够,非常实用:
expdp system/manager@orcl \ directory=dump_dir \ dumpfile=estimate.dmp \ schemas=scott \ estimate_only=y从输出里能看到预估的MB数,再结合df -h看磁盘剩余,心里就有数了。
3.2 多用户导出:一个schemas参数搞定
多用户导出的命令,其实就是把schemas参数里追加多个用户,用逗号分隔:
expdp system/manager@orcl \ directory=dump_dir \ dumpfile=multi_schema.dmp \ logfile=multi_schema.log \ schemas=scott,hr,oe这里有个限制要先说明:普通用户只能导自己的schema,如果想去导别人的schema,需要EXP_FULL_DATABASE角色或者对应的系统权限。所以实际生产里多用户导出大多用system账号或者专门的dba账号来执行。
还有一个容易忽略的地方:多个用户之间存在外键关联。比如scott用户下的某张表引用了hr用户下的主键表,如果只把scott导走,目标库里hr的表不存在,导入时会报外键相关的错误。我的经验是,导多用户之前先理清业务依赖,把强关联的用户放同一个schemas列表里一起导,别拆开。
3.3 用exclude参数精细化控制
有时候我不想把整个用户全导出去,比如某个用户名下有一张巨无霸日志表,光它就能让导出变慢好几倍。这种时候可以用exclude参数把指定对象排除掉:
expdp system/manager@orcl \ directory=dump_dir \ dumpfile=scott_nolog.dmp \ logfile=scott_nolog.log \ schemas=scott \ exclude=table:"IN ('T_LOG','T_AUDIT')"注意这个语法细节:exclude后面跟的是对象类型和过滤条件,表名要放在单引号里,外层用双引号包住。在Linux shell下这么写没问题,但到了Windows的CMD或PowerShell下转义规则又不一样,经常出幺蛾子。我的建议是:过滤条件复杂的时候,干脆把参数写到一个文件里,用parfile参数指定它:
expdp system/manager@orcl parfile=exp_par.txt文件内容:
directory=dump_dir dumpfile=scott_nolog.dmp logfile=scott_nolog.log schemas=scott exclude=table:"IN ('T_LOG','T_AUDIT')"参数文件的好处是既避免了shell转义问题,又能把一长串参数整理得清清楚楚,方便复用和版本管理。
4. 整个库导出:full=y背后的取舍与系统表处理
4.1 full=y之前,先想清楚这几个问题
"导出整个库"听起来最简单,一个full=y就完事,但实际它是最需要谨慎的一类操作。因为全库导出的对象量非常大,包括所有用户的数据、所有系统表、存储过程、序列、同义词、物化视图,甚至包括一些底层元数据。如果只是想要"业务数据"的全量备份,直接full=y反而会导出一堆系统对象,目标库再导入的时候还容易冲突。
我一般在以下几种情况才会用整库导出:一是数据库迁移,两边环境版本接近,想把所有业务用户一次性搬过去;二是做逻辑备份的兜底方案;三是库很小、用户很少,直接全库导出然后导入到新环境最省事。
4.2 整库导出的常用排除与版本控制
整库导出的命令本身不复杂:
expdp system/manager@orcl \ directory=dump_dir \ dumpfile=full_db.dmp \ logfile=full_db.log \ full=y \ version=19.1 \ exclude=schema:"IN ('SYSTEM','XDB','ORDSYS','MDSYS','OLAPSYS','EXFSYS','WMSYS','DBSNMP','OUTLN','CTXSYS','APPQOSSYS','ORDDATA')"这个exclude列表是我根据自己的实战经验积累的,把那些系统自带的schema排除掉。理由很简单:这些schema里的对象通常都是Oracle内部功能用的,业务迁移用不到,而且它们往往带有版本特性、补丁状态相关信息,导入到目标库容易造成冲突。当然,不同的Oracle版本对系统schema的要求不一样,12c以后还引入了PDB、CDB相关的对象,所以具体排除列表要结合目标环境调整。
还有一点:12c之后如果用的是多租户架构,在PDB里执行full=y导出的是当前PDB,而不是整个CDB实例。如果要从CDB层面全库导出,需要在root容器里操作,而且需要考虑PDB是否处于mount或open状态。这块内容展开讲又是一大篇,这里先提醒一句,免得你发现导出来的文件里少了一大半数据却不知道原因。
4.3 整库导入时候的反向注意事项
导出的另一半是导入。全库导出文件在导入的时候,我最担心的是权限、同义词和公共同义词的指向。有些业务表在用户A下面,但是用户B建了公有同义词指向它;导到新库之后,如果能保证用户A、用户B都存在,问题不大;但如果用户本身不存在,导入会直接报错。
所以整库导出迁移的实践里,我建议同步把用户创建脚本也准备好,先建用户再导数据,顺序别反了。数据泵导入的时候,impdp默认不会帮你去create user,除非你用了transform=segment_attributes之类的参数配合另外的选项。最稳的做法是:导全库的时候,顺便用数据泵把用户元数据一起带上,导入端加create database之类的逻辑,但这个比较进阶,日常用还是先把用户建好再走导入流程。
5. 指定表导出:include/exclude/query的组合玩法
5.1 tables参数的两种用法
指定表导出是开发提需求时最常碰到的场景:"帮我把生产环境上XX表的数据导出来分析一下"。最简单的方式就是tables参数直接指定,可以带schema点名:
expdp system/manager@orcl \ directory=dump_dir \ dumpfile=tables_emp.dmp \ logfile=tables_emp.log \ tables=scott.emp同时导多张表,用逗号分隔:
expdp system/manager@orcl \ directory=dump_dir \ dumpfile=tables_core.dmp \ logfile=tables_core.log \ tables=scott.emp,scott.dept,hr.locations注意两个坑。第一个,tables参数如果不带schema,默认就是当前登录用户的schema,所以最好都显式写schema.表名。第二个,表名在数据泵里默认会转成大写,如果表是用小写或混合大小写建的(加了引号创建的那种),过滤和匹配时要处理特殊情况,否则会报"table not found"。
5.2 include参数:白名单方式的灵活过滤
跟tables直接列表名相比,include参数更强大,它可以按对象类型来做白名单过滤。比如只想导出scott用户下的所有表,并且只要emp、dept、salgrade这三张:
expdp system/manager@orcl \ directory=dump_dir \ dumpfile=include_tables.dmp \ logfile=include_tables.log \ schemas=scott \ include=table:"IN ('EMP','DEPT','SALGRADE')"注意:这里用的是schemas=scott配合include过滤,而不是直接写tables=...。区别在于tables参数还会自动带上表相关的索引、约束、触发器这些依赖对象,而include=table则是严格按照对象类型来过滤——如果只写了table类型,那么索引、约束、授权、触发器都不会被导出,拿到目标库之后表是光秃秃的,没有约束和索引。这到底好不好,取决于你的需求。如果只要数据,那无所谓;如果要把表完整搬过去,还是用tables更省事。
5.3 query参数:只导满足条件的数据
更细一级的需求是"表可以整张导,但我只要其中一部分数据"。比如只要emp表里部门编号为10的数据:
expdp system/manager@orcl \ directory=dump_dir \ dumpfile=emp_dept10.dmp \ logfile=emp_dept10.log \ tables=scott.emp \ query=scott.emp:"WHERE deptno=10"query参数的执行逻辑是在导出阶段就对源表做数据过滤,条件写在表名后面的冒号里。多张表各自带过滤条件也是可以的,比如:
query=scott.emp:"WHERE deptno=10",scott.dept:"WHERE deptno IN (10,20)"这里必须提醒一件事:query参数只对表数据生效,对元数据不生效。而且如果目标表上有关联的外键约束,你只导了部分数据,导入时很可能因为缺父表数据导致约束校验失败。所以用query做数据抽取之前,先想清楚数据间的关联关系。
5.4 content参数:只要结构还是只要数据
最后一个高频参数是content,它控制导出内容的类型:
content=all:默认值,结构+数据都导content=data_only:只要数据,不导建表语句content=metadata_only:只要结构,不导数据
只导数据比较常见于:目标库表结构已经建好了,只需要把生产库的数据灌进去。只导结构则常见于:先在新环境把表结构建好,再通过别的方式同步数据。
# 只导数据 expdp system/manager@orcl \ directory=dump_dir \ dumpfile=emp_data.dmp \ tables=scott.emp \ content=data_only # 只导结构 expdp system/manager@orcl \ directory=dump_dir \ dumpfile=emp_meta.dmp \ tables=scott.emp \ content=metadata_only使用content=data_only导出的文件,导入之前必须确保目标表的表结构已经存在,否则impdp会直接报错。这一点在写自动化脚本的时候特别容易漏。
6. 踩坑复盘:几个最常遇见的错误和排查思路
6.1 ORA-31623 / ORA-39002:版本和作业状态引发的连锁报错
ORA-31623和ORA-39002是我在实际运维中收到过最多的问题。ORA-31623通常提示的是作业参数无效,ORA-39002则提示操作无效,两者经常一起出现。排查思路是这样的:先查数据库版本和数据泵版本是否匹配,如果源库版本高于数据泵客户端版本,导出的文件目标库可能不认;再看dmp文件头部版本信息,如果文件是19c导的、目标库是11g,报错就是必然的,解决办法是用version=参数指定低版本导出。
另外,数据泵作业偶尔会因为前一次操作中断而残留在数据库里,导致后续作业起不来。可以查询并清理:
-- 查看当前数据泵作业 SELECT * FROM dba_datapump_jobs; -- 如果发现残作业,用impdp或expdp attach上去做stop/kill处理 expdp system/manager@orcl attach=SYS_IMPORT_FULL_016.2 找不到dumpfile:导出文件到底落在哪
还有一种高频问题,是用户问"我文件导出成功了,但路径在哪找不着"。这往往是因为对directory对象的路径不了解。expdp最终输出的路径,以directory对象指向的服务器路径为准,而不是你命令行里写的工作目录。查询方式:
select owner, directory_name, directory_path from dba_directories where directory_name = 'DUMP_DIR';拿到路径后,再去服务器上用ls -l确认文件存在。很多人在本地电脑上找半天,是因为不知道文件根本没落在客户端。
6.3 字符集与目标环境不一致的应对
导出导入过程中,字符集不一致是个慢性病。源库字符集是ZHS16GBK,目标库是AL32UTF8,数据导过去之后中文可能变成乱码或者问号。在导出前先确认两边的字符集:
select value from nls_database_parameters where parameter='NLS_CHARACTERSET';如果有差异,能改的话尽量对齐;改不了的话,导入后要做数据抽样验证。这个问题的根因是逻辑备份本质上导出的是数据内容,字符集转换发生在导入阶段,而数据泵对字符集转换的支持并不总是尽如人意的,尤其是SQL里硬编码的字符串、注释、存储过程源码里带的中文,都可能在转换过程出问题。
6.4 两个提高效率的小习惯
最后分享两个我自己的使用习惯。第一个是并行度:导出和导入时都可以设置parallel=4这样类似的参数,配合dumpfile=exp_%U.dmp的多文件方式,能显著缩短大表导出时间。但注意parallel不是越大越好,还要看服务器的CPU和IO能力,我一般先在4到8之间试。
第二个是保留历史日志。数据泵的logfile默认每次导出覆盖同名文件,我建议在文件名里带上日期:
dumpfile=exp_$(date +%Y%m%d).dmp logfile=exp_$(date +%Y%m%d).log这样每次导出的文件不会互相覆盖,出了问题还能追溯是哪一天导的、当时用了什么参数。代价只是多几个文件,却能在恢复数据时省下大把时间。
数据泵这套工具,说简单也简单,无非是参数组合的问题;说复杂也复杂,版本、字符集、权限、依赖关系、作业残留,每一个都能让人踩到怀疑人生。从我个人的经验看,最靠谱的成长路径就是反复在一套测试环境上练习:把单用户导出多用户导出整库导出指定表这四类场景各自跑通一遍,再故意制造一些报错去排查,见过足够多的异常输出,到了生产环境才能不慌。希望这篇文章能让你少走几步弯路,哪怕只是避开了"找不到file"和"版本不兼容"这两个大坑,也算值了。