merge触发器不解决并发冲突反而放大死锁风险,因其在高并发下查改原表易引发“自己锁自己”;应禁用嵌套触发、用唯一索引防重、应用层重试、触发器仅做无副作用操作。

MERGE 触发器本身不解决并发冲突,反而容易放大它;真正要防的不是 MERGE,而是触发器里查表、改表、嵌套调用这些动作在高并发下引发的死锁和循环。
为什么 MERGE + 触发器组合特别容易死锁
MERGE 语句执行时,SQL Server 会对匹配行加键锁(Key Lock)或范围锁(Range Lock)。如果触发器里再执行 SELECT ... FROM target_table WHERE ... 或 UPDATE target_table SET ...,就极可能形成“自己锁自己”的闭环:
- 主 MERGE 锁住某几行,准备更新
- 触发器内部又对同一张表做
SELECT FOR UPDATE或普通SELECT(尤其没索引支撑时会升级成表锁) - 另一并发事务也走同样路径,两方互相等待对方释放锁
这种死锁不是偶发,是结构缺陷——触发器不该承担数据校验或状态同步这类需读写原表的逻辑。
INSERTED/DELETED 表里根本看不出是 MERGE 来的
触发器看到的永远只是底层 DML 类型:AFTER UPDATE 就只有 INSERTED 和 DELETED,不会带任何“来自 MERGE”的元信息。你无法靠它们判断这一行是被 WHEN MATCHED THEN UPDATE 更新的,还是被单独 UPDATE 改的。
-
INSERTED和DELETED行数不一定相等(批量优化可能拆分处理) - 如果 MERGE 更新了主键字段,
ON i.id = d.id关联直接断裂,旧值新值对不上 - 别试图在触发器里
JOIN INSERTED d ON ...去还原业务意图——MERGE 的语义在触发器层已丢失
真正该做的三件事:索引兜底、应用重试、触发器只干轻量活
把防重和一致性保障压给数据库底层和应用层,触发器退守为“记录日志”“发通知”“改统计字段”这类无副作用操作:
- 建带业务语义的
UNIQUE INDEX ON t (user_id, DATE(created_at)),这是并发安全的唯一可靠防线 - 应用层捕获
Violation of UNIQUE KEY constraint错误,做指数退避重试(不是让触发器去查重) - 触发器里禁止
SELECT ... FROM target_table、禁止调用存储过程、禁止跨库调用——所有耗时操作都移出触发器
例如审计日志,应该写到独立的 audit_log 表,而不是更新原表的 last_modified_by 字段——后者正是循环触发的温床。
如果非要用触发器响应 MERGE,必须关掉嵌套触发
SQL Server 默认允许 32 层嵌套,但 MERGE + 触发器 + 触发器里再改目标表,3 层就爆了。最简方案是显式禁用:
SET CONTEXT_INFO 0x0; -- 在触发器开头加这句,避免自身再被触发 IF TRIGGER_NESTLEVEL() > 1 RETURN;
但这只是止痛药。更彻底的做法是:把触发器逻辑抽成存储过程,由 MERGE 所在的存储过程末尾显式调用——控制权回到应用层,不再依赖数据库自动触发。
复杂点在于:你以为在解决 MERGE 的并发问题,其实真正卡住你的,是触发器里那条没走索引的 SELECT COUNT(*),或者那个忘了加 WHERE 条件的 UPDATE。










