Oracle 调用外部过程时,extproc 作为独立进程承担实际加载共享库和调用 C 函数的工作。它由监听器根据客户端请求中的 SID 信息按需启动,数据库服务器进程再通过 IPC 或 TCP 与 extproc 通信。正因为这个链路涉及监听器、网络配置和库对象三层设置,只要其中一层参数写错,外部过程就会报错或者直接无法执行。

一、extproc 的工作链路与监听器配置
extproc 并不是数据库实例的常驻进程,它由监听器在收到外部过程调用请求时拉起。数据库进程会先根据 tnsnames.ora 中配置的连接描述符去连接监听器,监听器再根据 SID_NAME 找到对应的 SID_DESC 条目,启动 PROGRAM 指定的进程。如果监听器没有正确声明 PLSExtProc 这个 SID,Oracle 就无法找到 extproc,此时执行外部过程会直接抛出连接错误。
监听器配置的核心文件是 listener.ora。通常需要两个部分:一个监听地址列表,以及一个 SID 列表。监听地址可以同时配置 IPC 和 TCP,其中 IPC 主要用于本机 extproc 通信,TCP 则可服务于远程调用。下面是一个典型的 extproc 监听配置,`ORACLE_HOME` 需要替换成实际数据库安装目录,`KEY` 的值可以自定义但必须与 tnsnames.ora 保持一致。
LISTENER =
(DESCRIPTION_LIST =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC1))
(ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521))
)
)
SID_LIST_LISTENER =
(SID_LIST =
(SID_DESC =
(SID_NAME = PLSExtProc)
(ORACLE_HOME = /u01/app/oracle/product/19.0.0/dbhome_1)
(PROGRAM = extproc)
(ENVS = "EXTPROC_DLLS=ONLY:/u01/app/oracle/ext/hello.so")
)
)
需要注意的是,SID_NAME 一般固定写成 PLSExtProc,除非你修改了外部过程服务命名。`PROGRAM` 的值也必须是 extproc,不能写成完整路径,否则监听器会把它当作其他可执行程序处理。配置完成后要执行 lsnrctl reload 让监听器重新加载配置。
数据库端对应的 tnsnames.ora 需要提供一个可以连接到 extproc 服务的网络服务名。这个服务名的 ADDRESS 部分要使用与 listener.ora 中完全相同的 PROTOCOL 和 KEY,CONNECT_DATA 中则要写 SID = PLSExtProc。常见的错误就是两边 KEY 不一致,导致数据库进程根本找不到目标监听入口。
EXTPROC_CONNECTION_DATA =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC1))
(CONNECT_DATA =
(SID = PLSExtProc)
(PRESENTATION = RO)
)
)
如果外部过程需要跨主机调用,可以把 IPC 地址改成 TCP 地址,同时监听器也要暴露相应的 TCP 端口。不过跨主机调用时共享库必须放在 extproc 进程所在的主机上,而且还要考虑网络权限、防火墙以及库文件路径的可达性,因此生产环境更推荐将外部过程部署在与数据库相同的主机上。
二、创建库对象与外部函数映射
监听器和网络服务名配好之后,下一步是在数据库中创建一个库对象,指向要调用的共享库文件。这一步使用 CREATE OR REPLACE LIBRARY 语句完成。库对象本身只记录一个文件路径或目录对象,并不会立即加载文件,真正的加载发生在第一次调用外部函数时。
CREATE OR REPLACE LIBRARY ext_lib AS '/u01/app/oracle/ext/hello.so'; /
创建库对象需要 CREATE LIBRARY 系统权限。如果共享库存放在数据库服务器本地,可以直接使用绝对路径;如果希望通过目录对象管理,可以先用 CREATE DIRECTORY 创建目录,然后在库定义中写目录对象名。使用目录对象的好处是路径变更时不再需要重建库对象,管理上更灵活。
接着需要定义一个 PL/SQL 函数或过程,并通过 EXTERNAL 子句把它映射到 C 函数。下面的示例创建了一个 PL/SQL 函数 hello_ext,它会调用 C 函数 hello_oracle,传入一个 VARCHAR2 参数,返回一个数字。这里的 LANGUAGE C 表示外部程序使用 C 语言调用规范。
CREATE OR REPLACE FUNCTION hello_ext(p_msg IN VARCHAR2) RETURN NUMBER AS LANGUAGE C LIBRARY ext_lib NAME "hello_oracle"; /
对应的 C 函数需要编译成共享库,并且函数签名要和 PL/SQL 定义保持一致。Oracle 调用 C 函数时默认使用 C 调用约定,参数类型需要按照 Oracle 外部过程规范进行匹配。下面是一个简单的 C 实现,它将传入的字符串打印到标准输出,并返回 0 表示成功。
#include <stdio.h>
int hello_oracle(char *msg) {
printf("Oracle says: %s\n", msg);
return 0;
}
编译命令使用 GCC 生成动态链接库,通常需要加上 -fPIC 和 -shared 选项。编译成功后把生成的 hello.so 放到库对象指向的路径,并确保 oracle 用户有读取权限。之后在 SQL 中执行 SELECT hello_ext('extproc test') FROM DUAL; 即可触发外部过程。
如果 C 函数需要返回字符串或处理复杂类型,就需要使用 OCI 或 Oracle 提供的类型映射宏,并在 PARAMETERS 子句中明确每个参数的方向和类型。简单标量类型可以直接通过默认映射完成,但数组、结构体或 LOB 参数必须额外处理,否则会导致内存越界或者调用失败。
三、EXTPROC_DLLS 安全限制与多库配置
默认情况下,extproc 可以加载数据库主机上的任意共享库,这带来了较大的安全风险。攻击者一旦能够创建库对象,就可能通过外部过程执行任意本地代码。为了缓解这个问题,Oracle 提供了 EXTPROC_DLLS 环境变量,可以在监听器配置中限制 extproc 允许加载的库范围。
限制的方式有两种:使用 ONLY: 前缀时,冒号后面列出的库路径是允许加载的白名单;使用 ANY: 前缀时,则只禁止某些指定的库,其他库仍然允许加载。生产环境建议使用 ONLY: 白名单模式,只开放实际需要调用的共享库文件。下面是一个只允许加载 hello.so 的配置片段。
(ENVS = "EXTPROC_DLLS=ONLY:/u01/app/oracle/ext/hello.so")
如果需要允许多个库,可以用冒号分隔多个绝对路径,也可以使用通配符。例如 EXTPROC_DLLS=ONLY:/u01/app/oracle/ext/*.so 可以匹配该目录下所有以 .so 结尾的文件。要注意这个匹配规则基于文件名,不递归子目录,而且路径大小写在 Linux 系统上是敏感的。
除了 EXTPROC_DLLS,还可以通过操作系统用户权限来加固 extproc。比如将共享库目录设置成只有 oracle 用户可读可执行,这样即使有 SQL 注入或应用漏洞,攻击者也无法轻易替换库文件。此外,监听器配置中还可以指定 ENVS 添加其他环境变量,例如 LD_LIBRARY_PATH,但要注意这同样会扩大可加载库的搜索范围,需要谨慎使用。
四、常见错误 ORA-28595 与 ORA-28576 排查思路
ORA-28595 通常表示 extproc 进程无法完成外部过程调用,可能的原因包括共享库路径错误、库文件不存在、权限不足、库依赖缺失或者 EXTPROC_DLLS 限制没有放行。排查时应先确认 CREATE LIBRARY 中写的路径是否真实存在,并检查 oracle 用户对该文件是否有读取权限。
ORA-28576 则往往与网络连接有关,比如 tnsnames.ora 中的服务名无法解析,或者监听器没有配置 PLSExtProc 条目。可以先用 tnsping EXTPROC_CONNECTION_DATA 测试网络服务名是否可达,再查看监听器状态 lsnrctl status 中是否列出了 PLSExtProc 服务。如果监听器没有加载该 SID,需要检查 SID_LIST_LISTENER 是否写入正确并重新加载配置。
另一个容易被忽略的问题是 IPC 通信的 KEY 不一致。监听器配置中写的是 EXTPROC1,而 tnsnames.ora 中写成了 EXTPROC2,这种情况下 tnsping 可能显示 OK,但实际调用外部过程时仍然会失败。排查时务必逐字符核对两个文件中的 KEY 值。
验证 extproc 是否正常工作时,可以先创建一个指向简单 C 函数的外部过程,例如返回固定数字的函数,然后执行它并观察结果。如果返回值正确,说明整条链路已经打通。若仍然报错,可以查看数据库告警日志以及 extproc 进程的标准输出,很多情况下 C 函数内的 printf 输出会被写入数据库服务器上的某个 trace 文件,这能帮助定位函数是否被真正调用。
Oracle extproc外部过程配置修改时间:2026-10-03 15:53:53