在不存在但线程安全的地方插入(我不想重复)

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。但是,如果您不关心已有的重复,为什么还要关心新的?

【讨论】:

这些重复的记录现在用于生产,它们与另一个表(许多表)相关。我想防止它在未来再次发生。

以上是关于在不存在但线程安全的地方插入(我不想重复)的主要内容,如果未能解决你的问题,请参考以下文章

在单个链表线程中擦除和插入是不是安全?

如何解决线程安全问题

Python 以线程安全的方式在本地抑制警告

ArrayList 中的线程安全

ArrayList 中的线程安全

HashMap为啥不安全?