触发器内直接调用存储过程或发HTTP请求会导致写入卡死,因其同步执行且事务必须等待触发器完成;应改用“写入后落队列”模式,由外部消费者异步处理变更。

触发器里直接调用存储过程或发 HTTP 请求为什么卡死写入
因为 SQL 触发器是同步执行的,AFTER INSERT 或 BEFORE UPDATE 一旦触发,事务必须等它完全跑完才能提交。你在里面调 CALL notify_service()、跑复杂计算、甚至用 curl 发请求,整个写入就卡在那——不是慢,是阻塞。
常见错误现象:INSERT 耗时从几毫秒飙升到 500ms+,监控看到 innodb_row_lock_time 上涨,应用端报超时但数据库没报错。
- MySQL 不支持触发器内异步操作,
INSERT DELAYED已废弃,别试 - PostgreSQL 的
AFTER触发器同样同步,且不能开新事务(除非用dblink这种绕过事务边界的方式,但风险高) - SQL Server 的
FOR INSERT同理,加WAITFOR DELAY只会让问题更糟
用「写入后落队列」替代「触发器里干活」
核心思路:触发器只干一件事——把变更记录写进一张轻量级日志表,比如 trigger_queue,然后由外部消费者异步处理。这样主事务毫秒级完成。
使用场景:需要通知下游服务、更新搜索索引、生成统计快照,但不强依赖实时性(秒级延迟可接受)。
- 建表只要几个字段:
id、table_name、row_id、operation('INSERT'/'UPDATE'/'DELETE')、created_at - 触发器里只做
INSERT INTO trigger_queue (...) VALUES (...),禁止任何函数调用、子查询、JOIN - 消费程序用长轮询或监听 binlog(如
maxwell、debezium),避免自己写定时任务扫表漏数据
MySQL 的 AFTER INSERT 触发器怎么安全写日志表
关键是避免锁竞争和死锁。日志表不能和业务表共用引擎或事务隔离级别,否则可能被主事务锁住。
参数差异:trigger_queue 必须用 ENGINE=BLACKHOLE 或 ENGINE=MyISAM(仅 MySQL 5.7 及以前)?不,现在推荐 ENGINE=InnoDB + 单独事务控制。
- 日志表设为
AUTOCOMMIT=1,触发器内显式用START TRANSACTION; INSERT ...; COMMIT;隔离 - 不要在触发器里引用
NEW字段做复杂表达式,尤其别调UUID()或NOW()多次——MySQL 会重复求值 - 如果日志表写入失败(比如磁盘满),触发器抛错会导致主事务回滚,所以得加简单容错:用
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION捕获并写 error_log 表
PostgreSQL 用 LISTEN/NOTIFY 做轻量异步通知
比轮询更实时,且不依赖额外中间件。触发器发通知,外部进程 LISTEN 到后立刻拉取变更数据。
性能影响:单次 NOTIFY 开销约 0.1ms,远低于插入日志表;消息不持久,断连会丢,适合非关键路径。
- 触发器里只写
PERFORM pg_notify('data_change', row_to_json(NEW)::text);,别拼字符串 - 监听端必须用支持
LISTEN的驱动(如psycopg2的connection.notifies),不能用普通 HTTP 客户端直连 - 注意
NOTIFYpayload 限制 8KB,超长要先存表再发 ID










