SQL Server触发器必须同步执行且不能异步,真正的异步需依赖Service Broker:触发器仅负责轻量发消息(BEGIN DIALOG + SEND),由激活存储过程异步处理;严禁在触发器中执行耗时操作。

SQL Server 触发器本身不能异步执行任何逻辑——它必须在主事务内完成,否则会阻塞 INSERT/UPDATE/DELETE。真能落地的“异步”,靠的是 Service Broker 把变更消息甩出去,让独立存储过程后续处理。
触发器里只能做三件事:建对话、发消息、不报错
触发器不是干活的地方,是“发单子”的窗口。它必须轻量、快速、失败可忽略(否则卡住主表写入)。常见错误是试图在触发器里解析 JSON、查远程表、调 HTTP 接口——这些全都不允许。
- 必须用
BEGIN DIALOG CONVERSATION获取句柄,再SEND ON CONVERSATION;跳过对话直接SEND会报错The conversation endpoint is not found - 消息体必须是
VARBINARY(MAX),推荐用CAST((SELECT * FROM inserted FOR JSON PATH, WITHOUT_ARRAY_WRAPPER) AS VARBINARY(MAX))序列化 - 不要在触发器里
WAITFOR (RECEIVE ...)——那是同步反模式,会锁死当前会话 - 若
SEND失败(比如队列不存在或契约不匹配),整个触发器事务会回滚,主 DML 也失败;所以务必提前验证对象链完整
Service Broker 对象链缺一不可,顺序不能乱
Service Broker 不是开关一开就能用,它是一套依赖严格的基础设施。漏建任意一个,消息就卡在 sys.transmission_queue 里不动,且不报明显错误。
- 先启用数据库:
ALTER DATABASE YourDB SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE(注意加WITH ROLLBACK IMMEDIATE,否则可能被连接阻塞) - 按顺序创建:
MESSAGE TYPE→CONTRACT→QUEUE→SERVICE;CONTRACT必须引用已存在的MESSAGE TYPE,SERVICE必须绑定到已存在的QUEUE和CONTRACT - 跨实例场景还得多三步:
CREATE MASTER KEY+CREATE CERTIFICATE(导出/导入公钥)、CREATE ROUTE(ADDRESS 必须写对方真实 IP+端口,不能写localhost)、CREATE REMOTE SERVICE BINDING - 检查是否就绪:
SELECT is_broker_enabled FROM sys.databases WHERE name = 'YourDB';查消息堆积:SELECT * FROM sys.transmission_queue
激活存储过程才是干活的人,得自己管生命周期
没人手动去 RECEIVE,消息才叫“异步”。靠的是队列的 ACTIVATION 属性自动拉起存储过程——但它不会自动帮你收尾,不处理会话终结,消息就会一直占着资源。
- 创建队列时必须指定
ACTIVATION:CREATE QUEUE Q_Sync WITH ACTIVATION (STATUS = ON, PROCEDURE_NAME = [usp_ProcessSync], MAX_QUEUE_READERS = 4, EXECUTE AS OWNER) - 激活过程里必须用
WAITFOR (RECEIVE ...)循环读取,每次RECEIVE后要END CONVERSATION @h或MOVE CONVERSATION @h TO [new_queue],否则会话状态卡在CONVERSING - 必须捕获错误并
END CONVERSATION @h WITH CLEANUP,否则失败会话持续占用会话 ID,最终耗尽系统会话资源 - 别在激活过程中做重试逻辑——SSB 自带重发机制;重点是把消息体反序列化后交由业务逻辑处理,失败则记录日志、丢进死信表
为什么不用轮询临时表或 WAITFOR DELAY 模拟异步
因为它们根本不是异步,只是把阻塞从“毫秒级”拖成“秒级”,还引入竞态和资源泄漏风险。Service Broker 是唯一被 SQL Server 官方支持、事务安全、持久化、可监控的异步原语。
-
WAITFOR DELAY会让触发器线程挂起,主事务长时间持有锁,极易引发死锁和超时 - 轮询临时表或普通表需要额外锁控制,且无法保证消息不丢失、不重复;而 SSB 消息写入即持久化,即使 SQL Server 重启也不会丢
- SSB 提供完整可观测性:
sys.service_queues查积压、sys.conversation_endpoints查会话状态、sys.transmission_queue查发送失败原因 - 性能上,一次
SEND是微秒级操作;而远程调用、HTTP 请求、甚至跨库查询,都是毫秒到秒级,完全不在一个量级
最易被忽略的点是:Service Broker 的会话(conversation)不是“发完就结束”,它是有状态的长连接抽象。哪怕你只发一条消息,也得显式 END CONVERSATION,否则它永远留在系统视图里,堆积多了会导致 sys.conversation_endpoints 膨胀、查询变慢、甚至影响新会话创建。










