防止多行的竞争条件

Nel*_*mel 4 locking race-condition sql-server-2008

我已经阅读了很多关于防止竞争条件的内容,但通常在upsert场景中有一条记录.例如: SQL Server 2005中的Atomic UPSERT

我有不同的要求,它是为了防止多行的竞争条件.例如,假设我有以下表结构:

GiftCards:
  GiftCardId int primary key not null,
  OriginalAmount money not null

GiftCardTransactions:
  TransactionId int primary key not null,
  GiftCardId int (foreign key to GiftCards.GiftCardId),
  Amount money not null
Run Code Online (Sandbox Code Playgroud)

可能有多个进程插入GiftCardTransactions,我需要阻止插入,如果SUM(GiftCardTransactions.Amount) + insertingAmount将继续GiftCards.OriginalAmount.

我知道我可以使用TABLOCKXGiftCardTransactions,但很明显,对于很多的交易,这将是不可行的.另一种方法是添加一个GiftCards.RemainingAmount列,然后我只需要锁定一行(虽然可能会锁定升级),但不幸的是,这对我来说不是一个选项(这是最好的选择吗?).

可能答案只是插入,然后选择SUM(GiftCardTransactions.Amount),并在必要时回滚,而不是试图阻止插入.这是一个边缘情况,所以我不担心不必要地使用PK值等.

所以问题是,如果不修改表结构并使用事务,隔离级别和提示的任何组合,我如何通过最小量的锁定来实现这一点?

Cod*_*ian 8

我在过去遇到过这种情况并最终使用SP_GetAppLock在密钥上创建信号量以防止竞争条件.几年前我写了一篇文章讨论各种方法.文章在这里:

http://www.sqlservercentral.com/articles/Miscellaneous/2649/

基本思想是获取与表分离的构造键的锁定.通过这种方式,您可以非常精确,并且只能阻止可能会产生竞争条件并且不会阻止表的其他使用者的spid.

我已经离开了下面这篇文章的内容,但我会通过获取一个构造键的锁来应用这种技术

@Key = 'GiftCardTransaction' + GiftCardId 
Run Code Online (Sandbox Code Playgroud)

获取此密钥的锁定(并确保您始终如一地应用此方法)可以防止任何潜在的竞争条件,因为第一个获取锁定会使所有其他请求等待锁定被释放(或超时,取决于您希望自己的应用如何运作.)

文章的内容在这里:

SP_getapplock是扩展过程的包装器XP_USERLOCK.它允许您使用SQL SERVER锁定机制来管理表和行范围之外的并发.它可以用来编组PROC调用,就像上面的解决方案一样具有一些附加功能.

Sp_getapplock 将锁直接添加到服务器内存,从而降低开销.

其次,您可以指定锁定超时,而无需更改会话设置.如果您只想要一个特定键的调用运行,快速超时将确保proc不会长时间执行应用程序.

第三,sp_getapplock返回一个状态,该状态可用于确定代码是否应该运行.同样,在您只需要对特定键进行一次调用的情况下,返回代码1会告诉您在等待释放其他不兼容的锁之后已成功授予锁定,因此您可以在不运行任何其他代码的情况下退出(例如例如,存在检查).synax如下:

   sp_getapplock [ @Resource = ] 'resource_name',
      [ @LockMode = ] 'lock_mode'
      [ , [ @LockOwner = ] 'lock_owner' ]
      [ , [ @LockTimeout = ] 'value' ]
Run Code Online (Sandbox Code Playgroud)

一个使用的例子 sp_getapplock

/************** Proc Code **************/
CREATE PROC dbo.GetAppLockTest
AS

BEGIN TRAN
    EXEC sp_getapplock @Resource = @key, @Lockmode = 'Exclusive'

    /*Code goes here*/

    EXEC sp_releaseapplock @Resource = @key
COMMIT
Run Code Online (Sandbox Code Playgroud)

我知道这是不言而喻的,但由于sp_getapplock锁的范围是显式事务,所以请务必SET XACT_ABORT ON或在代码中包含检查以确保在需要时发生ROLLBACK.