【问题标题】:Locking data in SQL Server using transaction isolation levels使用事务隔离级别锁定 SQL Server 中的数据
【发布时间】:2017-05-13 01:48:11
【问题描述】:

我开始开发应处理数据访问并发问题的应用程序,但我无法理解如何正确使用事务隔离级别。

我有一个名为Folders 的表格,其中包含一个树状文件夹结构:

+-----------------------------------------------------------------+
| Id (int) | Name (varchar) | FullPath (varchar) | ParentId (int) |
|----------+----------------+--------------------+----------------|
| 1        | 'root1'        | '/root1/'          | NULL           |
| 2        | 'c1'           | '/root1/c1/'       | 1              |
| 3        | 'c2'           | '/root1/c1/c2/'    | 2              |
| 4        | 'root2'        | '/root2/'          | NULL           |
+----------+----------------+--------------------+----------------+

我正在尝试像这样实现“移动文件夹”工作流程(例如,我想将 ID=2 的文件夹移动到 ID=4 的新父级):

  1. 开始交易
  2. 读取 ID=2 的文件夹(称为文件夹 2):SELECT * FROM Folders WHERE Id=2
  3. 读取 ID=4 的文件夹(称为文件夹 4):SELECT * FROM Folders WHERE Id=4
  4. 更新ParentIdFullPathfolder2: UPDATE Folders SET ParentId=folder4.Id, FullPath=folder4.FullPath+folder2.Name+'/' WHERE Id = folder2.Id
  5. 读取folder2的所有子文件夹(称它们为subfoldersOfFolder2):SELECT * FROM Folders WHERE FullPath LIKE folder2.FullPath + '%'
  6. 对于subfoldersOfFolder2 中的每个subfolder 更新FullPath 列(查询省略)
  7. 提交事务

显然,在我的事务完成之前,我不希望任何其他事务写入(甚至读取)folder2subfoldersOfFolder2

在阅读this article on SQL Server transactions 之后,我了解到在第 1 步将隔离级别设置为 Serializable 将帮助我实现这一目标。但由于某种原因,这似乎没有发生。我尝试让事务保持打开状态(在步骤 #7 之前停止),打开另一个 SSMS 实例并执行SELECT * FROM Folders,查询成功完成,我仍然可以看到第一个事务读取的数据。

为什么会这样?如何防止其他人读/写folder2subfoldersOfFolder2?我觉得我错过了关于事务如何实际锁定数据的重要内容。

【问题讨论】:

  • Serializable 专门表示全局没有并发。这真的是你想要的吗?如果不是,您可能应该让修改代码首先选择带有updlock, holdlock 的所有相关父节点。但是,这只会设置修改代码,它不会阻止人们阅读您要修改的子文件夹的路径,但没有。鉴于您只有在锁定并读取父文件夹(竞争条件)后才知道这些子文件夹的 ID,因此每次编辑路径时,您似乎都希望在整个表上放置一个 tablock, xlock, holdlock

标签: sql sql-server transactions


【解决方案1】:

当您使用Serializable 时,它的作用是将共享 锁(来自SELECT)保留在您已读取的行上,直到事务完成。但是一行上的共享锁不会阻止另一个事务读取同一行......它只是阻止另一个事务获得该行上的独占锁(共享锁)用于更新或删除。

如果您想阻止任何其他事务甚至读取这些行上的 (SELECT),则需要在您 SELECT 时强制执行 独占锁:

SELECT *
FROM dbo.Folders WITH (XLOCK)
WHERE ....

现在,如果 事务“保持打开状态”,没有其他事务可以读取由该 WHERE 条件选择的任何行 - 直到该 SELECT .. FROM dbo.Folders WITH (XLOCK)事务已提交或回滚。

【讨论】:

    【解决方案2】:

    如果您执行以下操作,它会完全按照您的意愿执行(阻止读取器和写入器查看行: 在会话 1 中:

    create table dbo.test(i1 int, a1 varchar(25))
    insert into dbo.test values (1,'NY'),(1,'NY'),(1,'NJ'),(2,'NY'),(2,'NY'),(2,'NJ') 
    set transaction isolation level serializable
    begin transaction
    select * from test where i1=1
    update dbo.test set i1=3 where a1='NJ'
    

    在会话 2 中尝试

    select * from dbo.test where i1=1 
    

    挂起....

    添加开始尝试和捕获只能改善事情,但即使没有它可序列化的作品。您没有向我们展示您的所有代码。

    【讨论】:

    • 如果我从您的代码中删除 update 语句,会话 2 不会挂起。为什么是这样?我喜欢的文章说Database Engine keeps read and write locks acquired on selected data,所以select在会话1中读取的数据应该被锁定吧?
    • 没有办法(没有设置)让 SQL Server 锁定没有任何更新/插入/删除仅选择的事务中的行。由于更改,SQL Server 保证了不同级别的数据一致性。没有变化,那么保证什么。您可能想探索快照级别的隔离,它可以保证数据的一致性而无需锁定。每个人都有自己的数据版本。
    • @benjaminmoskovits:真的吗?!?!?! WITH (XLOCK) 对行设置排他性锁定 - 这防止 甚至那些行中的SELECT .....
    • 我错了。即使没有更新,在选择上使用 (xlock) 的选择也会阻止对行的选择。 marc_s 解决方案是正确的。
    【解决方案3】:

    更改序列化是我在不得已的情况下才会做的事情。 在这种情况下,我会坚持使用单个事务来更新父 ID,但将更新文件夹路径的代码放在更新触发器中。

    【讨论】:

      猜你喜欢
      • 2015-05-03
      • 2018-06-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-02
      • 1970-01-01
      相关资源
      最近更新 更多