文章
服务器与存储管理
作者:Yuli Vasiliev
2013 年 9 月发布
能否将 Linux 实用程序视作数据库表?显然可以 — 通过 Oracle Database 11g 中为外部表提供的预处理器指令特性。其工作方式如下。将操作系统实用程序包装在 bash 脚本中,在创建外部表时将该脚本指定为预处理器程序。然后,要调用该实用程序并以表行形式获取生成的结果,只需在 SQL 语句中查询外部表。其实现方式类似于查询常规关系表。换句话说,这种方法让您能够将操作系统数据组织成表的形式,就像其他数据库数据一样。您可能会问:这有什么好处?主要好处是可以轻松地将操作系统数据与数据库数据联接 — 全部在一条 SQL 查询中实现。
|
值得注意的是,上述方法并不是从数据库内部启动操作系统实用程序的唯一方法。例如,使用 DBMS_SCHEDULER 的 CREATE_PROGRAM 过程,可以按计划或直接从 PL/SQL 代码中启动外部可执行文件。
要使用本文提供的示例,您需要一个 Linux 操作系统并在其上安装了 Oracle Database。如果 Linux 系统上未安装 Oracle Database(或未安装 Linux),最简单、最快速准备工作环境的办法是安装 Oracle VM VirtualBox(适用于 Microsoft Windows、Mac OS X、Oracle Solaris 和 Linux),然后安装虚拟来宾软件设备,它提供预配置的 Oracle 软件。这个软件设备包含作为操作系统的 Oracle Linux,预配置了 Oracle Database 企业版 11g 第 2 版,可立即投入使用。
在 Oracle Database 中,外部表特性可用作数据加载池,很好地替代了 SQL Loader。还可以使用外部表查询操作系统数据,用操作系统实用程序生成的输出填充外部表。此方法基于使用从 Oracle Database 11g 开始可用于外部表的预处理器指令特性。简单地说,编写一个调用系统实用程序的 shell 脚本,然后在外部表中将该脚本指定为预处理器程序。一般步骤如下:
图 1 显示使用外部表查询操作系统过程的可能状况:

图 1:使用外部表查询操作系统的一般步骤。
此方法可能适用于许多 Linux 实用程序,包括显示磁盘空间和系统信息的实用程序,以及执行监视/调试的实用程序等等。因此,您可以轻松地将此方法用于 df、ps、ls、du、env、uname 等命令。但请注意,大多数情况下,在执行数据库相关任务时,可以避免直接使用操作系统实用程序,而是依赖于数据库 SQL 命令和数据库对象。
以下示例展示如何使用外部表包装对 Database File System 文件系统(包含 Oracle 数据库中存储的文件)发出的 ls 命令的输入。
注:Database File System 是一个 Oracle Database 11g 特性,它提供了一个标准文件系统接口,用于将文件存储在数据库中和访问数据库中的文件。这些文件以 SecureFiles LOB 形式存储在数据库表中。同时,由于该文件系统挂载在常规挂载点上,您可以像访问 Linux 中任何其他文件系统一样访问它。Database File System 的详细信息不在本文讨论范围内。有关简要介绍,请参见 Oracle Database SecureFiles 和大对象开发人员指南 中的“Oracle Database File System 简介”一节。
本示例假定您已在 Linux 系统上创建并挂载了 DBFS 文件系统。如果没有,现在可以按照以下步骤完成此操作(这些只是一般步骤;有关详细信息,请参见“DBFS 文件系统客户端”)。
dbfs_create_filesystem.sql 脚本创建文件系统。kernel-devel 软件包和 FUSE 软件包(此步骤在 Linux 上是必需的)。dbfs_client 脚本创建的文件系统。完成上述步骤之后,可以使用已创建并挂载的 DBFS 文件系统。第一步可能是将一些文件复制到该文件系统。
接下来,您可能会输入文件系统的根目录,然后发出 ls 命令以确保此文件系统就像系统中的任何其他文件系统一样:
$ cd /mnt/dbfs/staging_area $ ls -a -l -X total 9 -rw-r--r-- 1 oracle oracle 1110 Jul 4 21:48 test01.bmp -rwxrwxrwx 1 oracle oracle 1398 Jul 4 21:49 test02.bmp -r--r----- 1 oracle oracle 1765 Jul 4 21:52 test01.java -rw-r--r-- 1 oracle oracle 71962 Jul 4 21:50 test01.jpg -rwxrwxrwx 1 oracle oracle 10197 Jul 4 21:50 test02.jpg -rw-r--r-- 1 oracle oracle 9546 Jul 4 21:51 test04.jpg drwxr-xr-x 7 root root 0 Jul 4 21:28 .sfs -rw-r--r-- 1 oracle oracle 477 Jul 4 21:52 test01.xml
注意 -X 参数的使用,它指示 ls 按文件扩展名的字母数字顺序对输出进行排序。
现在,可以创建一个外部表来保存以上输出,就像是关系数据一样。步骤如下:
$ mkdir /home/oracle/ext_tbl_dir
ex_ls.sh shell 脚本: #!/bin/bash /bin/ls /mnt/dbfs/staging_area -a -l -X
ex_ls.sh 脚本: # chmod +x /home/oracle/ext_tbl_dir/ex_ls.sh
$ /home/oracle/ext_tbl_dir/ex_ls.sh
CONNECT /AS SYSDBA;
ex_ls.sh 脚本)所在目录指定一个别名。 CREATE OR REPLACE DIRECTORY my_dir AS '/home/oracle/ext_tbl_dir';
dbfs_user1 模式(在此模式下,使用同一用户与 DBFS 文件系统和外部表交互)对 my_dir 目录中的文件进行操作所必需的权限: GRANT READ,WRITE,EXECUTE ON DIRECTORY my_dir TO dbfs_user1;
dbfs_user1 用户身份连接: CONNECT dbfs_user1/pswd
CREATE TABLE ls_tbl(
file_priv VARCHAR2(11),
file_links NUMBER,
file_owner VARCHAR2(25),
file_owner_gr VARCHAR2(25),
file_sz NUMBER,
file_month VARCHAR2(3),
file_day NUMBER,
file_tm VARCHAR2(6),
file_nm VARCHAR2(25))
ORGANIZATION EXTERNAL (
TYPE ORACLE_LOADER
DEFAULT DIRECTORY my_dir
ACCESS PARAMETERS (
records delimited by newline
preprocessor my_dir:'ex_ls.sh'
skip 1
fields terminated by whitespace ldrtrim
)
LOCATION(my_dir:'ex_ls.sh')
)
/
清单 1
完成上述步骤之后,即可对 ls_tbl 外部表发出查询,确保至此一切正常:
SELECT file_nm "file", file_priv "privileges", file_owner "owner", file_sz "size" FROM ls_tbl WHERE file_sz>0;
所生成输出应如下所示:
file privileges owner size -------------------- ----------- --------------- ---------- test01.bmp -rw-r--r-- oracle 1110 test02.bmp -rwxrwxrwx oracle 1398 test01.java -r--r----- oracle 1765 test01.jpg -rw-r--r-- oracle 71962 test02.jpg -rwxrwxrwx oracle 10197 test04.jpg -rw-r--r-- oracle 9546 test01.xml -rw-r--r-- oracle 477
您会看到,ls 命令的 -X 参数指定的行顺序在以上表查询输出中保持不变。总结一下,使用 -X 可以让 ls 按扩展名的字母数字顺序对输出文件进行排序。这通过 SQL 也能办到,但稍微复杂些。
无论外部表中的数据来自何处,您都可以像查询常规关系数据一样对其进行查询,如果需要,还可以将其与从数据库中获取的其他一些数据联接。回到上节中的示例,您可能希望整合外部表数据与 DBFS_CONTENT 视图中的可用数据。
图 2 是其示意图:

图 2:联接从外部表和 DBFS_CONTENT 视图获取的数据。
以下查询是这种联接的一个示例:
SELECT e.file_nm, e.file_owner, dbms_lob.getlength(d.filedata) FROM ls_tbl e, dbfs_content d WHERE e.file_nm = substr(d.pathname,instr(d.pathname,'/',-1,1)+1,length(d.pathname));
以上查询生成的输出应如下所示:
FILE_NM FILE_OWNER DBMS_LOB.GETLENGTH(D.FILEDATA) -------------------- --------------- ------------------------------ test01.bmp oracle 1110 test02.bmp oracle 1398 test01.java oracle 1765 test01.jpg oracle 71962 test02.jpg oracle 10197 test04.jpg oracle 9546 .sfs root test01.xml oracle 477 8 rows selected.
以上示例展示了如何将 Linux 实用程序生成的输出与常规 SQL 数据联接。实际上,您可以使用相同的方法将操作系统数据与数据库中的可用数据(包括数据字典)联接。Tom Kyte 撰写的 Oracle Magazine 文章“关于外部表查询和数据一致性”中就有一个联接 df 与 DBA_DATA_FILES 的示例。
如上所述,使用外部表的预处理器特性并非在数据库中启动操作系统实用程序的唯一方式。因此,您可以使用 DBMS_SCHEDULER 的 CREATE_PROGRAM 过程将外部可执行文件包装在程序中,然后在数据库中调用该程序。并且,您还可以指定一些自动执行程序的计划。
以下示例展示如何定义一个包装 shell 脚本的调度程序,此脚本将调用 ls 命令并将输出保存到外部文本文件。然后,您就可以定义一个外部表,它从该文本文件导出数据,进而以关系数据的形式提供 ls 输出。图 3 给出了其工作原理的高级视图。

图 3:借助 DBMS_SCHEDULER 启动 Linux 实用程序。
以下步骤将指导您如何创建这种包装 shell 脚本的调度程序,以及如何基于文本文件创建一个用于保存 shell 脚本输出的外部表:
/home/oracle/ext_tbl_dir 目录中的 ls_to_file.sh),内容如下: #!/bin/bash /bin/ls /mnt/dbfs/staging_area -a -l -X > /home/oracle/ext_tbl_dir/flist.txt
sysdba 身份连接: CONNECT /AS SYSDBA;
BEGIN
DBMS_SCHEDULER.CREATE_PROGRAM(
program_name => 'sys.ls_to_file',
program_type => 'executable',
program_action => '/home/oracle/ext_tbl_dir/ls_to_file.sh',
enabled => TRUE);
DBMS_SCHEDULER.CREATE_JOB(
job_name => 'sys.ls_job',
program_name => 'sys.ls_to_file',
start_date => '15-JUL-13 1.00.00AM US/Pacific',
repeat_interval => 'FREQ=DAILY;BYHOUR=1;BYMINUTE=0',
enabled => TRUE);
END;
/
清单 2
BEGIN
DBMS_SCHEDULER.RUN_JOB('sys.ls_job');
END;
/
完成此步骤之后,您应有一个 flist.txt 文件,其中包含 ls_to_file.sh 脚本输出。
flist.txt 文件定义一个外部表: dbfs_user1 用户身份连接: CONNECT dbfs_user1/pswd
CREATE TABLE ls_ext_tbl(
file_priv VARCHAR2(11),
file_links NUMBER,
file_owner VARCHAR2(25),
file_owner_gr VARCHAR2(25),
file_sz NUMBER,
file_month VARCHAR2(3),
file_day NUMBER,
file_tm VARCHAR2(6),
file_nm VARCHAR2(25))
ORGANIZATION EXTERNAL (
TYPE ORACLE_LOADER
DEFAULT DIRECTORY my_dir
ACCESS PARAMETERS (
records delimited by newline
skip 1
fields terminated by whitespace ldrtrim
)
LOCATION('flist.txt')
)
/
清单 3
SELECT count(*) FROM ls_ext_tbl;
COUNT(*)
----------
10
值得一提的是,第 3 步中创建的 ls_job 作业将按指定的计划自动执行。因此,可以手动启动作业,但没有必要。
本文阐明了可能对 Linux 管理员很有用的 Oracle Database 特性,包括如何使用外部表查询操作系统数据,然后将该数据与数据库数据联接。本文还介绍了如何使用 DBMS_SCHEDULER 软件包从 PL/SQL 代码中启动 Linux 实用程序。
Yuli Vasiliev 是一名软件开发人员、自由撰稿人和顾问,目前专攻开源开发、Java 技术、商务智能 (BI)、数据库和面向服务的架构 (SOA),最近还在关注虚拟化。他著有一系列有关 Oracle 技术的图书,最近的一本是《Oracle Business Intelligence:An Introduction to Business Analysis and Reporting》 (Packt, 2010)。
| 修订版 1.0,2013 年 8 月 21 日 |