使用事务隔离级别

时间:2017-05-12 15:51:42

标签: sql sql-server transactions

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

我有一个名为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. 更新ParentId的{​​{1}}和FullPathfolder2
  5. 阅读UPDATE Folders SET ParentId=folder4.Id, FullPath=folder4.FullPath+folder2.Name+'/' WHERE Id = folder2.Id的所有子文件夹(称之为folder2):subfoldersOfFolder2
  6. 对于SELECT * FROM Folders WHERE FullPath LIKE folder2.FullPath + '%'更新subfolder列中的每个subfoldersOfFolder2(查询已省略)
  7. 提交交易
  8. 显然,在我的事务完成之前,我不希望任何其他事务写入(甚至读取)FullPathfolder2

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

    为什么会这样?如何阻止其他人阅读/撰写SELECT * FROM Foldersfolder2?我觉得我错过了一些关于事务如何实际锁定数据的重要信息。

3 个答案:

答案 0 :(得分:2)

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

如果您想阻止任何其他交易甚至在这些行上阅读(SELECT),您需要在SELECT时强制执行独占锁定:

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

现在,如果事务“保持打开”,没有其他事务可以读取WHERE条件选择的任何行 - 直到{ {1}}事务已提交或回滚。

答案 1 :(得分:0)

如果您执行以下操作,它会完全符合您的要求(阻止读者和作者查看行: 在第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 

...挂起

添加begin try和catch只能改进一些东西,但即使没有它可序列化的工作。您没有向我们展示您的所有代码。

答案 2 :(得分:0)

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