39

假设 Read Committed Snapshot 事务隔离设置,在您永远不会“丢失”并发增量的意义上,以下语句是“原子的”吗?

update mytable set counter = counter + 1

我假设在一般情况下,这个更新语句是更大事务的一部分,它不会是。例如,我认为这种情况是可能的:

  • 更新事务 #1 中的计数器
  • 在事务 #1 中做一些其他的事情
  • 使用事务 #2 更新计数器
  • 提交事务#2
  • 提交事务#1

在这种情况下,计数器最终不会只增加 1 吗?如果这是交易中的唯一声明,它会有所不同吗?

像 stackoverflow 这样的网站如何为其问题视图计数器处理这个问题?或者“失去”一些增量的可能性只是被认为是可以接受的?

4

5 回答 5

33

根据 MSSQL 帮助,您可以这样做:

UPDATE tablename SET counterfield = counterfield + 1 OUTPUT INSERTED.counterfield

这会将字段更新一,并将更新后的值作为 SQL 记录集返回。

于 2010-04-23T02:27:53.687 回答
15

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 的提交。

于 2008-10-11T00:21:51.717 回答
3

不,这不对。该值以共享模式读取,然后以独占模式更新,因此可能会发生多次读取。

要么使用可序列化级别,要么使用类似的东西

update t
set counter = counter+1
from t with(updlock, <some other hints maybe>)
where foo = bar
于 2008-10-10T22:45:00.583 回答
1

本质上只有一笔交易,最外层的一笔交易。内部事务更像是事务中的检查点。隔离级别仅影响同级最外层事务,而不影响父/子相关事务。

计数器将增加 2。下面产生一个值为 (Num = 3) 的行。(我打开 SMSS 并将其指向本地 SQL Server 2008 Express 实例。我有一个名为 Playground 的数据库用于测试。)

use Playground

drop table C
create table C (
    Num int not null)

insert into C (Num) values (1)

begin tran X
    update C set Num = Num + 1
    begin tran Y
        update C set Num = Num + 1
    commit tran Y
commit tran X

select * from C
于 2008-10-11T00:43:50.733 回答
0

我使用这个 SP 来处理 name 最初没有计数器的情况

ALTER PROCEDURE [dbo].[GetNext](
@name   varchar(50) )
AS BEGIN SET NOCOUNT ON

DECLARE @Out TABLE(Id BIGINT)

MERGE TOP (1) dbo.Counter as Target
    USING (SELECT 1 as C, @name as name) as Source ON Target.name = Source.Name
    WHEN MATCHED THEN UPDATE SET Target.[current] = Target.[current] + 1
    WHEN NOT MATCHED THEN INSERT (name, [current]) VALUES (@name, 1)
OUTPUT
    INSERTED.[current];
END
于 2019-09-26T19:21:59.217 回答