
上周接了个数据泵迁移的活儿客户把一套跑了几年的生产库导数据脚本交过来里面反复出现一个叫EXP_DIR的目录对象可交接文档里偏偏没写它映射到服务器哪个目录。我当时的第一反应就是查一下create directory的目录路径结果整个排查过程让我意识到这个看起来基础得不能再基础的需求放到真实环境里远不止一条SELECT语句那么简单——权限分层、视图可见范围、路径是否真实存在、改了路径之后授权会不会丢每一环都可能卡住你。这篇文章就把查看create directory的目录路径这件事讲透。不管你是DBA、运维还是偶尔要连数据库看外部表路径的开发者只要碰到目录对象相关的问题照着里面的思路走一遍基本不会再被这种小问题绊住。1. CREATE DIRECTORY创建的到底是什么目录对象与文件系统路径的关系1.1 一条SQL并不是真的建目录很多新手会下意识把CREATE DIRECTORY理解成在操作系统上新建了一个文件夹其实完全两码事。你在数据库里执行的是这样一条语句CREATE DIRECTORY dump_dir AS /u01/app/oracle/dump;这只是在Oracle数据字典里注册了一条逻辑记录告诉数据库以后凡是引用dump_dir这个名字的地方都对应服务器上的/u01/app/oracle/dump路径。操作系统层面不会因为你执行了这条SQL就自动把/u01/app/oracle/dump这个目录创建出来。如果OS上压根没有这个路径CREATE DIRECTORY在很多默认配置下也不会立即报错等到数据泵作业真正去读写文件的时候才翻车。可以把这个逻辑记录想象成快递柜的取件码。取件码只是一个编号柜门到底存不存在、格子能不能打开是另一回事。所以很多DBA排查时会遇到查出来的路径明明对作业却起不来的情况根源就在这里directory是逻辑对象路径字符串是死的物理目录是活的。1.2 目录对象存在哪里谁有权限查创建好的目录对象会以元数据的形式保存在数据字典里日常通过数据字典视图来查看。跟其他数据库对象一样Oracle按访问范围提供了三个常用的视图。这个权限分层在查路径时特别重要很多人上来就写SELECT * FROM dba_directories结果报表或视图不存在其实不是SQL写错了而是当前账号压根没有DBA权限。视图可见范围权限要求典型场景DBA_DIRECTORIES数据库中所有目录对象需要DBA角色或SELECT ANY DICTIONARY权限DBA排查全局情况ALL_DIRECTORIES当前用户有权限访问的目录对象只要被授予过某个目录的READ/WRITE即可开发、运维自查USER_DIRECTORIES当前用户自己创建的目录对象无需额外授权个人创建的个人用目录这里有个容易产生错觉的点ALL_DIRECTORIES名字里带ALL很多人以为它会显示所有目录实际上它只显示当前用户能访问的目录。没被授权过的目录对象根本不会出现在结果里。我见过有同事用ALL_DIRECTORIES排查半天得出数据库里没有这个目录的结论实际上只是当前账号没权限看不到而已。1.3 哪些功能会用到目录对象搞清楚目录对象的性质之后还要明白它到底在哪些场景里出现。因为查目录路径这个需求几乎总是跟着下面这些功能一起出现数据泵导入导出expdp/impdp命令的DIRECTORY参数指向的就是目录对象。外部表创建外部表时通过DEFAULT_DIRECTORY指定数据文件所在目录。UTL_FILE文件读写PL/SQL里访问服务器文件系统的必经之路。BFILE、ORACLE_LOADER等访问驱动读取外部二进制文件或文本文件。在这些场景里目录对象指向的路径一旦不对报错通常很隐晦。所以每当遇到数据泵导不出、外部表查不到数据、UTL_FILE报文件操作失败我的习惯是先查一遍目录路径再考虑其他问题。这比直接看错误码要快得多。2. 查系统视图DBA_DIRECTORIES最直接的一条SQL2.1 一条SQL看全路径如果当前账户有DBA权限最简单的方式就是直接查DBA_DIRECTORIESSELECT owner, directory_name, directory_path FROM dba_directories ORDER BY owner, directory_name;每一行就是一个目录对象DIRECTORY_PATH列就是它在操作系统上的完整路径。每条记录的大致样子是OWNER为SYSDIRECTORY_NAME为EXP_DIRDIRECTORY_PATH为/u01/app/oracle/dump。如果库里的目录对象很多可以加WHERE条件按名称过滤SELECT directory_path FROM dba_directories WHERE directory_name EXP_DIR;这里要提醒一个特别容易踩的坑Oracle默认把目录名以大写方式存储在数据字典里所以上面WHERE条件里的EXP_DIR按大写写就能命中。但如果当初创建目录时用了双引号比如CREATE DIRECTORY Exp_Dir AS ...那么目录名就是大小写混合的查询时必须带上准确的大小写。很多人查不到记录不是目录没建而是大小写写错了。2.2 SQL*Plus输出格式路径太长怎么处理查路径这个动作本身不难难的是在SQL*Plus里把结果看舒服。DIRECTORY_PATH通常是一长串绝对路径默认显示设置下很容易被截断或者折行一眼扫过去非常痛苦。我习惯先做两件事COLUMN owner FORMAT A20 COLUMN directory_name FORMAT A30 COLUMN directory_path FORMAT A80这样owner列宽20、目录名列宽30、路径列宽80绝大多数路径都能完整展示。如果路径特别长还可以配合SET WRAP OFF关闭换行。这两个设置在排障时的价值不亚于SQL本身要不然路径断成两行复制起来都容易带出多余空格。2.3 顺带把授权情况一起查出来DBA_DIRECTORIES只能告诉你路径是什么不能告诉你谁有权限读写这个目录。遇到作业报权限错误的时候还得继续查dba_tab_privs。我一般会一条SQL把路径和授权都拉出来SELECT d.owner, d.directory_name, d.directory_path, t.grantee, t.privilege_type FROM dba_directories d LEFT JOIN dba_tab_privs t ON t.table_name d.directory_name WHERE d.directory_name EXP_DIR;这样一次就能看清三件事目录指向哪、谁有读权限、谁有写权限。很多时候数据泵作业报错根本不是路径问题而是执行作业的数据库用户没被授予目录的WRITE权限。先看授权再看路径能省掉大量回头路。3. 没有DBA权限时的查路径思路ALL_DIRECTORIES、USER_DIRECTORIES与DDL还原3.1 按可见范围切换视图生产环境里数据库账号普遍讲究最小权限不是谁都有DBA角色。这时候就得按自己的权限范围选择视图。如果当前账号被授予过某些目录的读写权限可以用ALL_DIRECTORIESSELECT owner, directory_name, directory_path FROM all_directories;如果你的目录对象是自己创建的连授权都不用关心直接查USER_DIRECTORIESSELECT directory_name, directory_path FROM user_directories;USER_DIRECTORIES比全字段查询少一个OWNER列因为结果天然就是当前用户自己的目录。如果你用Python连接Oracle查询逻辑也完全一样就是一条普通SQL区别只在于用cursor执行然后逐行读取。所以工具不是问题权限可见范围才是核心。3.2 用DBMS_METADATA反向还原DDL如果连ALL_DIRECTORIES都用不了还可以试试DBMS_METADATA.GET_DDL。这个包能把目录对象完整的创建语句捞出来包括路径和引号写法非常直观SELECT dbms_metadata.get_ddl(DIRECTORY, EXP_DIR, SYS) FROM dual;输出大概是这样CREATE OR REPLACE DIRECTORY EXP_DIR AS /u01/app/oracle/dump这个方案的好处在于是直接基于对象的元数据生成DDL不会因为视图权限而限制目录对象本身是否可见。前提是当前账号有DBMS_METADATA的执行权限不过在不少环境里开发账号都有这个权限这条路往往比申请DBA角色快得多。它还能帮你看出目录名创建时是不是用了大小写混合的双引号写法这对后续查询和引用很有帮助。3.3 从外部表和脚本反推目录路径还有一种更旁门左道的情况手头什么目录视图权限都没有但知道某个外部表加载数据时用到了目录对象。这时候可以查ALL_EXTERNAL_TABLES拿到外部表的默认目录名再想办法通过其他途径拿到路径。虽然这个办法比较绕但在应急排查时聊胜于无。更常见的是你手里只有一份expdp或impdp的参数文件里面写着DIRECTORYEXP_DIR。此时最快的办法其实是去服务器上用操作系统命令grep一下相关脚本看看项目里是不是有固定的路径约定。当然最稳的做法还是申请对应视图的SELECT权限把权限这件事一次性解决不然每次查路径都要走旁路效率太低了。4. 查出来的路径不能轻信目录对象与OS目录的健康检查4.1 数据字典里的路径是死字符串这是整篇文章里我最想强调的一点DBA_DIRECTORIES里的DIRECTORY_PATH是一个创建时写入的字符串Oracle不会在每次查询时去操作系统上核对这个目录还在不在。我实际遇到过的情况是这样的A环境创建了目录对象指向/data/oracle/dump后来这套库通过克隆方式部署到B环境/data/oracle/dump在B环境根本不存在但DBA_DIRECTORIES里照常显示这条记录。等数据泵作业一跑立刻报错。所以凡是做过环境迁移、备份恢复、存储切换一定要重新核对目录对象对应的物理路径。最直接的验证方式是到服务器上用操作系统命令看一眼ls -ld /u01/app/oracle/dump注意要用运行数据库的属主用户通常是oracle去执行因为数据库进程在OS层面的权限取决于这个用户。目录不存在会看到No such file or directory目录存在但权限不对后续照样读写失败。4.2 在PL/SQL里做半自动健康检查不想登录服务器或者想把目录检查固化到巡检脚本里可以用UTL_FILE做验证。思路很简单尝试往目录对象里写一个临时文件能写成说明整个链路基本没问题不能写就根据错误信息判断是哪一环出了问题。SET SERVEROUTPUT ON DECLARE v_path VARCHAR2(200); v_file UTL_FILE.FILE_TYPE; BEGIN SELECT directory_path INTO v_path FROM dba_directories WHERE directory_name EXP_DIR; DBMS_OUTPUT.PUT_LINE(路径: || v_path); v_file : UTL_FILE.FOPEN(EXP_DIR, ora_check.tmp, W); UTL_FILE.FCLOSE(v_file); DBMS_OUTPUT.PUT_LINE(目录可写); EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(检查失败: || SQLERRM); END; /这个脚本跑下去有几个前提当前用户对EXP_DIR目录有WRITE权限、物理路径真实存在、数据库进程的OS用户拥有该目录的写权限。所以它返回错误时需要结合具体情况分层排查。如果是ORA-29280这类目录权限错误优先检查授权如果是ORA-29283这类文件操作错误优先检查物理路径和OS权限。测试完记得把ora_check.tmp临时文件删掉别留垃圾。4.3 RAC和ASM环境下的路径验证差异如果你的数据库是RAC集群验证路径时要多留个心眼。共享目录必须确保所有节点都挂载了相同路径否则经常出现一个节点能导出、另一个节点报路径不存在的情况。遇到这种间歇性失败先别怀疑配置去每个节点各执行一次ls -ld确定所有节点的可见性一致。如果数据库跑在ASM上情况又不一样。目录对象可能指向的是ASM磁盘组里的路径比如DATA/DB_DUMP/expdir这种路径用普通的ls看不到得用asmcmd或SQL去数据库侧查。这时候与其纠结OS路径不如直接去ASM文件系统里看目录层级。搞清楚了存储形态就不会把ASM路径误判成不存在。5. 改了目录路径之后重建授权与连带影响5.1 修改路径的正确方式CREATE OR REPLACE DIRECTORY目录对象创建之后路径并不是一成不变的。存储空间迁移、目录重构都可能导致路径变化。Oracle不支持ALTER DIRECTORY来修改路径但支持CREATE OR REPLACE DIRECTORYCREATE OR REPLACE DIRECTORY EXP_DIR AS /new/oracle/dump;这个语句相当于重新定义了目录对象指向的新路径。但在生产库里执行之前一定要先想清楚有多少作业引用了这个目录。数据泵的dump文件、外部表的数据文件、UTL_FILE读写的文件全部都会受到路径变更的影响。我见过最典型的翻车现场是DBA把目录路径改到新存储忘了同步归档脚本结果当天晚上备份脚本还在原路径找文件折腾了一个小时才发现导出的dump文件已经写到了新路径上。5.2 重建后授权丢失的连锁反应比改路径更容易踩的坑是用DROP DIRECTORY加CREATE DIRECTORY的方式重建目录。很多人手动删掉再重建以为目录名不变就完事了结果发现所有对目录的READ、WRITE授权全部跟着没了。Oracle的授权是挂在目录对象本身上的DROP之后对象都没了授权自然一并消失。重建同名目录之后原来的授权不会自动恢复。这时候需要重新执行授权GRANT READ, WRITE ON DIRECTORY EXP_DIR TO apps;这个坑隐蔽就隐蔽在它不会立刻报错。有些作业可能用了高权限用户跑没感觉一旦切到普通应用账号执行马上就冒出来权限不足。所以不管是用CREATE OR REPLACE还是DROP重建改完路径之后都建议重新查一遍授权清单SELECT grantee, privilege_type, table_name FROM dba_tab_privs WHERE table_name EXP_DIR;5.3 批量迁移时的授权快照脚本如果是大批量迁移目录比如整套环境从一个存储挪到另一个存储一个个查授权显然不现实。我会在动手之前先把当前授权生成一份快照脚本等迁移完成后再执行一次避免遗漏SELECT GRANT || privilege_type || ON DIRECTORY || table_name || TO || grantee || ; FROM dba_tab_privs WHERE table_name IN (SELECT directory_name FROM dba_directories);这样生成的GRANT语句可以直接批量执行。配合前面讲的健康检查脚本迁移完目录之后可以形成一套固定动作先对比DBA_DIRECTORIES里的路径是否全部更新到位再跑一遍授权快照脚本最后用UTL_FILE实际写一个临时文件验证。这套流程走下来目录相关的事故基本能避免一大半。另外还有一个多租户环境下的注意点登录PDB和登录CDB根容器时DBA_DIRECTORIES能看到的目录对象范围不一样。如果你在PDB里查不到某个目录先确认当前会话到底连接的是哪个容器别在同一套数据库里来回折腾还找不到原因。我在实际项目里已经养成了一个习惯接手任何一套Oracle环境第一件事就是导出一份目录对象清单把路径、属主、授权一次查清并存档。遇到数据泵、外部表、文件读写相关的故障先翻这份清单基本能过滤掉一半以上的低级问题。目录对象这个东西平时不起眼但真要用到的时候路径对不对、权限够不够、物理目录存不存在每一个小细节都可能让业务停下来。希望这篇围绕查看create directory目录路径的实战梳理能帮你少走几次弯路。