在不存在但线程安全的地方插入(我不想重复)
Posted
技术标签:
【中文标题】在不存在但线程安全的地方插入(我不想重复)【英文标题】:Insert where not exists but thread safe (I don't want duplicates) 【发布时间】:2021-07-02 17:40:24 【问题描述】:如果在此线程中不存在具有匹配 ID 的值,我需要将值插入表中:SQL - Insert Where Not Exists
但我需要确保如果另一个线程在完全相同的时间执行查询,我不会得到两个相同的行。
这是我的桌子:
CREATE TABLE [dbo].[Localizations]
(
[id] [int] IDENTITY(1,1) NOT NULL,
[name] [nvarchar](50) NOT NULL,
[regionId] [int] NOT NULL
) ON [PRIMARY]
这是我当前的查询,如果不存在带有 regionId = x
的本地化行,则插入一个新的本地化行(不幸的是它工作不正确 - 现在我的数据库中有重复项):
-- firstly I execute something like that (my real queries are more complex):
DECLARE @id int = (SELECT [id] FROM [dbo].[Localizations] WHERE regionId = 1);
-- secondly I execute something like that (my real queries are more complex):
IF (@id IS NULL)
BEGIN
INSERT INTO [dbo].[Localizations]
VALUES ('Test', 1);
END
这导致现在我有很多行具有相同的regionId
,我现在无法删除它们,它们用于不同的表:( :( :( 因此,我无法创建唯一约束在regionId
列上,因为我有重复 :( :(
如果多个线程同时执行该查询,您能否告诉我以下查询是否不会创建具有相同 regionId
的重复项?我读过这个线程:
SQL - Insert Where Not Exists
但我不确定,我不想插入更多重复项:(
INSERT INTO [dbo].[Localizations] (name, regionId)
SELECT 'Test', 1
WHERE NOT EXISTS (SELECT *
FROM [dbo].[Localizations]
WHERE regionId = 1)
【问题讨论】:
如何创建唯一索引? @Orkad 我不能这样做,因为我的表中已经有重复项,因为我当前的查询不正确并且不是线程安全的。 :( 所以你可以先尝试删除重复项 :) ***.com/questions/18390574/… @Orkad 我不能,我需要它们 :( 这能回答你的问题吗? Only inserting a row if it's not already there 【参考方案1】:删除重复项并添加唯一约束后,您可以更改批处理以防止会话尝试插入重复项,如下所示:
BEGIN TRANSACTION;
DECLARE @id int = (SELECT [id] FROM [dbo].[Localizations] WITH (UPDLOCK,HOLDLOCK) WHERE regionId = 1);
-- secondly I execute something like that (my real queries are more complex):
IF (@id is null)
BEGIN
INSERT INTO [dbo].[Localizations] VALUES('Test', 1);
END
COMMIT TRANSACTION;
这将强制第一个查询在行或空范围上获取并持有更新锁,这将确保 INSERT 成功,并且运行此代码的任何其他会话将阻塞,直到事务提交。
【讨论】:
非常感谢。 @GSerg 还建议了一个线程,其中的解决方案类似于您的 UPDLOCK、HOLDLOCK 和 MERGE 解决方案。它们之间有什么区别,在我的情况下哪个更好? 合并不是原子的,它本身并不能解决并发问题。你看到它的错误列表了吗?显式锁定会起作用,但它可能比必要的限制更多。为什么不只是尝试/捕获方法?无论如何你都需要错误处理。 是的。 MERGE 还需要“scan”子句上的锁定提示。【参考方案2】:您已经知道答案,您应该删除重复项并添加唯一约束。在此之前,您的数据已损坏。
如果您只想要一个新数据的补丁,您可以在 regionId 上创建唯一过滤索引,您可以在其中过滤 regionId > lastDuplicitValue。但是,如果您不关心已有的重复,为什么还要关心新的?
【讨论】:
这些重复的记录现在用于生产,它们与另一个表(许多表)相关。我想防止它在未来再次发生。以上是关于在不存在但线程安全的地方插入(我不想重复)的主要内容,如果未能解决你的问题,请参考以下文章