HOLDLOCK XLOCK ROWLOCK 有时不工作

HOLDLOCK XLOCK ROWLOCK not working some times

我有以下情况:银行环境,我不希望特定客户完成从他的帐户中取款,除非之前的取款没有提交。

为了实现这一点,我创建了以下 table

CREATE TABLE [dbo].[Locks](
        [CustomerID] [int] NOT NULL,
    CONSTRAINT [PK_Locks] PRIMARY KEY CLUSTERED 
    (
        [CustomerID] ASC
    )

现在,每当提款开始时,我都有以下代码,基本上将客户插入此助手 table(如果他不存在),然后在交易期间锁定该行,以便在第一笔交易提交之前不会发生其他取款

BEGIN TRAN
IF NOT EXISTS (SELECT * FROM Locks WHERE CustomerID=@customerId) --if customerid does not exist, insert the row
BEGIN
    INSERT INTO Locks (CustomerID) 
    VALUES (@customerId)
END
SELECT CustomerID FROM Locks WITH (HOLDLOCK XLOCK ROWLOCK) WHERE CustomerID=@customerId --lock on row
--(check if customer has enough balance, then perform withdraw from customer account)
COMMIT

上面的代码似乎一般每天有数千次取款,但每周一次左右,我确实遇到一个情况,锁不起作用并且发现客户余额为负值,因为两次取款操作同时发生。

在什么情况下 HOLDLOCK XLOCK ROWLOCK 可能无法锁定事务有什么想法吗?

IF NOT EXISTS (SELECT * FROM Locks WHERE CustomerID=@customerId) --if customerid does not exist, insert the row
BEGIN
    INSERT INTO Locks (CustomerID) 
    VALUES (@customerId)
END
SELECT CustomerID FROM Locks WITH (HOLDLOCK XLOCK ROWLOCK) WHERE CustomerID=@customerId --lock on row
--(check if customer has enough balance, then perform withdraw from customer account)

这是一个竞争条件农场

  • 两个事务都可以运行 SELECT,断定没有锁,并且都继续插入锁。有了 PK 约束,fail.This 就是最幸福的情况。
  • 一个事务可以来,运行 SELECT,断定有一行并进行第二个SELECT。同时,该行可以删除。 SELECT WITH (lock hints) 将找不到任何行,但仍继续得出它锁定了某些内容的结论(它没有,没有要锁定的行)。这是一个更糟糕的情况,它可能导致(滚鼓,拜托!)负余额

这些在大约 10 秒的代码检查中出现。我敢肯定还有更多(我什至没有考虑回滚......)。使用行作为锁是一种反模式。 Use applocks instead.