如何不使用主键与唯一约束限制表中重复数据插入?含同时插入场景
解决无主键/唯一约束下阻止重复数据插入的问题
你的问题很典型——当你想一次性插入多条数据,或者面临并发插入场景时,单纯的事前IF判断根本挡不住重复数据,因为判断和插入不是原子操作,而且你自己一次性插两条相同id的记录时,事前判断完全不会触发。下面给你几个靠谱的解决方案:
1. 插入时直接过滤重复数据(解决单会话批量插入重复的情况)
如果是你自己一次性提交多条可能重复的记录,直接在INSERT语句里做过滤,既要排除目标表已存在的数据,也要先对自己的待插入数据去重:
方案A:用WHERE NOT EXISTS做双重过滤
先把要插入的临时数据放到表变量里,再通过对比目标表+去重处理,只插入符合要求的记录:
DECLARE @id INT = 5 -- 先把待插入数据存入临时表变量 DECLARE @tempData TABLE (id INT, dat DATETIME) INSERT INTO @tempData VALUES (@id, GETDATE()), (@id, GETDATE()) -- 插入时先去重临时数据,再过滤目标表已存在的内容 INSERT INTO ##time (id, dat) SELECT DISTINCT td.id, td.dat FROM @tempData td WHERE NOT EXISTS (SELECT 1 FROM ##time t WHERE t.id = td.id)
这里的DISTINCT先把你要插入的两条重复数据合并成一条,再判断目标表是否已有该id,没有才执行插入。
方案B:用MERGE原子操作(兼容插入/更新,避免竞态)
MERGE是原子性的SQL语句,能同时处理匹配/不匹配的情况,天然避免并发下的重复插入问题:
DECLARE @id INT = 5 DECLARE @tempData TABLE (id INT, dat DATETIME) INSERT INTO @tempData VALUES (@id, GETDATE()), (@id, GETDATE()) MERGE INTO ##time t USING (SELECT DISTINCT id, dat FROM @tempData) td ON t.id = td.id WHEN NOT MATCHED THEN INSERT (id, dat) VALUES (td.id, td.dat);
USING部分先完成待插入数据的去重,然后只向目标表插入不存在的记录,整个操作是原子性的,比单独的IF+INSERT可靠得多。
2. 处理并发插入场景(多会话同时插同一个id)
如果是多个会话同时插入相同id的记录,哪怕用了上面的过滤,还是可能因为竞态条件出现重复。这时候需要通过锁机制保证判断和插入的原子性:
DECLARE @id INT = 5 DECLARE @tempData TABLE (id INT, dat DATETIME) INSERT INTO @tempData VALUES (@id, GETDATE()), (@id, GETDATE()) BEGIN TRANSACTION -- 用UPDLOCK+HOLDLOCK锁定查询范围,避免其他会话同时插入相同id IF NOT EXISTS (SELECT 1 FROM ##time WITH (UPDLOCK, HOLDLOCK) WHERE id = @id) BEGIN INSERT INTO ##time (id, dat) SELECT DISTINCT id, dat FROM @tempData END COMMIT TRANSACTION
UPDLOCK会给查询到的行加更新锁,HOLDLOCK会保持锁直到事务结束,这样其他会话在当前事务完成前,无法插入相同id的记录,彻底避免了并发重复。
为什么你的原代码失效?
你的原逻辑存在两个核心问题:
- 只检查了目标表是否存在该id,完全没处理自己要插入的两条重复数据
- 判断和插入是两个独立操作,没有原子性保证——如果多会话同时执行,都会先查到目标表无该id,然后同时插入,最终产生重复
所以核心是要把判断+插入变成原子操作,或者在插入环节直接完成重复数据的过滤。
内容的提问来源于stack exchange,提问作者user11856620
相关产品推荐
相关产品推荐

