报错“attempted to update or delete the same row more than once”根本原因是源表存在重复匹配键(如多行on到同一目标行),需前置去重(如row_number())、确保on字段在目标表有唯一索引,并避免多层子查询直接入using。

SQL Server MERGE 报错 “attempted to UPDATE or DELETE the same row more than once” 怎么办
这是并发写入时最典型的 MERGE 失败现象,根本原因不是并发本身,而是源数据里存在重复匹配键(比如多个 source 行 ON 到同一个 target 行),触发 SQL Server 的严格校验。它不认为这是“应该合并的多条更新”,而是直接报错中断执行。
常见错误场景:
- 源表用
SELECT FROM ... JOIN拼出数据,JOIN 条件没控制好,导致一条 target 主键被多条 source 匹配 - 临时表
#staging里没去重,比如上游 ETL 把同一笔订单发了两次 - ON 条件字段在目标表上没建唯一索引,但 MERGE 还是按“逻辑唯一”去跑,结果底层扫描时发现多行可匹配
解决办法必须前置:
- 源数据进
#staging后立刻用ROW_NUMBER() OVER (PARTITION BY match_col ORDER BY priority DESC) = 1去重,别等 MERGE 里处理 - 确认
ON字段组合在 target 表上有UNIQUE INDEX,不是“业务上唯一”,而是数据库强制唯一 - 避免在 USING 子句里直接写多层子查询——执行计划不可控,且无法加索引;先 INSERT INTO #staging,再 CREATE INDEX ON #staging(match_col)
MySQL 用 INSERT ON DUPLICATE KEY UPDATE 却还是插入了重复行
这不是并发问题,是约束没生效。只要没建 UNIQUE 或 PRIMARY KEY,ON DUPLICATE KEY UPDATE 就完全不触发,退化成普通 INSERT,自然重复。
关键检查点:
- 执行
SHOW CREATE TABLE your_table,确认输出里真有UNIQUE KEY行,且字段顺序和你的ON条件一致 - 如果业务主键含 NULL(比如
tenant_id允许为空),MySQL 默认允许多个 NULL 共存于唯一索引,导致“空值不冲突”——这时得改用COALESCE(tenant_id, -1)计算列建唯一索引 -
INSERT ... VALUES ROW(), ROW()批量写入时,若某一行违反了非唯一约束(如NOT NULL),整个语句会失败,ON DUPLICATE KEY UPDATE一概不执行
PostgreSQL INSERT ON CONFLICT 在高并发下仍出现重复
根本原因是没指定 CONFLICT ON 的具体索引名,或者用了表达式索引但没在 ON CONFLICT 里显式声明。PostgreSQL 不会自动推导“哪个唯一约束被违反”,必须明确。
正确写法示例:
INSERT INTO orders (order_no, status, updated_at)
VALUES ('ORD-001', 'shipped', NOW())
ON CONFLICT ON CONSTRAINT orders_order_no_key -- 必须写约束名,不能只写 (order_no)
DO UPDATE SET status = EXCLUDED.status, updated_at = EXCLUDED.updated_at;
注意:
- 查约束名用
\d+ orders或SELECT conname FROM pg_constraint WHERE conrelid = 'orders'::regclass - 如果用的是部分索引(比如
WHERE deleted_at IS NULL),ON CONFLICT必须指向该索引名,且 INSERT 的值必须满足索引条件,否则不触发冲突处理 -
DO NOTHING和DO UPDATE都是原子操作,但如果你在 UPDATE 里调用函数(比如nextval()),可能被多次执行——因为 PostgreSQL 可能重试整个语句
MERGE 或 ON CONFLICT 之后,旧数据没清理干净
所有这些语法只保“单语句原子性”,不负责业务层面的数据生命周期管理。比如你用 MERGE 同步订单状态,但源系统已归档旧订单,目标表却还留着它们——这不是语法缺陷,是你漏写了 WHEN NOT MATCHED BY SOURCE THEN DELETE(SQL Server)或没设计对应的清理逻辑(MySQL/PG)。
真实取舍点:
- SQL Server
MERGE支持WHEN NOT MATCHED BY SOURCE,但删错行后果严重,务必加AND target.updated_at 类条件兜底 - MySQL 没等价语法,得另起事务:先
DELETE FROM target WHERE id NOT IN (SELECT id FROM #staging),再做INSERT ... ON DUPLICATE KEY UPDATE - PostgreSQL 的
ON CONFLICT也不处理“消失的数据”,得靠定期 job 或触发器补位
最易被忽略的是:这些机制只管单表、单语句。跨表一致性(比如“插入订单同时扣库存”)必须靠外层事务 + 可串行化隔离级别,否则再稳的 MERGE 也救不了。










