SQL Server用MERGE必须写全WHEN MATCHED和WHEN NOT MATCHED分支并加HOLDLOCK,MySQL依赖唯一索引触发INSERT ON DUPLICATE KEY UPDATE,PostgreSQL需显式指定ON CONFLICT目标及EXCLUDED引用新值,三者均须严格满足约束与并发安全要求。

SQL Server 用 MERGE,MySQL 用 INSERT ... ON DUPLICATE KEY UPDATE,PostgreSQL 用 INSERT ... ON CONFLICT——选错语法或漏掉约束,轻则报错,重则重复插入、丢数据、锁表。
SQL Server 的 MERGE 必须写全分支且加 HOLDLOCK
它不是“有匹配就更新、没匹配就插”,而是强制要求 WHEN MATCHED 和 WHEN NOT MATCHED 同时存在。漏一个直接报错:Incorrect syntax near the keyword 'MERGE' 或 The MERGE statement attempted to UPDATE or DELETE the same row more than once。
-
ON条件必须基于非 NULL 的唯一键(主键或唯一索引),比如ON t.id = s.id;若id允许 NULL,比较结果恒为UNKNOWN,所有行都进NOT MATCHED分支 - 源数据别拼参数,走临时表:
CREATE TABLE #staging (id INT, name NVARCHAR(50)),并提前WHERE id IS NOT NULL过滤 - 高并发下必须加
WITH (HOLDLOCK),否则两个会话可能同时判定“不存在”而重复插入:MERGE INTO users WITH (HOLDLOCK) AS t USING #staging AS s ON t.id = s.id - 想避免无意义更新(比如字段值没变也执行
UPDATE),不能在WHEN MATCHED后加AND t.name s.name——SQL Server 不允许,得用CASE或把条件移到UPDATE SET里
MySQL 的 INSERT ... ON DUPLICATE KEY UPDATE 依赖唯一索引触发
它不查表,只靠插入时的唯一约束冲突来跳转分支。没建 UNIQUE KEY 或 PRIMARY KEY,这条语句就退化成普通 INSERT,冲突也不会转更新。
- 检查索引是否存在:
SHOW CREATE TABLE your_table,确认输出里有UNIQUE KEY行;联合唯一索引(如(a,b))必须提供全部列才触发 -
VALUES(col)是关键:它代表本次INSERT尝试写入的值,不是原记录旧值。累加场景要写成score = score + VALUES(score),而不是score = VALUES(score) - 批量时别单条塞万行,拆成每批 1000–5000 行提交,避免超
max_allowed_packet;也别在UPDATE子句里反复调NOW(),它每次都会重新求值 - 禁止用
REPLACE INTO替代——它本质是DELETE + INSERT,会导致自增 ID 跳变、触发器双执行、外键级联误删
PostgreSQL 的 INSERT ... ON CONFLICT 要显式指定冲突目标
它比 MySQL 更可控,但错写 ON CONFLICT 子句会直接报错:there is no unique or exclusion constraint matching the ON CONFLICT specification。
- 主键冲突写法:
ON CONFLICT ON CONSTRAINT users_pkey(users_pkey可用\d users查) - 唯一索引冲突写法:
ON CONFLICT (email)或ON CONFLICT (tenant_id, external_id),括号内必须和索引定义完全一致 -
EXCLUDED是伪表,代表被拒绝插入的那行数据,字段引用写EXCLUDED.name,不能加引号也不能拼字符串 - 想只在字段真变化时更新?加
WHERE:DO UPDATE SET updated_at = NOW() WHERE users.score EXCLUDED.score
真正容易被忽略的是并发安全——不管用哪种语法,只要批量 Upsert 没加锁提示(SQL Server)、没走唯一索引(MySQL)、没对准约束名(PostgreSQL),在多线程写入时就会出现数据覆盖或重复插入,而且问题往往压测才暴露。


















