sql触发器本身不能发邮件或调用外部服务,只能在数据库内做数据操作;预警必须靠“写入预警表+外部程序轮询”来落地,否则就是纸上谈兵。

直接说结论:SQL触发器本身不能发邮件或调用外部服务,只能在数据库内做数据操作;预警必须靠“写入预警表+外部程序轮询”来落地,否则就是纸上谈兵。
为什么不能在触发器里直接发邮件或调用HTTP?
MySQL的TRIGGER不支持SLEEP()、GET_LOCK()以外的系统函数,更不支持curl、SMTP或存储过程调用外部命令;PostgreSQL虽支持pg_notify()和dblink,但发邮件仍需扩展(如plpythonu),且存在安全与权限限制。硬塞逻辑进触发器,轻则报错ERROR: cannot execute INSERT in a read-only transaction,重则阻塞写入事务。
常见错误现象:
- 在MySQL触发器中写
INSERT INTO alert_log ...; CALL send_email(...);→ 报错ERROR 1422: Explicit or implicit commit is not allowed - 在PostgreSQL中用
PERFORM pg_notify('alert', ...)但没监听端 → 消息直接丢弃,无日志、无反馈
推荐做法:触发器只写预警记录,由独立服务消费
把“检测+记录”和“通知+处理”彻底解耦。触发器职责唯一:当inventory_qty更新后低于min_stock_level,向alert_queue表插入一条待处理记录。
实操建议:
- 预警表结构必须包含:
alert_id(主键)、product_id、current_qty、threshold、status('pending'/'sent')、created_at(带索引) - 触发器中避免子查询:不要写
SELECT min_stock_level FROM products WHERE id = NEW.product_id,而应把阈值冗余到inventory表(如加字段reorder_threshold),否则会锁products表 - 用
AFTER UPDATE而非BEFORE UPDATE:确保拿到最终NEW.inventory_qty,且不干扰原UPDATE事务 - 加去重判断:
IF NEW.inventory_qty NEW.reorder_threshold THEN ...,避免重复插入
示例(MySQL):
CREATE TRIGGER tr_inventory_low_alert
AFTER UPDATE ON inventory
FOR EACH ROW
BEGIN
IF NEW.inventory_qty NEW.reorder_threshold THEN
INSERT INTO alert_queue (product_id, current_qty, threshold, status)
VALUES (NEW.product_id, NEW.inventory_qty, NEW.reorder_threshold, 'pending');
END IF;
END;
外部轮询服务怎么设计才不拖垮数据库?
轮询不是“每秒查一次alert_queue WHERE status='pending'”,那样IO爆炸。关键在控制节奏和减少锁竞争。
实操要点:
- 用
SELECT ... FOR UPDATE SKIP LOCKED(MySQL 8.0+/PostgreSQL)批量取走N条记录,避免多个实例争抢同一行 - 处理完后用单条
UPDATE alert_queue SET status='sent' WHERE alert_id IN (...),别用循环逐条更新 - 加指数退避:首次失败后等1s,再失败等2s、4s……最大不超过5分钟,防止雪崩式重试
- 务必设置
WHERE created_at > NOW() - INTERVAL 1 DAY,自动丢弃超时未处理的老记录,防堆积
性能影响提示:如果alert_queue日增10万条,没加status和created_at联合索引,SELECT ... WHERE status='pending'会全表扫描,QPS掉90%以上。
最容易被忽略的三个细节
一是inventory表没有reorder_threshold字段,而是每次JOIN products表读阈值——这会让触发器变成热点锁源;二是轮询服务没设autocommit=False,导致SELECT ... FOR UPDATE后忘记COMMIT,整张alert_queue被长事务锁死;三是预警表没定期归档,半年后百万级数据让DELETE FROM alert_queue WHERE status='sent'变成慢查询,进而拖慢新预警写入。











