SQL Server竞争条件问题

时间:2009-11-05 21:39:12

标签: sql sql-server transactions

(注意:这是针对MS SQL Server的)

假设您有一个带有主键标识列和CODE列的表ABC。我们希望这里的每一行都有一个独特的,顺序生成的代码(基于一些典型的校验位公式)。

假设您有另一个表DEF只有一行,它存储下一个可用的CODE(想象一个简单的自动编号)。

我知道下面的逻辑会出现竞争条件,其中两个用户最终可能会使用相同的代码:

1) Run a select query to grab next available code from DEF
2) Insert said code into table ABC
3) Increment the value in DEF so it's not re-used.

我知道,两个用户可能会陷入第1步),最终可能会在ABC表中找到相同的CODE。

处理这种情况的最佳方法是什么?我以为我可以围绕这个逻辑包装一个“begin tran”/“commit tran”,但我认为这不起作用。我有一个这样的存储过程来测试,但当我从MS中的两个不同的窗口运行时,我没有避免竞争条件:

begin tran

declare @x int

select   @x= nextcode FROM  def

waitfor delay '00:00:15'

update def set nextcode = nextcode + 1

select @x

commit tran

有人可以对此有所了解吗?我认为该事务会阻止其他用户在第一个事务完成之前能够访问我的NextCodeTable,但我想我对事务的理解是有缺陷的。

编辑:我尝试将等待移到“更新”声明之后,我有两个不同的代码...但我怀疑。我在那里有waitfor声明来模拟延迟,因此可以很容易地看到竞争条件。我认为关键问题是我对交易如何运作的错误看法。

7 个答案:

答案 0 :(得分:6)

将事务隔离级别设置为Serializable 在较低的隔离级别,其他事务可以读取此事务中读取(但尚未修改)的行中的数据。因此,两个事务确实可以读取相同的值。在非常低的隔离(Read Uncommitted)下,其他事务甚至可以在修改后(但在提交之前)读取数据......

查看有关SQL Server隔离级别here

的详细信息

所以底线是隔离级别在这里是一个重要的部分来控制其他事务进入这个访问的访问级别。

请注意。来自link,关于 Serializable
   语句无法读取已修改但尚未由其他事务提交的数据 这是因为锁定是在修改行时放置的,而不是在Begin Trans发生时放置的。所以你所做的可能仍然允许另一个事务读取旧值,直到修改它为止。所以我会改变逻辑,在你读取它的同一个语句中修改它,从而同时锁定它。

begin tran
declare @x int
update def set @x= nextcode, nextcode += 1
waitfor delay '00:00:15'
select @x
commit tran

答案 1 :(得分:5)

正如其他响应者所提到的,您可以设置事务隔离级别,以确保您使用SELECT语句“读取”的任何内容都不能在事务中更改。

或者,您可以通过在表名后添加语法WITH HOLDLOCK来专门取消DEF表上的锁定,例如,

SELECT nextcode FROM DEF WITH HOLDLOCK

这里没有太大区别,因为您的事务很小,但是为某些SELECT而不是事务中的其他SELECT取出锁定会很有用。这是“可重复性与并发性”的问题。

一些相关的MS-SQL文档。

答案 2 :(得分:4)

迟到的答案。你想避免竞争条件......

"SQL Server Process Queue Race Condition"

答案 3 :(得分:3)

回顾:

  • 您开始了一项交易。这实际上并不“做”任何事情本身,它会修改后续行为
  • 您从表中读取数据。默认隔离级别为Read Committed,因此该select语句成为事务的一部分。
  • 然后等待15秒
  • 然后发出更新。使用声明的事务,这将生成一个锁,直到提交事务。
  • 然后提交事务,释放锁定。

所以,猜测你在两个窗口(A和B)中同时运行它:

  • 从表def读取“next”值,然后进入等待模式
  • B从表中读取相同的“next”值,然后进入等待模式。 (由于A只进行了读取,因此事务没有锁定任何内容。)
  • 然后更新了表,并且可能在B退出等待状态之前提交了更改。
  • B然后在A的写入提交后更新了表。

尝试在更新之后,提交之前放置wait语句,看看会发生什么。

答案 4 :(得分:1)

这不是真正的竞争条件。这是并发事务的常见问题。一种解决方案是在表上设置读锁定,因此可以进行序列化。

答案 5 :(得分:0)

您可以将列设置为持久的计算值。这将照顾竞争条件。

Persisted Computed Columns

注意

使用此方法意味着您无需将下一个代码存储在表中。代码列成为参考点。

<强>实施

在计算列规范下为列提供以下属性。

Formula = dbo.GetNextCode()

坚持=是

Create Function dbo.GetNextCode()
Returns VarChar(10)
As
Begin

    Declare @Return VarChar(10);
    Declare @MaxId Int

    Select @MaxId = Max(Id)
    From Table

    Select @Return = Code
    From Table
    Where Id = @MaxId;

    /* Generate New Code ... */

    Return @Return;

End

答案 6 :(得分:0)

这实际上是SQL数据库中的常见问题,这就是为什么大多数(全部?)都有一些内置功能来处理获取唯一标识符的问题。如果您使用的是Mysql或Postgres,请参阅以下内容。如果你使用的是另一个数据库我敢打赌提供非常相似的东西。

这方面的一个很好的例子是postgres序列,你可以在这里查看:

Postgres Sequences

Mysql使用一种称为自动增量的东西。

Mysql auto increment