在插入之前检查行是否存在的线程安全方法 - 我的代码是否正确?

时间:2014-02-21 21:47:40

标签: sql sql-server sql-server-2008 sql-server-2005

我有一张表“INSERTIF”,看起来像这样 -

id  value
S1  s1rocks
S2  s2rocks
S3  s3rocks

在向此表中插入行之前,我想检查给定的ID是否存在。如果它不存在,则插入。否则,只需更新值即可。我想以线程安全的方式做到这一点。你能告诉我我的代码是否正确吗?我尝试了它,它的工作原理。但是,我想确保我不会错过任何性能问题。

编辑1 - 我想使用此代码一次插入数百万行。每个insert语句都包含在我显示的代码中。

编辑2 - 我不想使用代码的UPDATE部分,只需插入即可。

我不想使用MERGE,因为它仅适用于SQL Server 2008及更高版本

感谢。

代码 -

-- no check insert 
INSERT INTO INSERTIF(ID,VALUE)
VALUES('S1', 's1doesNOTrock')

--insert with checking 

begin tran /* default read committed isolation level is fine */
if not exists 
(select * from INSERTIF with (updlock, rowlock, holdlock) 
where ID = 'S1')
BEGIN
INSERT INTO INSERTIF(ID,VALUE)
VALUES('S1', 's1doesNOTrock')
END
else
/* update */
UPDATE INSERTIF
SET VALUE = 's1doesNOTrock'
WHERE ID = 'S1'
commit /* locks are released here */

创建表格的代码 -

CREATE TABLE [dbo].[INSERTIF](
    [id] [varchar](50) NULL,
    [value] [varchar](50) NULL
)
INSERT [dbo].[INSERTIF] ([id], [value]) VALUES (N'S1', N's1rocks')
INSERT [dbo].[INSERTIF] ([id], [value]) VALUES (N'S2', N's2rocks')
INSERT [dbo].[INSERTIF] ([id], [value]) VALUES (N'S3', N's3rocks')

2 个答案:

答案 0 :(得分:6)

您的问题是关于代码的线程安全性。简洁,不 - 它不是线程安全的。 (但请参阅下文讨论隔离的地方。)

由于您的“不存在”SELECT与相应操作之间的TOCTOU(检查时间,使用时间)问题,您有一个(小的)漏洞窗口。假设您在id列上有一个唯一的(主要)键约束,您应该使用“更容易请求宽恕而不是权限”范例,而不是“先寻找你”之前的范例(请参阅EAFP vs LBYL

这意味着您应该确定要使用的两个操作序列中的哪一个:

  1. INSERT,但如果失败则更新。
  2. 更新,但如果没有更新行则INSERT。
  3. 要么有效。如果工作主要是插入并偶尔更新,则1优于2;如果工作将主要通过偶尔插入更新,那么2优于1.你甚至可以自适应地工作;跟踪最后N行中发生的事情(其中N可能只有5或多达500),并使用启发式方法来决定尝试新行。如果INSERT失败(因为该行存在)仍然可能存在问题,但UPDATE不会更新任何内容(因为有人在插入失败后删除了行)。同样,UPDATE和INSERT也可能存在问题(没有行,但插入了一行)。

    请注意,INSERT选项完全依赖于唯一约束,以确保不插入重复行; UPDATE选项更可靠。

    您还需要考虑您的隔离级别 - 这可能会改变原始答案。如果您的隔离度足够高,以确保在执行“不存在”SELECT之后,没有其他人能够插入您确定不存在的行,那么您可能没问题。这对您的DBMS有了深刻的理解(我不是SQL Server专家)。

    您还需要考虑交易边界;交易有多大,特别是如果源数据有一百万个条目。

答案 1 :(得分:1)

此技术通常称为UPSERT。可以使用MERGE在SQL Server中完成。 它的工作原理如下:

MERGE INTO A_Table
USING 
    (SELECT 'data_searched' AS Search_Col) AS SRC
    -- Search predicates
    --
    ON A_Table.Data = SRC.Search_Col
WHEN MATCHED THEN
    -- Update part of the 'UPSERT'
    --
    UPDATE SET
        Data = 'data_searched_updated'
WHEN NOT MATCHED THEN
    -- INSERT part of the 'UPSERT'
    --
    INSERT (Data)
    VALUES (SRC.Search_Col);    

另见http://www.sergeyv.com/blog/archive/2010/09/10/sql-server-upsert-equivalent.aspx

编辑:我看到你正在使用旧的SQL Server。在这种情况下,您必须使用多个语句。