触发器不能直接调用http或发消息,正确做法是写入通知队列表由外部服务消费。需建notification_queue表含id、event_type、table_name、record_id、payload、status、created_at字段,触发器仅执行insert,消费端用select ... for update skip locked+幂等处理,避免事务阻塞与性能瓶颈。

触发器不能直接调用HTTP或发消息
MySQL触发器运行在数据库事务内,不支持网络I/O、外部命令执行(如curl)、或调用Webhook。哪怕你用sys_eval()或SELECT ... INTO OUTFILE强行绕过,也会面临权限关闭、安全策略拦截、事务回滚时副作用无法撤销等问题。真实生产环境里,这类写法基本不可用,且MySQL 8.0+默认禁用sys_eval等危险函数。
正确做法:触发器只写通知队列表
把“推送”拆成两步:触发器负责可靠落库,外部服务负责消费和投递。关键在于设计一张轻量、带状态的中间表:
-
notification_queue表必须包含:id(自增主键)、event_type(INSERT/UPDATE/DELETE)、table_name、record_id、payload(JSON文本,存变更字段)、status('pending'/'processing'/'done'/'failed')、created_at - 触发器用
AFTER INSERT/UPDATE/DELETE,只做INSERT INTO notification_queue (...) VALUES (...),不查其他表、不拼接字符串、不调用存储过程 - 避免在触发器里做
SELECT ... INTO或关联查询——会锁表、拖慢主业务,尤其高并发时
中间件怎么可靠消费这张表?
轮询不是不行,但要注意三点:
- 用
SELECT ... FOR UPDATE SKIP LOCKED(MySQL 8.0+)或加status = 'pending'+ORDER BY id LIMIT N避免重复消费 - 消费逻辑必须幂等:更新
status到'processing'后再发消息;成功则置为'done';失败则重试或进死信队列 - 不要用
SLEEP()做定时轮询,改用应用层定时任务(如Python的APScheduler)或消息队列的延迟重试机制
如果已接入Kafka/RabbitMQ,可让消费服务把处理完的记录转成消息发到对应topic,下游再对接钉钉/企微/WebSocket——这样触发器只管“记一笔”,后续链路完全解耦。
容易被忽略的坑:事务与性能
触发器代码看似简单,但实际影响远超预期:
- 每个触发器都在原事务中执行,如果写入
notification_queue慢(比如磁盘IO瓶颈),整个INSERT/UPDATE会变慢,用户感知卡顿 - 大量小事务高频触发时,
notification_queue可能成为热点表,建议按天分表或加shard_key字段做水平拆分 - 别在触发器里存大字段(如全文内容),用
record_id关联主表查更安全;payload字段建议用JSON类型(MySQL 5.7+),便于后续解析
真正要推实时消息,优先看binlog解析(Debezium/Flink CDC),而不是堆触发器——后者只适合低频、非核心、允许几秒延迟的场景。











