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

Ste*_*eam -1 sql sql-server sql-server-2005 sql-server-2008

我有一个表"INSERTIF",看起来像这样 -

id  value
S1  s1rocks
S2  s2rocks
S3  s3rocks
Run Code Online (Sandbox Code Playgroud)

在将一行插入此表之前,我想检查给定的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 */
Run Code Online (Sandbox Code Playgroud)

代码创建表 -

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')
Run Code Online (Sandbox Code Playgroud)

Jon*_*ler 6

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

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

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

  1. INSERT,但如果失败则更新.
  2. 更新,但如果没有更新行,则INSERT.

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

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

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

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

  • @blasto嗯,这显着改变了事情.如果您不想进行upsert并且只想忽略已经存在的任何行,则可以使用将"ignore_dup_key"设置为on的唯一约束并插入它们.将跳过具有重复键值的任何行,并且仅插入非dupe. (2认同)
  • @blasto:你的问题仍然询问UPDATE和INSERT,但你的评论表明你已经改变了对你需要处理的事情的看法.当你改变规则时,人们很难帮助你.如果您对ID列没有唯一约束,则不保证该列是标识符.总是在需要强制执行唯一约束的情况下设置一个唯一约束,至少在您测量性能并且可以证明它是严重的性能损失并且您愿意重写所有需要它的独特约束以适应它之前它不是唯一的. (2认同)