SQL Server触发器无法异步,必须通过Service Broker解耦:触发器仅发消息,由激活存储过程异步处理;需完整构建数据库启用、消息类型、契约、队列、服务等基础设施,并在激活过程中显式转换类型、终结会话、处理错误。

触发器本身不能异步,必须靠Service Broker解耦
SQL Server 的触发器强制同步执行,任何耗时操作(包括远程写入、HTTP 调用、复杂计算)都会卡住主表的 INSERT/UPDATE/DELETE。所谓“异步同步”,本质是让触发器只干一件事:发消息。真正的数据搬运由独立激活的存储过程完成。
常见错误是直接在触发器里写 EXEC [LinkedSrv].[DB].[dbo].[usp_sync] 或 INSERT INTO OPENQUERY(...)——这些仍是同步阻塞路径,网络抖动就会拖垮业务。
- 触发器内只允许:
BEGIN DIALOG+SEND ON CONVERSATION+ 快速返回 - 消息体必须是
VARBINARY(MAX),推荐用CAST((SELECT * FROM inserted FOR JSON PATH, WITHOUT_ARRAY_WRAPPER) AS VARBINARY(MAX)) - 跳过对话句柄直接
SEND会报错The conversation endpoint is not found - 若
SEND失败(如队列不存在、契约不匹配),整个主事务回滚,DML 也失败——所以队列、服务、契约必须提前建好并验证
Service Broker 基础设施缺一不可
Service Broker 不是开关一开就通,它是一套强依赖链:数据库启用 → 消息类型 → 契约 → 队列 → 服务 → (跨实例还需证书+路由)。漏掉任意一环,消息就堆在 sys.transmission_queue 里不动,且无明显报错。
- 启用数据库:
ALTER DATABASE YourDB SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE(必须加WITH ROLLBACK IMMEDIATE,否则可能被连接阻塞) - 按顺序创建:
CREATE MESSAGE TYPE→CREATE CONTRACT→CREATE QUEUE→CREATE SERVICE - 检查是否就绪:
SELECT is_broker_enabled FROM sys.databases WHERE name = 'YourDB' - 查堆积:
SELECT * FROM sys.transmission_queue(非空说明链路卡住了)
激活存储过程才是干活的人,得自己管收尾
没人手动 RECEIVE,消息才叫异步。靠的是队列的 ACTIVATION 属性自动拉起存储过程,但它不会帮你终结会话、清理错误状态。
- 创建队列时必须指定激活:
CREATE QUEUE Q_Sync WITH ACTIVATION (STATUS = ON, PROCEDURE_NAME = [usp_ProcessSync], MAX_QUEUE_READERS = 1) - 激活过程里必须循环
WAITFOR (RECEIVE ...),且每次收到后要END CONVERSATION,否则会话长期占用资源 - 必须处理错误会话:
WHERE status = 'ERROR'的消息需单独END CONVERSATION WITH CLEANUP -
usp_ProcessSync内部可安全调用OPENQUERY、远程存储过程或 HTTP 客户端,失败不影响主库 DML
字段映射和类型转换最容易静默失败
同步不是把 inserted 直接扔过去就行。源表和目标表字段名、类型、精度、空值逻辑稍有不一致,就会导致截断、隐式转换失败、比较失准,甚至整条消息被丢弃而不报错。
- 别信隐式转换:源
datetime2(7)→ 目标datetime会丢精度;源nvarchar(50)→ 目标varchar(30)会截断 - 所有值必须显式转换:
CONVERT(nvarchar(50), inserted.name),别省这个步骤 - 跳过
IDENTITY、计算列、默认约束字段;若目标表需要插入IDENTITY,激活过程里得先SET IDENTITY_INSERT ON - 时间字段务必转 UTC:
SWITCHOFFSET(inserted.created_at, '+00:00'),避免时区混乱
真正落地的异步同步,核心不在触发器写了什么,而在你有没有把 Service Broker 的每一条链路都亲手验证过,以及激活过程是否健壮到能扛住下游任意一次超时或格式错误。多数失败不是语法问题,而是对象没建全、会话没终结、类型没对齐。










