Oracle 12c 及更高版本中,DBMS_MAIL 包已被彻底移除——无法通过任何补丁或权限设置恢复。一旦调用,系统会抛出 ORA-00904: "dbms_mail": invalid identifier 错误,因为该对象已不存在。唯一的解决方案是改用 UTL_MAIL 或 UTL_SMTP,但这两条路径均存在不少难点,每一步都可能踩坑。

结论先行:DBMS_MAIL 已被淘汰,UTL_MAIL 或 UTL_SMTP 成为唯一出路,但启用它们需要满足三个硬性前提,且各环节还隐藏着中文乱码、长度限制、STARTTLS 握手、ACL 权限等陷阱。
UTL_MAIL 启用失败的三个硬性前提
许多人以为一行 UTL_MAIL.SEND 就能搞定,结果却卡在 80% 的阶段——以下三步未完成,后续操作全是徒劳:
- DBA 必须手动执行两个安装脚本:
@?/rdbms/admin/utlmail.sql和@?/rdbms/admin/prvtmail.plb。注意路径中的?会自动解析为$ORACLE_HOME,请勿弄错。 - 参数
smtp_out_server必须设置,且需携带端口号:ALTER SYSTEM SET smtp_out_server = 'smtp.example.com:587' SCOPE=BOTH;。这里有两个易错点:一是不能写https://,二是端口不能遗漏,否则默认值不会生效。 - 数据库服务器本身必须能直接连接邮件服务器——防火墙、SELinux、网络策略均需放行出站 587 或 465 端口。注意,并非应用服务器能连通即可,而是数据库服务器自身必须拥有通路。
如何验证是否生效?执行 SELECT value FROM v$parameter WHERE name = 'smtp_out_server';,返回非空字符串才算通过。
UTL_MAIL.SEND 中文乱码与长度陷阱
主题或正文变成问号?多半是字符集未正确配对,或长度超限触发了一个误导性错误:
- 发送中文邮件时,必须显式指定
mime_type => 'text/plain; charset=utf-8'(HTML 邮件同理)。如果省略,默认采用数据库字符集(如WE8ISO8859P1),中文将直接损坏。 subject和message参数的上限为 32767 字节。一旦超长,不会提示“太长了”,而是抛出ORA-29260: network error: TNS:connection refused——实际上与网络无关,纯粹是长度校验失败。- 多个收件人必须合并为一个字符串,以逗号分隔:
'a@x.com,b@y.com'。不能使用数组、集合,也不能包含换行符。
一个最小可用示例:
BEGIN
UTL_MAIL.SEND(
sender => 'noreply@db.example.com',
recipients => 'user@example.com',
subject => '测试',
message => '这是一封测试邮件',
mime_type => 'text/plain; charset=utf-8'
);
END;
UTL_SMTP 连接卡死在 STARTTLS 的关键动作
当邮件服务器强制要求 STARTTLS(例如 Gmail、Outlook、多数企业 SMTP),如果在 UTL_SMTP.OPEN_CONNECTION 之后直接发送 MAIL 命令,连接会挂起或报错 ORA-29279。正确的顺序是:
- 先发送
EHLO(注意不是HELO),再发送STARTTLS命令。 - 发送完
STARTTLS后,需要重新协商 TLS 层——Oracle 不会自动处理。在 12.2 及以上版本可以手动调用UTL_SMTP.STARTTLS,旧版本则需使用UTL_TCP手动包装 SSL 握手。 - 认证用户名/密码必须进行 Base64 编码:
UTL_ENCODE.base64_encode(UTL_RAW.cast_to_raw(v_user))。漏掉任何一环都会收到 535 错误。 - 邮件头与正文之间必须有一个空行(
UTL_TCP.CRLF || UTL_TCP.CRLF),少一个会被拒收,多一个也可能引发问题。
中文支持还需依赖字符集转换:UTL_RAW.cast_to_raw(CONVERT(v_msg, 'ZHS16GBK', 'AL32UTF8')),仅靠 mime_type 声明是不够的。
ACL 权限不是可选项,而是前置开关
即使 UTL_MAIL 或 UTL_SMTP 配置完全正确,缺少 ACL 仍然无效——会报错 ORA-24247: network access denied by access control list。以下是几个容易被忽略的关键点:
- ACL 文件名、主机名、端口范围必须严格匹配。例如:
DBMS_NETWORK_ACL_ADMIN.ASSIGN_ACL(acl => 'email.xml', host => '*', lower_port => 25, upper_port => 587)。 - 用户必须被明确授予
connect权限:DBMS_NETWORK_ACL_ADMIN.ADD_PRIVILEGE('email.xml', 'MY_USER', TRUE, 'connect')。 - ACL 生效后,还需显式授权包执行权:
GRANT EXECUTE ON UTL_MAIL TO MY_USER(UTL_SMTP同理)。
最容易被忽略的一点:ACL 主机匹配支持通配符 *,但不支持正则。如果只配置了 host => 'smtp.example.com',那么连 smtp.example.com:587 都会被拒绝——端口必须单独通过 lower_port 和 upper_port 放开,不能写在主机名中。
