SQL Server 的 MERGE 语句必须同时包含 WHEN MATCHED 和 WHEN NOT MATCHED 分支,禁止省略任一分支;需确保 USING 子句返回结果集、ON 条件避免 NULL 比较、批量操作使用临时表加索引、并发时添加 HOLDLOCK 提示,并严格满足目标表的 NOT NULL 约束。

MERGE 语句必须配对 WHEN MATCHED 和 WHEN NOT MATCHED
SQL Server 的 MERGE 不允许只写一半逻辑。哪怕你业务上「只更新不插入」或「只插入不更新」,也得显式写出两个分支,否则直接报错:The MERGE statement attempted to UPDATE or DELETE the same row more than once 或语法拒绝。常见错误是漏掉 WHEN NOT MATCHED,或把 INSERT 写成 INSERT INTO ... SELECT 却没加 VALUES 子句。
-
WHEN NOT MATCHED THEN INSERT (col1, col2) VALUES (s.col1, s.col2)—— 必须用VALUES,不能省略 - 源数据(
USING子句)必须返回结果集,USING (VALUES (@id, @name)) AS s(id, name)合法,但裸写USING (@id, @name)会报错 -
ON条件里避免NULL比较:例如t.id = s.id在任一为NULL时恒为UNKNOWN,导致匹配失败;应提前过滤或改用t.id = s.id OR (t.id IS NULL AND s.id IS NULL)(需确保业务允许空值主键)
批量 Upsert 必须走临时表 + 索引优化
单行循环执行 MERGE(比如 Python 中 for row in df: cursor.execute(...))在万级以上数据量下极慢,本质是网络往返 + 解析开销叠加。真正高性能的做法是:先把数据批量载入临时表,再用 MERGE 一次处理整个结果集。
- 创建本地临时表:
CREATE TABLE #staging (id INT, name NVARCHAR(50), url VARCHAR(200)) - 用
bcp、SqlBulkCopy或pymssql的executemany批量灌入数据(比逐行快 10–100 倍) -
ON字段必须有索引:目标表的匹配列(如id)要是主键或唯一索引;临时表的对应列也建议建索引(尤其数据量 > 10k) - 避免在
ON中写函数或表达式,例如UPPER(t.email) = UPPER(s.email)会让索引失效
并发安全要加 HOLDLOCK,别信默认隔离级别
高并发场景下,多个 MERGE 同时运行可能因幻读导致重复插入或丢失更新。SQL Server 默认的 READ COMMITTED 不足以保护 MERGE 的匹配判断过程。必须显式加锁提示。
- 在目标表别名后加
WITH (HOLDLOCK),等价于SERIALIZABLE,确保整个MERGE过程串行化 - 错误写法:
MERGE INTO users AS t USING ...—— 缺少锁提示,高并发时大概率出问题 - 正确写法:
MERGE INTO users WITH (HOLDLOCK) AS t USING ... - 注意:
HOLDLOCK会延长锁持有时间,若批量数据跨分钟级,要考虑阻塞影响
字段对齐和 NOT NULL 约束最容易被忽略
MERGE 的 INSERT 分支和 UPDATE 分支字段不要求顺序一致,但每个分支都必须满足目标表的约束。最常踩的坑是:目标表某列为 NOT NULL,而 INSERT 分支里没提供值,或传了 NULL,直接报错中断整个语句。
- 检查目标表 DDL:
SELECT COLUMN_NAME, IS_NULLABLE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'your_table' -
INSERT分支必须显式列出所有NOT NULL列,并确保值非空(包括默认值列也要显式写,除非定义了DEFAULT且未禁用) - 如果源数据某些字段可能为空,
INSERT分支里用ISNULL(s.col, 'default')或CASE处理,别指望数据库自动补
真正卡性能的地方往往不是 MERGE 本身,而是 ON 条件能不能走索引、临时表有没有建好、并发时锁没加对——这些点没调好,再标准的语法也扛不住批量压力。


















