sql server中if exists+insert/update组合最稳妥,但需加updlock/holdlock锁避免并发问题;mysql用on duplicate key update、postgresql用on conflict更原子高效。

SQL Server 用 IF EXISTS + INSERT/UPDATE 组合最稳妥
直接查是否存在、再决定插入或更新,是业务逻辑清晰且可控的做法。关键不是“能不能做”,而是“怎么避免并发和性能坑”。
-
IF EXISTS(SELECT 1 FROM dbo.Users WHERE UserId = @id)是标准写法,SELECT 1比SELECT *或COUNT(*)快得多——找到第一行就停,不扫描全表 - WHERE 条件字段(如
UserId)必须有索引,否则EXISTS也慢;没索引时,哪怕只查一行,也可能触发全表扫描 - 别写成
IF (SELECT COUNT(*) FROM ...) > 0——这会强制统计所有匹配行,大表上耗时可能差 10 倍以上 - 判断后紧跟 INSERT 或 UPDATE,中间别加 PRINT、WAITFOR、调外部服务等耗时操作,否则窗口期放大,别人可能在这期间删掉那行
MySQL 用 INSERT ... ON DUPLICATE KEY UPDATE 更原子
如果你的表有唯一键(比如 UNIQUE(UserId) 或主键),MySQL 原生支持一条语句搞定“存在则更新,不存在则插入”,比先查再分支更安全。
INSERT INTO users (UserId, UserName, UpdatedAt) VALUES (@id, @name, GETDATE()) ON DUPLICATE KEY UPDATE UserName = VALUES(UserName), UpdatedAt = VALUES(UpdatedAt);- 它依赖唯一约束触发冲突,不是靠逻辑判断;没唯一键就 fallback 到存储过程里的
IF EXISTS+ 手动分支 - 注意:
VALUES(col)引用的是 INSERT 子句中对应列的值,不是当前行旧值,写错容易覆盖错字段 - 如果需要在冲突时返回状态(比如告诉应用“这次是更新不是插入”),得配合
ROW_COUNT():执行后SELECT ROW_COUNT();返回 -1 表示冲突更新,1 表示新插入
PostgreSQL 用 INSERT ... ON CONFLICT 替代手工判断
和 MySQL 类似,PostgreSQL 的 ON CONFLICT 是真正原子的 upsert,比先 SELECT 再 INSERT/UPDATE 少一次网络往返,也规避了竞态。
INSERT INTO users (id, name, updated_at) VALUES ($1, $2, NOW()) ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, updated_at = EXCLUDED.updated_at;-
EXCLUDED是关键字,代表本次 INSERT 尝试插入的那行数据;别误写成NEW或OLD,它们在触发器里才有效 - 冲突目标必须是唯一索引或主键字段;如果想按非键字段(比如 email)去重,得先建
UNIQUE INDEX ON users(email) - 如果冲突后想跳过(不更新也不报错),用
ON CONFLICT DO NOTHING,但此时ROW_COUNT返回 0,需靠其他方式判断是否生效
跨数据库通用陷阱:别忽略事务与锁粒度
即使语法正确,不配事务或锁提示,高并发下仍可能出错——比如判断存在、别人立刻删掉、你再更新就失败或静默忽略。
- SQL Server 中,若必须分两步(如复杂校验不能塞进单条语句),在
IF EXISTS前加SELECT ... WITH (UPDLOCK, HOLDLOCK),防止别人改或删该行 - MySQL 默认
REPEATABLE READ,INSERT ... ON DUPLICATE KEY UPDATE自带行级锁,一般够用;但若校验逻辑涉及多表关联,仍需显式START TRANSACTION - PostgreSQL 的
ON CONFLICT在事务内自动加锁,但若你在冲突分支里又查其他表,得注意隔离级别——READ COMMITTED下,其他事务的提交可能影响你的后续判断 - 所有场景下,
SET NOCOUNT ON(SQL Server)或SELECT ROW_COUNT()(MySQL/PG)都得紧挨着 DML 语句,中间插任何语句都会让行数统计失效











