SQL Server锁定 - 避免插入重复的条目

时间:2009-12-05 20:56:07

标签: sql 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);

在根据“a”列验证该行尚不存在后,查询在表t3中插入一行。

许多文章和答案表明使用上述查询无法插入两行。

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

  1. 首先执行子查询。
  2. 数据库引擎在范围上设置共享锁。
  3. 读取数据。
  4. 共享锁被释放。据MSDN分享 一旦数据发布锁即释放 已被阅读。
  5. 如果某行不存在,则会在表格中插入新行。
  6. 使用独占锁定(x)
  7. 锁定新行

    现在考虑以下情况:

    1. 上述查询由处理器A(SPID 1)执行。
    2. 同一个查询由a执行 处理器B(SPID 2)。
    3. [SPID 1]数据库引擎设置共享锁
    4. [SPID 1]子查询读取 数据。现在返回行。
    5. [SPID 1]共享锁是 释放。
    6. [SPID 2]数据库引擎设置了一个 shared(s)lock
    7. [SPID 2]子查询读取 数据。没有行返回。
    8. [SPID 2]共享锁是 释放。
    9. 两个进程都继续插入行(我们得到一个重复的条目)。
    10. 我错过了什么吗?上述方法是否是避免重复输入的正确方法?

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

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

2 个答案:

答案 0 :(得分:8)

如果您对列只有一个唯一约束,那么您将永远不会有重复项。

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

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

答案 1 :(得分: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设置一样。)