我在 C#.Net 中制作一个自我项目,通过使用 3 层应用程序,我不想让我的 sql 语句具有事务性,这些语句用于多个存储过程,我一直在使用 WITH(NOLOCK)方法是为了查询那些在事务期间用于插入或更新的表,在 C# 端我一直在使用 TransactionScope,但我最近读到不推荐使用 WITH(NOLOCK),因为它会导致幻像读取或使用脏数据和不一致数据。我的问题是,为了使用在事务期间插入或更新的数据,当涉及到选择时,就事务操作而言,最好的方法是什么?无论是数据库端还是业务代码端。
4 回答
您是否考虑过快照隔离?它提供了完美的读取一致性,并且完全不锁定数据。
SI 在许多 RDBMS 上是标准的,并且默认启用。不知道为什么 SQL Server 的人对使用它犹豫不决。缺点是轻微的,但你需要研究它们。
你根本不需要它。如果你修改了一些数据,然后在同一个事务中查询它,你会得到修改后的数据。
您的事务将对修改的数据持有排他锁,因此它可以对它做任何事情 - 查询、再次修改等。
如果指定 WITH(NOLOCK),则允许查询忽略来自其他事务的排他锁。这将导致您的查询返回不正确的数据。
也许你不知道你想要什么。
WITH(NOLOCK) 返回脏数据和不一致数据。
如果您想要干净的数据,则必须删除此子句,并等待您要读取的表的最终并发更新。
下表显示了不同隔离级别允许的并发副作用。
从上面的链接中阅读并理解每个列标题的含义。
大多数数据库的默认设置为已提交读。因此,当您在使用或不使用 NOLOCK 的情况下触发查询时,并假设您的数据库处于默认的读取提交模式,您将面临不可重复读取和幻象的问题。
当您使用 NOLOCK 时,您还面临脏读问题。这意味着您甚至可以在事务提交或回滚之前读取它们所做的修改。
还有缺失行的问题(例如,假设您的选择查询已读取更新的行)和重复行(例如,假设已读取的行已更新为尚未读取的值选择查询)。NOLOCK 会导致分配顺序扫描而不是索引顺序扫描,这会导致丢失或重复行问题。
您需要务实地思考上述问题对您的影响,并决定是否使用 NOLOCK 提示。
以下答案给出了一个关于两次读取某些行的示例:https ://stackoverflow.com/a/2268078/1779091 (答案粘贴在下面)
NOLOCK 提示会导致脏读异常,其中一种异常是重复读取。如果更新更改了查询扫描的索引中行的位置,则此类读取很频繁:
假设您在表中有 2 行,具有 ID 键,键值为 1 和 2 的行一个请求 (T1) 运行 UPDATE table SET key=3 WHERE key=1;
第二个请求 (T2) 运行 SELECT ... FROM table WITH(NOLOCK);
T1 锁定键值为 1 的行
T2 忽略 T1 拥有的锁并读取键值为 1 的行
T2 继续并读取键值为 2 的行
T1 更新该行,该行在索引中移动到键值 3 的新位置
T2继续扫描,读取键值为3的行
因此 SELECT 读取了一行两次,一次是键值为 1 时,另一次是键值为 3 时。这只是可能发生的事情的一个简单示例。实际上,更复杂的查询可以运行复杂的计划并使用其他索引,所有这些都会受到此类异常的影响。
简而言之:NOLOCK 提示是邪恶的。如果要避免争用,请使用快照隔离。