merge 的 output 是语句级原子反馈,而 select+update 是两次独立操作,存在并发导致幻读或丢失更新的风险;merge 在单语句内完成匹配与执行,$action 和 output 实时捕获变更快照,确保结果可信。

MERGE 的 OUTPUT 是语句级原子反馈,SELECT + UPDATE 是两次独立操作
先 SELECT 再 UPDATE 本质是两轮独立 SQL 执行:第一轮读取符合条件的行,第二轮基于读取结果去更新。中间存在时间窗口,其他事务可能修改同一行(比如并发 UPDATE、DELETE 或状态变更),导致你 UPDATE 的行已不是 SELECT 时的状态——这就是典型的“幻读”或“丢失更新”。而 MERGE 在一个语句内完成匹配、判断、执行和返回,整个过程被数据库引擎视为单次原子操作,OUTPUT 子句拿到的 inserted.* 和 deleted.* 只反映本次语句真正触达的行,不依赖外部查询逻辑。
为什么 $action 和 OUTPUT 能保证结果可信
MERGE 的 OUTPUT $action, inserted.*, deleted.* 不是“事后统计”,而是执行过程中实时捕获的变更快照。它和 DML 操作绑定在同一事务上下文中,哪怕在 WHEN MATCHED THEN UPDATE 中加了 WHERE 条件(比如只更新 status = 'pending' 的行),$action 仍只会标记实际被修改的行,且 deleted.* 包含更新前的旧值,inserted.* 是新值——这些数据与最终落盘内容严格一致。
-
$action返回字符串'INSERT'、'UPDATE'或'DELETE',注意是$action(带美元符),不是ACTION - 如果源数据里有重复键(如多个相同
id),MERGE直接报错The MERGE statement attempted to update or delete the same row more than once,必须提前用ROW_NUMBER() OVER (PARTITION BY id ORDER BY ...)去重 -
OUTPUT不能引用目标表别名(如t.id),只能用inserted.id或deleted.id
MERGE 的 ON 条件写错会导致误删,比 UPDATE 更危险
WHEN NOT MATCHED BY SOURCE THEN DELETE 这个子句非常强大,但也极易出错:只要目标表某行在源数据中没匹配上,就会被删。常见陷阱包括:
- 源查询漏加
WHERE过滤(比如忘了WHERE status = 'active'),导致把本该保留的行当成“多余”删掉 -
ON条件列未建索引,或类型隐式转换(如int对比varchar),导致匹配失败,触发误删 - 源是子查询或 CTE,但未确保 key 列唯一,造成一对多匹配,触发重复操作报错
SQL Server 中 MERGE 性能依赖索引,不是无脑快
MERGE 看似“一次扫描”,但若没建对索引,性能反而比分开的 UPDATE + INSERT 更差。关键索引要求很具体:
- 目标表的
ON列(如id)必须有唯一聚集索引(PRIMARY KEY或显式UNIQUE CLUSTERED) - 源表的
ON列需有唯一非聚集索引,最好是涵盖索引(包含所有SELECT中用到的字段) - 避免在
ON条件中做函数转换(如ON UPPER(t.name) = UPPER(s.name)),会跳过索引
真正容易被忽略的是:MERGE 的原子性只保障语句内操作的一致性,不解决业务逻辑冲突。比如两个并发 MERGE 都试图把同一行从 pending 改成 processing,数据库不会报错,但业务上可能需要额外状态校验——这得靠 WHEN MATCHED AND target.status = 'pending' THEN UPDATE ... 这类条件来兜底,而不是指望原子性自动解决。










