触发器中不可直接发邮件,应写入通知队列表并由SQL Server Agent轮询发送;需显式指定mail profile、防NULL比较、集合处理批量更新、建索引并定期清理队列表。

触发器里不能直接发邮件,得靠 SQL Server Agent 或外部服务
SQL Server 的 AFTER UPDATE 触发器本身不支持调用邮件发送函数(比如 sp_send_dbmail)并等待结果,尤其在事务中——它会阻塞事务、拖慢响应,还可能因邮件配置失败导致更新回滚。真实场景里,没人把发邮件逻辑塞进触发器主体。
常见错误现象:sp_send_dbmail 在触发器里执行后没报错但邮件没发出;或更新语句卡住几秒后超时;更糟的是,事务一回滚,邮件却已发出(状态变更未持久化,通知却发了)。
- 正确做法是:触发器只做轻量动作——写入一个待通知队列表(如
NotificationQueue),字段至少含OrderId、OldStatus、NewStatus、CreatedTime - 用 SQL Server Agent 创建轮询作业,每 10–30 秒查一次该队列,取出未处理记录,调用
sp_send_dbmail发送,并更新Status = 'Sent' - 避免在触发器里用
WAITFOR DELAY或尝试异步线程——SQL Server 不允许
sp_send_dbmail 参数必须显式指定 profile_name,否则默认用 public profile(常被禁用)
很多人测试时本地能发,上线就失败,错误信息通常是:The mail could not be sent to the recipients because of the mail server configuration. (The account used for this profile is disabled.)。根本原因不是 SMTP 配错了,而是没指定可用的 mail profile。
- 先确认 profile 是否启用:
SELECT * FROM msdb.dbo.sysmail_profile WHERE name = 'YourProfileName' - 发邮件时必须传
@profile_name = 'YourProfileName',不能依赖默认值 -
@recipients必须是字符串(如'admin@company.com'),不能是变量拼接后带空格或换行——容易静默失败 - 邮件正文建议用
FOR XML PATH('')或STRING_AGG(SQL Server 2017+)拼接多行数据,避免 NULL 导致整封邮件为空
状态变更检测要防重复和漏判,别只比 new old
单纯用 INSERTED.Status != DELETED.Status 判断变更,在多值枚举或 NULL 状态下会出问题。比如旧值是 NULL,新值是 'Shipped',NULL != 'Shipped' 返回 UNKNOWN 而非 TRUE,导致跳过通知。
- 改用
ISNULL(INSERTED.Status, '') != ISNULL(DELETED.Status, '')或更稳妥的CHECKSUM(INSERTED.Status) != CHECKSUM(DELETED.Status) - 如果业务要求“仅当从 Pending → Shipped 才发邮件”,就得写明确分支:
IF DELETED.Status = 'Pending' AND INSERTED.Status = 'Shipped' - 注意批量更新:一个 UPDATE 可能影响上百行,触发器里的
INSERTED和DELETED是表,必须用集合操作插入队列表,别写循环
队列表加索引和清理策略,否则半年后查询变龟速
没人一开始想清理,但 NotificationQueue 表三个月后可能百万行,Agent 作业每次扫全表,CPU 拉满,发信延迟飙升。这不是功能问题,是运维盲点。
- 必建索引:
CREATE INDEX IX_Queue_Status_Time ON NotificationQueue (Status, CreatedTime) WHERE Status IN ('Pending', 'Failed') - Agent 作业里发信成功后,用
UPDATE TOP (1000)批量标记为 Sent,避免长事务锁表 - 另起一个低频作业(每天凌晨),删掉
Status = 'Sent'且CreatedTime 的记录 - 别用
DELETE FROM NotificationQueue清空——日志暴涨,可能触发自动增长卡死
真正难的不是写出发邮件的那几行代码,而是让这个链路在高并发、状态乱跳、DBA 重启服务、邮件服务器抖动时,依然不丢不重不卡。队列表结构、索引、清理节奏,比触发器里那一行 INSERT 更值得花时间推演。

















