SQL Server 表隔离级别和锁定问题

Posted

技术标签:

【中文标题】SQL Server 表隔离级别和锁定问题【英文标题】:SQL Server table isolation level and lock issue 【发布时间】:2009-07-11 15:23:33 【问题描述】:

对于同一个 ADO.Net 语句,我想确保我对隔离级别和锁的理解是正确的。

    在默认的 SQL Server 隔离级别(已提交读)下,读取每一行后,该行将解锁;

    如果我将隔离级别提高到可重复读取,锁(在整个表上?还是其他级别的锁?)将一直保持到 while 循环结束?

例如:

SqlCommand cmd = conn.CreateCommand();

cmd.CommandText= "select operation_id, operation_code, product_id, quantity
from dbo.operations where processed=0";

reader=cmd.ExecuteReader();

while (reader.Read())    

   // some operations

提前致谢, 乔治

【问题讨论】:

【参考方案1】:

1)您的第一点不正确:默认隔离级别 Read Committed 意味着不会发生脏读(尽管可能会发生幻读或不可重复读取)。它不保证单行被锁定。

在以下情况下可能会发生不可重复读取:

1. Transaction 1 begins
2. Transaction 1 read a row
3. Transaction 2 begins
4. Transaction 2 changes the value of the same row read by Transaction 1
5. Transaction 2 commits
6. Transaction 1 reads the row again. Transaction 1 has inconsistent data.

2) 可重复读隔离级别意味着上述情况不会发生(尽管幻读仍然可能发生)。幻读可能发生在以下情况:

1. Transaction 1 begins
2. Transaction 1 read a row
3. Transaction 2 begins
4. Transaction 2 deletes the row read by Transaction 1
5. Transaction 2 commits. Transaction 1 can no longer repeat its initial read, 
   since the row no longer exists.

如果您想保证数据在读取时不会更改,则需要 Serializable 隔离级别。我强烈建议您不要使用 Serializable 隔离级别,除非您绝对必须这样做,因为并发性会受到影响。

【讨论】:

感谢Mitch,对于(1),我很困惑如果行没有被锁定,如何保证不读取脏数据(如果没有锁定,其他事务可能会更改/删除该行)?对于(2),如果我将隔离级别提高到可重复读取,我认为所有行(由 SELECT WHERE 语句选择)都被锁定在 while 循环中,这是正确的理解吗?如果不加锁,如何保证可重复读取?【参考方案2】:

一些关于隔离级别差异的有用文章:Selects under READ COMMITTED and REPEATABLE READ may return incorrect results.

When Snapshot Isolation Helps and When It Hurts

【讨论】:

嗨,AlexKuznetsov,在您的第一个链接中,我很困惑为什么在读取提交的隔离级别中,选择和事务会得到不同的结果。我的困惑是我认为 select sum 交易将始终读取已提交,因此帐户应始终保持平衡——帐户转移交易仅在提款和存款操作都成功时才会提交。有没有cmets? @George2:您可能会得到不同的结果,因为一行可能被计算两次或根本不计算。当您在我的示例中修改一行时,相应的索引条目会移动。因为查询扫描了这个索引,所以它可以计算两次移动的索引条目。【参考方案3】:

在可重复读取或可序列化隔离级别中,SELECT 获取的行锁将保持直到事务提交,而不是直到循环结束。如果您未指定显式事务,则 SELECT 语句将启动一个隐式事务,该事务将在 SELECT 语句完成时自动提交。这与 while 循环结束的时刻不同,循环在客户端,SELECT 语句可能在循环结束之前在服务器上完成。

正如米奇所说,更高级别的隔离级别有一定的目的,以避免幻读或不可重复读。在自动提交的隐式事务中,单个 SELECT 语句不能要求更高的隔离级别。当数据被多次读取时,这些级别仅在多个语句事务中起作用。如果您解释您正在执行的操作的上下文以及您为什么担心此 SELECT 将放置的锁,也许会更好?

【讨论】:

谢谢 Remus,很好的回复!还有两个cmets,1.“这与while循环结束的时刻不同”,您的意思是我们在执行select语句后立即持有锁吗?如果是,我不同意,因为我在 while 循环中(以流式方式)使用 DataReader 读取数据,并且应该保持锁定直到循环结束。感谢您能澄清一下。 :-) 2.“在自动提交的隐式事务中,单个 SELECT 语句不能要求更高的隔离级别。” - 我很困惑。我认为在 T-SQL 中,单语句事务是允许的,在事务中我们应该能够 (续)设置隔离级别。如果您能澄清一下,不胜感激。 1) 一旦最后一行被发送回客户端,SELECT 语句就会结束,因此会自动提交。客户端网络堆栈将缓冲所有接收到的行,直到它们被 SqlDataReader 使用。因此,您可以在 SELECT 已经在服务器上完成很久之后迭代您的循环。 2) 不需要,就像在这样做没有意义。如果您没有在事务中再次读取相同的数据,则更高的隔离级别没有意义。

以上是关于SQL Server 表隔离级别和锁定问题的主要内容,如果未能解决你的问题,请参考以下文章

浅析SQL Server在可序列化隔离级别下,防止幻读的范围锁的锁定问题

浅析SQL Server在可序列化隔离级别下,防止幻读的范围锁的锁定问题

SQL SERVER的锁机制——概述(锁与事务隔离级别)

SQL Server 事务隔离级别详解

SQL Server 的 Sequelize 事务隔离级别问题

SQL Server中锁与事务隔离级别