What You See Is What You Get Element

如何从 Oracle 数据库启动 Linux 实用程序

作者:Yuli Vasiliev

通过将 Linux 实用程序包装在一个脚本中并使用外部数据库表预处理器指令,可以从 Oracle 数据库内部启动实用程序并将实用程序的输出插入到外部表中。这样您就可以执行查询操作系统数据然后将其与 Oracle 数据库中数据联接等操作。


2013 年 9 月发布


能否将 Linux 实用程序视作数据库表?显然可以 — 通过 Oracle Database 11g 中为外部表提供的预处理器指令特性。其工作方式如下。将操作系统实用程序包装在 bash 脚本中,在创建外部表时将该脚本指定为预处理器程序。然后,要调用该实用程序并以表行形式获取生成的结果,只需在 SQL 语句中查询外部表。其实现方式类似于查询常规关系表。换句话说,这种方法让您能够将操作系统数据组织成表的形式,就像其他数据库数据一样。您可能会问:这有什么好处?主要好处是可以轻松地将操作系统数据与数据库数据联接 — 全部在一条 SQL 查询中实现。

想对本文发表评论吗?请将链接发布在 Facebook 的 OTN Garage 页面上。有类似文章要分享?请将其发布在 Facebook 或 Twitter 上,我们来进行讨论。

值得注意的是,上述方法并不是从数据库内部启动操作系统实用程序的唯一方法。例如,使用 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. 外部表预处理器调用 shell 脚本,此脚本必须放在数据库中创建的一个目录内。
  2. shell 脚本调用操作系统实用程序,并在必要时对生成的输出执行某些操作。
  3. 然后根据创建表时指定的格式,将实用程序输出插入到外部表中。

图 1 显示使用外部表查询操作系统过程的可能状况:

使用外部表查询操作系统的一般步骤

图 1:使用外部表查询操作系统的一般步骤。

此方法可能适用于许多 Linux 实用程序,包括显示磁盘空间和系统信息的实用程序,以及执行监视/调试的实用程序等等。因此,您可以轻松地将此方法用于 dfpslsduenvuname 等命令。但请注意,大多数情况下,在执行数据库相关任务时,可以避免直接使用操作系统实用程序,而是依赖于数据库 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 文件系统客户端”)。

  1. 创建一个用于存放文件系统的表空间。
  2. 在表空间中创建一个用户,并授予其 DBFS_ROLE。
  3. 以刚创建的用户身份连接并运行 dbfs_create_filesystem.sql 脚本创建文件系统。
  4. 确保安装了 kernel-devel 软件包和 FUSE 软件包(此步骤在 Linux 上是必需的)。
  5. 挂载刚才用 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 按文件扩展名的字母数字顺序对输出进行排序。

现在,可以创建一个外部表来保存以上输出,就像是关系数据一样。步骤如下:

  1. 创建一个目录,其中放置包装以上命令的 shell 脚本:

    $ mkdir /home/oracle/ext_tbl_dir 
    
  2. 在该目录中,使用以下内容创建 ex_ls.sh shell 脚本:

    #!/bin/bash
    /bin/ls /mnt/dbfs/staging_area -a -l -X 
    
  3. 允许执行 ex_ls.sh 脚本:

    # chmod +x /home/oracle/ext_tbl_dir/ex_ls.sh
    
  4. 尝试执行脚本,确保其按本阶段预期方式运行:

    $ /home/oracle/ext_tbl_dir/ex_ls.sh
    
  5. 启动 SQLPlus 会话:

    CONNECT  /AS SYSDBA; 
    
  6. 创建外部表,如下所示:

    1. 在数据库中为外部表数据文件(在这个具体示例中,即 ex_ls.sh 脚本)所在目录指定一个别名。

      CREATE OR REPLACE DIRECTORY my_dir AS '/home/oracle/ext_tbl_dir'; 
      
    2. 授予 dbfs_user1 模式(在此模式下,使用同一用户与 DBFS 文件系统和外部表交互)对 my_dir 目录中的文件进行操作所必需的权限:

      GRANT READ,WRITE,EXECUTE ON DIRECTORY my_dir TO dbfs_user1;
      
    3. 现在,以 dbfs_user1 用户身份连接:

      CONNECT dbfs_user1/pswd
      
    4. 创建外部表,如清单 1 所示:

      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 是其示意图:

联接从外部表和 DBFS_CONTENT 视图获取的数据

图 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 的示例。

从 PL/SQL 中启动 Linux 实用程序

如上所述,使用外部表的预处理器特性并非在数据库中启动操作系统实用程序的唯一方式。因此,您可以使用 DBMS_SCHEDULER 的 CREATE_PROGRAM 过程将外部可执行文件包装在程序中,然后在数据库中调用该程序。并且,您还可以指定一些自动执行程序的计划。

以下示例展示如何定义一个包装 shell 脚本的调度程序,此脚本将调用 ls 命令并将输出保存到外部文本文件。然后,您就可以定义一个外部表,它从该文本文件导出数据,进而以关系数据的形式提供 ls 输出。图 3 给出了其工作原理的高级视图。

借助 DBMS_SCHEDULER 启动 Linux 实用程序

图 3:借助 DBMS_SCHEDULER 启动 Linux 实用程序。

以下步骤将指导您如何创建这种包装 shell 脚本的调度程序,以及如何基于文本文件创建一个用于保存 shell 脚本输出的外部表:

  1. 首先,创建一个 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 
    
  2. 在 SQLPlus 会话中,以 sysdba 身份连接:

    CONNECT  /AS SYSDBA; 
    
  3. 执行清单 2 所示的 PL/SQL 代码块,创建一个调度程序以及用来执行该程序的调度程序作业:

    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

  4. 现在,手动运行作业:

    BEGIN
      DBMS_SCHEDULER.RUN_JOB('sys.ls_job');
    END;
    /
    

    完成此步骤之后,您应有一个 flist.txt 文件,其中包含 ls_to_file.sh 脚本输出。

  5. 基于 flist.txt 文件定义一个外部表:

    1. dbfs_user1 用户身份连接:

      CONNECT dbfs_user1/pswd
      
    2. 创建外部表,如清单 3 所示:

      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

  6. 确保一切运行正常,针对新创建的外部表执行查询:

    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 日

关注我们:
博客 | Facebook | Twitter | YouTube