在阅读了许多与上述主题相关的文章和答案之后,我仍然想知道在以下示例中SQL Server数据库引擎如何工作:

假设我们有一个名为t3的表:

create table t3 (a int , b int);
create index test on t3 (a);


和查询如下:

INSERT INTO T3
SELECT -86,-86
WHERE NOT EXISTS (SELECT 1 FROM t3 where t3.a=-86);


在基于“ a”列验证该行不存在之后,查询将在表t3中插入一行。

许多文章和答案表明,使用以上查询无法将一行插入两次。

为了执行上述查询,我​​假设数据库引擎的工作方式如下:


子查询首先执行。
数据库引擎在范围上设置共享锁。
读取数据。
共享锁被释放。据MSDN共享
数据一被释放
已阅读。
如果行不存在,则会在表中插入新行。
新行被排他锁(x)锁定


现在考虑以下情形:


上述查询由处理器A(SPID 1)执行。
相同的查询由
处理器B(SPID 2)。
[SPID 1]数据库引擎设置了共享锁
[SPID 1]子查询读取
数据。现在返回行。
[SPID 1]共享锁为
已发布。
[SPID 2]数据库引擎设置一个
共享锁
[SPID 2]子查询读取
数据。没有行返回。
[SPID 2]共享锁为
已发布。
这两个过程都进行了行插入(并且我们得到了重复的条目)。


我想念什么吗?以上方法是避免重复输入的正确方法吗?

一种避免重复输入的安全方法是使用下面的代码,但是我只是想知道上面的方法是否正确。

begin tran
    if (SELECT 1 FROM t3 with (updlock) where t3.a=-86)
    begin
        INSERT INTO T3
        SELECT -86,-86
    end
commit

最佳答案

为了在多个语句之间保持锁,必须将它们包装在事务中。在您的示例中:

If (SELECT 1 FROM t3 with (updlock) where t3.a=-86)
    INSERT INTO T3 SELECT -86,-86


可以在执行插入操作之前释放更新锁。这将可靠地工作:

begin transaction
If (SELECT 1 FROM t3 with (updlock) where t3.a=-86)
    INSERT INTO T3 SELECT -86,-86
commit transaction


单个语句始终包装在事务中,因此也可以使用:

 INSERT INTO T3 SELECT -86,-86
 WHERE NOT EXISTS (SELECT 1 FROM t3 with (updlock) where t3.a=-86)


(这是假设您已关闭“隐式事务”,例如默认的SQL Server设置。)

10-08 00:56