触发器内禁止高耗时运算,因其同步运行且属于事务一部分,复杂计算会阻塞主事务、加剧锁竞争并引发超时或死锁;应改用“落库即走”队列表+外部异步消费模式。

触发器里不能做高耗时运算——这不是建议,是硬性限制。一旦执行复杂计算(比如聚合查询、多表 JOIN、JSON 解析、远程调用),它会卡住主事务,拖慢所有写入,甚至引发锁等待超时或死锁。
为什么触发器内做复杂计算必然拖垮写入性能
触发器运行在 DML 事务的上下文中,INSERT/UPDATE 不返回,触发器不结束,整个事务就一直挂着。MySQL/PostgreSQL 的行级锁、SQL Server 的锁升级机制,都会持续到触发器退出。这意味着:
- 每插入一行就执行一次
SUM()或SELECT FROM huge_table,批量插入 1000 行 = 1000 次全表扫描风险 - 触发器里调用
sp_OACreate或EXEC xp_cmdshell,等于把外部延迟同步塞进数据库事务 - 即使加了索引,WHERE 中用了
UPPER(name)或DATE(created_at),也可能让优化器放弃索引 - 多个触发器嵌套调用(A → B → C)时,堆栈深度和锁持有时间呈指数增长
MySQL/PostgreSQL:用队列表 + 外部消费者替代实时触发
主流开源数据库没内置消息队列能力,但可以用“落库即走”模式解耦。核心是:触发器只干一件事——把关键参数写进一张轻量队列表,其余全交给应用层异步消费。
- 建一张
trigger_queue表,字段最少只需id、table_name、row_id、event_type、created_at,引擎用InnoDB,避免MyISAM表锁 - 触发器中只写
INSERT INTO trigger_queue (table_name, row_id, event_type) VALUES ('orders', NEW.id, 'INSERT'),禁止任何SELECT、UPDATE、函数调用 - 用 Python/Go 写一个常驻进程,定时
SELECT ... FOR UPDATE SKIP LOCKED LIMIT 100拉取未处理任务,处理完再DELETE或标为done - 注意:别用
SELECT * FROM queue WHERE status = 'pending' ORDER BY id LIMIT 1—— 无索引或大 offset 会导致全表扫描
SQL Server:必须用 Service Broker 实现事务安全的异步
SQL Server 唯一被官方支持、能保证事务一致性的异步机制是 Service Broker。试图用 WAITFOR DELAY、临时表轮询或 CLR 线程模拟异步,本质仍是阻塞。
- 必须先执行
ALTER DATABASE [YourDB] SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE,否则SEND直接报错Service Broker is not enabled - 最小必要对象链:先建
MESSAGE TYPE→ 再建CONTRACT→ 接着建QUEUE→ 最后建SERVICE,漏任一环都会导致对话失败 - 发送必须用
BEGIN DIALOG获取句柄,再SEND ON CONVERSATION @h,不能跳过对话直接发;消息体推荐CAST((SELECT @id, @type FOR JSON PATH) AS VARBINARY(MAX)) -
SEND后立即COMMIT,否则回滚时对话状态丢失;绝对不要在同一个事务里RECEIVE,那不是异步,是自缚手脚
哪些操作看起来“轻量”,实则暗藏陷阱
开发者常误判某些操作的开销,结果上线后才发现是瓶颈。这些看似无害的操作,在高频写入场景下极易暴露问题:
-
GETDATE()或NOW()本身快,但若配合INSERT ... SELECT FROM other_table WHERE created_at > GETDATE() - INTERVAL 1 DAY,可能触发大范围扫描 -
JSON_EXTRACT(MySQL)或jsonb_path_query(PostgreSQL)解析字段,CPU 占用高,且无法走索引 - 触发器里查
sys.dm_exec_sessions或pg_stat_activity这类动态视图,可能因元数据锁引发连锁等待 - 哪怕只是
INSERT INTO log_table,如果log_table没建好索引或日志量极大,I/O 也会成为瓶颈
真正难的不是写出能跑的异步逻辑,而是判断哪部分必须立刻做、哪部分可以延后——这个边界一旦划错,后续所有优化都是在给错误设计打补丁。

















