在SELECT语句中使用XLOCK

时间:2017-04-14 22:53:45

标签: sql sql-server

在SELECT语句中使用XLOCK(Exclusive Lock)被认为是不好的做法吗?

让我们假设一个简单的场景,即客户的账户余额为40美元。两个并发的20美元购买请求到达。交易包括:

  1. 阅读余额
  2. 如果客户有足够的钱,请从余额中扣除产品价格
  3. 所以没有XLOCK:

    1. T1(Transaction1)读取40美元。
    2. T2读取40美元。
    3. T1将其更新为20美元。
    4. T2将其更新为20美元。
    5. 但帐户中应该还有0美元。

      有没有办法在不使用XLOCK的情况下阻止这种情况?有哪些替代方案?

3 个答案:

答案 0 :(得分:0)

执行更新时,应直接更新到数据项以防止出现这些问题。下面的示例代码演示了一种安全的方法:

CREATE TABLE #CustomerBalance (CustID int not null, Balance decimal(9,2) not null)
INSERT INTO #CustomerBalance Values (1, 40.00)

DECLARE @TransactionAmount decimal(9,2) = 19.00
DECLARE @RemainingBalance decimal(9,2)

UPDATE #CustomerBalance
SET @RemainingBalance = Balance - @TransactionAmount,
    Balance = @RemainingBalance

SELECT @RemainingBalance

(No column name)
21.00

此方法的一个优点是,只要UPDATE语句开始执行,就会锁定该行。如果两个用户同时更新值"同时",由于数据库如何工作,将开始在另一个之前更新数据。第一个UPDATE将阻止第二个UPDATE操纵数据,直到第一个完成。当第二个UPDATE开始处理记录时,它将看到第一次更新时已更新到余额中的值。

作为这样做的副作用,您需要在更新后使用代码检查余额,如果您有"透支"则回滚该值。平衡,或任何必要的。这就是为什么这个示例代码返回变量@RemainingBalance中的剩余余额。

答案 1 :(得分:0)

根据您放置查询的方式,隔离级别READ COMMITTED应该完成这项工作。 假设要执行以下代码:

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
start transaction;
update account set balance=balance-20 where accountid = 'XY';
commit;

假设T1执行语句update account set balance=balance-20 where accountid = 'XY';它将使用accountid ='XY'在记录上放置一个写锁定。 如果现在第二个事务T2在T1提交之前执行相同的语句,则阻止T2的语句直到T1提交。 之后,T2继续。最后,余额将减少40

答案 2 :(得分:0)

您的问题是基于使用XLOCK是一种不好的做法的假设。虽然将这个提示始终放在任何地方都是最正确的方法是正确的,但是没有其他方法可以在您的特定情况下实现所需的功能。

当我遇到同样的问题时,我发现在同一事务中放置在验证XLOCK, HOLDLOCK中的select组合通常可以完成工作。 (我有一个执行所有必要验证的存储过程,然后只有在一切正常时才更新Accounts表。是的,在单个事务中。)

然而,有一个重要的警告:如果您的数据库启用了RCSI,其他读者将能够通过从版本存储中获取先前的值来通过锁定。在这种情况下,添加READCOMMITTEDLOCK会关闭相关行的乐观版本控制,并将行为恢复为标准RC。