Read Committed Snapshot 只处理从表中选择数据的锁。
但是,在 t1 和 t2 中,您正在更新数据,这是一种不同的情况。
当您更新计数器时,您升级为写锁(在行上),防止发生其他更新。t2 可以读取,但 t2 将阻止其 UPDATE 直到 t1 完成,并且 t2 将无法在 t1 之前提交(这与您的时间线相反)。只有其中一个事务会更新计数器,因此在给出的代码的情况下,两者都将正确更新计数器。(已测试)
- 计数器 = 0
- t1 更新计数器(计数器 => 1)
- t2 更新计数器(阻塞)
- t1 提交(计数器 = 1)
- t2 畅通(现在可以更新计数器)(计数器 => 2)
- t2 提交
Read Committed 仅意味着您只能读取已提交的值,但这并不意味着您具有可重复读取。因此,如果您使用并依赖于 counter 变量,并打算稍后对其进行更新,那么您可能会在错误的隔离级别上运行事务。
您可以使用可重复读锁,或者如果您只是偶尔更新计数器,您可以使用乐观锁技术自己完成。例如带有计数器表的时间戳列,或条件更新。
DECLARE @CounterInitialValue INT
DECLARE @NewCounterValue INT
SELECT @CounterInitialValue = SELECT counter FROM MyTable WHERE MyID = 1234
-- do stuff with the counter value
UPDATE MyTable
SET counter = counter + 1
WHERE
MyID = 1234
AND
counter = @CounterInitialValue -- prevents the update if counter changed.
-- the value of counter must not change in this scenario.
-- so we rollback if the update affected no rows
IF( @@ROWCOUNT = 0 )
ROLLBACK
这篇devx文章提供了丰富的信息,尽管它讨论了仍处于测试阶段的功能,因此它可能并不完全准确。
更新:正如Justice所指出的,如果t2是t1中的嵌套事务,则语义不同。同样,两者都会正确更新计数器(+2),因为从 t2 在 t1 内部的角度来看,计数器已经更新了一次。嵌套的 t2 无法访问 t1 更新它之前的计数器。
- 计数器 = 0
- t1 更新计数器(计数器 => 1)
- t2 更新计数器(嵌套事务)(计数器 => 2)
- t2 提交
- t1 提交(计数器 = 2)
对于嵌套事务,如果 t1 在 t1 COMMIT 之后发出 ROLLBACK,计数器将返回到它的原始值,因为它也撤消了 t2 的提交。