触发器不能直接发邮件或调用外部服务,必须通过轻量级写入队列表(如warning_queue),再由后台作业批量处理;需避免递归、死锁与并发问题,并冗余关键字段以确保告警上下文完整。

触发器里不能直接发邮件或调用外部服务
SQL Server 的 FOR INSERT, UPDATE 触发器本身不支持直接执行 sp_send_dbmail(除非启用代理且配置了数据库邮件),MySQL 和 PostgreSQL 更是完全禁止在触发器中做网络 I/O 或长时间操作。强行写会报错,比如 MySQL 报 ERROR 1442: Can't update table 'inventory' in stored function/trigger because it is already used by statement which invoked this stored function/trigger,或者 SQL Server 报 Invalid use of a side-effecting operator 'xp_sendmail'。
真正可行的路径是:触发器只做轻量级记录,把告警任务“甩出去”。
- 在触发器中插入一条待处理记录到
warning_queue表(含product_id、current_stock、created_at) - 用后台作业(如 SQL Server Agent Job / Linux cron +
mysql -e)定时扫描该表,批量发邮件或调用 API - 处理完后删掉或标记为
sent = 1
库存更新触发器必须避开递归和死锁
如果在 UPDATE inventory 的触发器里又去改 inventory(比如自动补货逻辑),会触发自身,导致无限递归或死锁。哪怕只是读取也要注意隔离级别——默认 READ COMMITTED 下可能漏掉并发更新。
正确写法是只读 + 仅写队列表:
CREATE TRIGGER trg_check_stock_low ON inventory AFTER UPDATE AS BEGIN INSERT INTO warning_queue (product_id, current_stock, created_at) SELECT i.product_id, i.stock, GETDATE() FROM inserted i WHERE i.stock
-
inserted是 SQL Server 特有虚拟表,MySQL 要用NEW,PostgreSQL 用NEW但语法不同 - 别在触发器里加
IF EXISTS (SELECT ...)再查原表——这会引发额外锁,且并发下判断失效 - 确保
warning_queue表有索引:CREATE INDEX ix_pending ON warning_queue(created_at) WHERE sent = 0;
警告通知内容必须带上下文,不能只说“库存不足”
运维人员收到告警时,最需要知道三件事:哪个商品、当前多少、阈值是多少。光靠触发器里的 i.stock 不够,因为 min_stock 可能存在另一张表里,而触发器不能跨库 JOIN(尤其 MySQL)。所以得提前冗余关键字段。
- 在
inventory表里加min_stock列(即使它来自products表),保持触发器内可查 - 或者在插入
warning_queue前,用视图或存储过程预关联好product_name、min_stock,再写入 - 邮件正文模板建议固定字段:
【库存告警】{product_name} 当前 {current_stock},低于安全阈值 {min_stock}(最后更新:{updated_at})
高并发场景下队列表容易成为瓶颈
每秒上百次库存更新时,warning_queue 的 INSERT 和后续的 SELECT+UPDATE 可能争抢行锁,尤其当后台作业用 SELECT TOP 1000 ... FOR UPDATE 扫描未处理记录时。
- 避免用
SELECT * FROM warning_queue WHERE sent = 0 ORDER BY created_at LIMIT 100这类无索引排序——加复合索引(sent, created_at) - 考虑分片:按
product_id % 4写入不同队列表(warning_queue_0~warning_queue_3),后台开 4 个进程并行消费 - 更彻底的解法是把队列换为消息中间件(RabbitMQ/Kafka),触发器只发一条 JSON 消息,由独立服务消费——但这已超出纯 SQL 范畴
真正上线前,一定要用真实流量压测队列表的写入吞吐和消费延迟。很多团队卡在这里:告警延迟 10 分钟才发出,等发现时仓库早就缺货了。











