sql server 2005+已彻底移除xp_sendmail,必须改用sp_send_dbmail;需先配置database mail、授权角色、启用sql server agent,并在触发器中用try...catch避免事务回滚。

SQL Server 2005 及更高版本中,xp_sendmail 已被彻底移除,无法使用 —— 这是绝大多数人踩坑的起点。
为什么 xp_sendmail 在你的 SQL Server 上根本调用失败
xp_sendmail 是 SQL Server 2000 的遗留扩展存储过程,依赖于已淘汰的 MAPI 邮件客户端(如 Outlook)。从 SQL Server 2005 开始,Microsoft 用基于 SMTP 的 sp_send_dbmail 取代它,并在后续版本中完全禁用 xp_sendmail。你在查询分析器里执行 EXEC xp_sendmail,大概率会看到错误:
Msg 15281, Level 16, State 1, Procedure xp_sendmail, Line 1 Server configuration option 'show advanced options' is off. You must set this option to 1 before you can change the value of 'xp_sendmail'.
即使你设了高级选项,也会提示该过程不存在或已被禁用。
- SQL Server 2000:可用(但需配置 MAPI,极不稳定)
- SQL Server 2005+:
xp_sendmail彻底删除,调用即报错 - 替代方案只有
sp_send_dbmail,且必须先配置 Database Mail
触发器里正确发邮件的最小可行路径:启用 Database Mail + 调用 sp_send_dbmail
Database Mail 是 SQL Server 内置的异步 SMTP 邮件服务,不依赖本地客户端,稳定性高,支持 HTML、附件、优先级等。在触发器中调用它,需注意三点:
- 触发器内不能有长时间阻塞操作 ——
sp_send_dbmail默认异步,但若传入@execute_query_database或大附件,可能拖慢事务 - 触发器上下文无用户登录态,
sp_send_dbmail必须用数据库邮件配置的「账户」发信,不能用当前登录 Windows 用户凭据 - 务必用
TRY...CATCH包裹,避免邮件发送失败导致整个 INSERT/UPDATE 回滚(除非你真需要这样)
示例:在订单表插入异常金额时发警报
CREATE TRIGGER tr_order_amount_alert ON orders
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE @body NVARCHAR(MAX);
SELECT @body = '高风险订单:ID=' + CAST(i.order_id AS VARCHAR) +
', 金额=' + CAST(i.amount AS VARCHAR)
FROM inserted i
WHERE i.amount > 100000;
<pre class="brush:php;toolbar:false;">IF @body IS NOT NULL
BEGIN
BEGIN TRY
EXEC msdb.dbo.sp_send_dbmail
@profile_name = 'DBAlerts', -- 必须提前创建好的邮件配置名
@recipients = 'dba@company.com',
@subject = '【生产告警】超大额订单',
@body = @body;
END TRY
BEGIN CATCH
-- 记录失败日志,但不抛出错误(避免影响主事务)
INSERT INTO dbo.mail_send_log(error_msg, event_time)
VALUES(ERROR_MESSAGE(), GETDATE());
END CATCH
ENDEND;
配置 Database Mail 前必须确认的四件事
很多人卡在“发不出邮件”,其实问题不在触发器,而在基础配置。检查以下项是否全部满足:
-
msdb数据库中已运行向导创建「邮件配置文件(Profile)」和「邮件账户(Account)」—— 不能只建账户,必须关联到 Profile - SQL Server Agent 服务正在运行(Database Mail 后台队列依赖它)
- 防火墙放行 SMTP 端口(通常是 25、587 或 465),且 SMTP 服务器允许该 IP 发信(部分企业邮箱需白名单)
- 执行
sp_send_dbmail的账号需有DatabaseMailUserRole角色权限(通常加到msdb的 public 角色即可)
快速验证是否通:在 SSMS 中直接执行一次 sp_send_dbmail,不走触发器。成功了,再往触发器里搬逻辑。
触发器发邮件的真实代价与替代建议
虽然技术上可行,但把邮件逻辑塞进 DML 触发器里,会带来隐性成本:
- 每条 INSERT/UPDATE 都触发一次邮件队列提交,高频写入场景下可能压垮 Database Mail 队列(
sysmail_mailitems表堆积) - 触发器内无法 await 邮件实际送达结果,失败只能靠日志事后排查
- 违反关注点分离 —— 业务数据变更和通知应解耦
更健壮的做法是:触发器只写一条告警事件到专用日志表(如 dbo.alert_queue),再用 SQL Server Agent 定时作业(比如每 30 秒查一次)批量消费并调用 sp_send_dbmail。这样既可控,又可重试,还不会拖慢核心事务。
真正难的不是怎么调函数,而是想清楚:这个通知,到底该由谁、在什么时候、以什么粒度发出。











