在MS SQL Server中,有没有办法“原子地”递增用作计数器的列?

时间:2008-10-10 22:39:26

标签: sql sql-server-2005 transactions

假设一个Read Committed Snapshot事务隔离设置,在你不会“失去”并发增量的意义上,下面的语句是“atomic”吗?

update mytable set counter = counter + 1

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

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

在这种情况下,计数器最终只会增加1吗?如果这是交易中唯一的陈述,它会有所作为吗?

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

5 个答案:

答案 0 :(得分:26)

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

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

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

答案 1 :(得分:13)

Read Committed Snapshot仅处理从表中选择数据时的锁定。

然而,在t1和t2中,您正在更新数据,这是一种不同的情况。

当您更新计数器时,您会升级到写锁(在行上),从而阻止发生其他更新。 t2可以读取,但是t2将在其UPDATE上阻塞,直到t1完成,并且t2将无法在t1之前提交(这与您的时间轴相反)。只有其中一个事务将更新计数器,因此两者都会在给出代码时正确更新计数器。 (测试)

  • counter = 0
  • t1更新计数器(counter => 1)
  • t2更新计数器(已屏蔽)
  • t1 commit(counter = 1)
  • t2 unblocked(现在可以更新计数器)(counter => 2)
  • t2 commit

Read Committed只表示您只能读取已提交的值,但这并不意味着您具有可重复读取。因此,如果您使用并依赖于计数器变量,并打算稍后更新它,那么您可能正在以错误的隔离级别运行事务。

您可以使用可重复的读锁定,或者如果您有时只更新计数器,则可以使用乐观锁定技术自行完成。例如带有计数器表的时间戳列,或条件更新。

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),因为从t1的t1内部来看,计数器已经更新一次。在t1更新它之前,嵌套的t2无法访问计数器。

  • counter = 0
  • t1更新计数器(counter => 1)
  • t2更新计数器(嵌套事务)(counter => 2)
  • t2 commit
  • t1 commit(counter = 2)

使用嵌套事务,如果t1在t1 COMMIT之后发出ROLLBACK,则计数器返回其原始值,因为它还会撤消t2的提交。

答案 2 :(得分:3)

不,不是。该值以共享模式读取,然后以独占模式更新,因此可以进行多次读取。

使用Serializable级别或使用

之类的东西
update t
set counter = counter+1
from t with(updlock, <some other hints maybe>)
where foo = bar

答案 3 :(得分: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

答案 4 :(得分:0)

我使用此SP来处理名称最初没有计数器的情况

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