2009-12-05 91 views
2

插入阅读大量的文章和与上述有关的主题很多答案之后,我仍然不知道SQL Server数据库引擎在下面的例子中是如何工作的: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); 

查询验证该行母鹿后插入表中的T3线路根据列“a”不存在。

许多文章和答案表明,使用上面的查询是没有办法,一个行将被插入两次。

对于上述查询的执行,我假设数据库引擎的工作原理如下:

  1. 子查询首先执行。
  2. 数据库引擎设置范围上的共享锁定。
  3. 读取数据。
  4. 共享锁被释放。根据MSDN,只要数据 被读取,共享的 锁就会被释放。
  5. 如果一行不存在,它会在表中插入一条新行。
  6. 新线路被锁定的独占锁(X)

现在考虑以下情形:

  1. 上面的查询是由处理器A(SPID 1)执行。
  2. 相同的查询由 处理器B(SPID 2)执行。
  3. [SPID 1]数据库引擎设置共享锁
  4. [SPID 1]子查询读取 数据。现在行被返回。
  5. [SPID 1]发布了共享锁 。
  6. [SPID 2]数据库引擎设置(一个或多个)共享一个 锁定
  7. [SPID 2]子查询读取 数据。没有行返回。
  8. [SPID 2]已发布共享锁 。
  9. 这两个过程进行行插入(并且我们得到一个重复的条目)。

我这么想吗?上述方式是避免重复输入的正确方法吗?

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

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

回答

4

要保持多个语句之间的锁定,它们必须包装在一个事务中。在你的例子中:

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设置。)

+0

这是正确的。这两个陈述必须包含在交易中。我纠正了这个问题中的陈述。 所以你说如果我们不强制更新锁,那么INSERT INTO ... WHERE NOT EXISTS(...)语法将不起作用。这就是我所知道的,但是在阅读了很多文章之后,甚至没有提到这些,我开始怀疑它是否真的需要。谢谢澄清。 – yioann 2009-12-06 00:47:49

7

如果您对该列只有一个唯一的约束,则永远不会有重复。

您所概述的技巧将避免您在(第二个“同步”)操作失败的情况下必须捕获错误或异常。

我想补充一点,依靠“外部”代码(甚至是T-SQL)来执行数据库一致性并不是一个好主意。在所有情况下,无论应用程序代码是否写得很好,在表级使用声明性参照完整性对于确保数据库的一致性和匹配预期非常重要。就安全性而言,您需要深入利用防御策略 - 约束条件,唯一索引,触发器,存储过程和视图都可以帮助实现多层方法,以确保数据库为应用程序提供一致且可靠的接口或系统。

+3

是的,唯一的约束/索引将是防止重复的首选,也是唯一可靠的方法。其他任何事情都注定会在某些情况下失败。 – 2009-12-05 22:06:44