在阅读了许多与上述主题相关的文章和答案之后,我仍然想知道在以下示例中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设置。)