必须先建唯一约束,否则所有防重语法都失效;MySQL用INSERT ON DUPLICATE KEY UPDATE最可靠但仅响应主键或唯一键冲突;SQL Server慎用MERGE因其严格依赖ON条件与索引一致性;触发器仅作兜底且需主动SIGNAL中断并确保被查字段有索引。

必须先建唯一约束,否则所有防重语法都失效
存储过程里写再多 MERGE、ON DUPLICATE KEY UPDATE 或 ON CONFLICT,只要目标表没建 UNIQUE INDEX 或 PRIMARY KEY,就等于没设防。MySQL 会退化成普通 INSERT,SQL Server 报错 The MERGE statement attempted to UPDATE or DELETE the same row more than once,PostgreSQL 直接抛出 there is no unique or exclusion constraint。
常见错误是只靠业务逻辑“认为”字段唯一,比如以为 order_no 不会重复,却没执行:
ALTER TABLE orders ADD UNIQUE (order_no);- 多字段组合唯一时漏掉括号:正确是
ADD UNIQUE (tenant_id, order_no),不是ADD UNIQUE tenant_id, order_no - 用
ANr当唯一标识,但没加索引,导致后续所有防重语句不触发
MySQL 存储过程中优先用 INSERT ON DUPLICATE KEY UPDATE
MySQL 没有 MERGE,INSERT ... ON DUPLICATE KEY UPDATE 是最可靠的选择,但它只响应主键或唯一键冲突,其他约束(如 NOT NULL、外键失败)仍会直接报错,不会进 UPDATE 分支。
批量插入别写循环,用 VALUES ROW(), ROW(), ...;但要注意 max_allowed_packet 限制,超限会报错 Packets larger than max_allowed_packet are not allowed。
如果业务要求“存在则不变”,别写 col = VALUES(col)——这会覆盖原值;真要更新时间戳,只更新无副作用字段:
INSERT INTO orders (order_no, user_id, amount)
VALUES ('ORD-123', 1001, 99.9)
ON DUPLICATE KEY UPDATE updated_at = NOW();
SQL Server 存储过程慎用 MERGE
MERGE 看似一步到位,实际极易踩坑:它不是“智能去重”,而是严格匹配 ON 条件与唯一索引字段。一旦 ON 子句和索引字段不一致,比如索引是 UNIQUE (tenant_id, order_no),但写成 ON t.order_no = s.order_no,就会误删或漏更新。
容易被忽略的陷阱:
- 漏写
WHEN NOT MATCHED BY SOURCE THEN DELETE?旧数据越积越多 - 源数据嵌套太深,比如
SELECT ... FROM (SELECT ...) s,执行计划不可控,建议先写入#staging临时表再MERGE -
UPDATE SET col = ISNULL(s.col, t.col)是危险写法:当s.col为NULL,会把t.col覆盖成NULL,改用CASE WHEN s.col IS NOT NULL THEN s.col ELSE t.col END
触发器只是兜底,不是主力
95% 的重复提交问题,靠 UNIQUE (user_id, request_id) 就能拦住。触发器只应在涉及多表状态、时间窗口判断或需调用外部服务时才启用。
在 BEFORE INSERT 触发器里查重,不能只 SELECT COUNT(*) 然后啥也不做——插入照样发生。必须用 SIGNAL SQLSTATE '45000' 主动中断:
SELECT 1 INTO @exists FROM orders WHERE order_no = NEW.order_no; IF @exists IS NOT NULL THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'duplicate submit'; END IF;
最关键的是:被查字段必须有索引,否则每次插入都全表扫描;绝对不要在触发器里写 SELECT ... FOR UPDATE,隐式锁会把高并发堵死。

















