可以使用检查约束代替悲观锁吗?
考虑 SQL Server 中有下表:
create table Balances(
id int,
userId int,
balance money)
如果超过 1 个线程尝试更改余额,将检查约束并解决任何问题 并发问题还是应该使用锁?
另外,我想知道检查约束在其他关系数据库(例如 Postgres)中是否具有相同的行为?
我在这里回答字里行间,因为问题本身没有多大意义,但评论似乎添加了一些上下文。
我怀疑真正的问题不是
CONSTRAINT
(你应该有),而是你的逻辑,并且你可能有如下所示的东西:
IF (SELECT Balance - @TransferValue
FROM dbo.YourTable
WHERE AccountID = @AccountID) >= 0
BEGIN
UPDATE dbo.YourTable
SET Balance = Balance - @TransferValue
WHERE AccountID = @AccountID;
END;
不管
CONSTRAINTS
,如果您同时运行 2 个线程,这可能无法按您想要的方式工作。然后我们会遇到一个潜在的竞争条件:
Balance - @TransferValue
的值并解析 >= 0 的值UPDATE
,还检查 Balance - @TransferValue
的值并解析 >= 0UPDATE
是 Balance
的值,将其减少到 >= 0 的值;事务完成时锁定进程中的行。UPDATES
Balance
的值将其减少到一个值 < 0。在这种情况下,
CONSTRAINT
将停止第二次更新的运行,但这并不能阻止上面的代码变得bad。如果您真的愿意,我们可以让它变得更糟,然后CONSTRAINT
根本不起作用:
DECLARE @NewBalance decimal(12,4);
SELECT @NewBalance = Balance - @TransferValue
FROM dbo.YourTable
WHERE AccountID = @AccountID;
IF @NewBalance > 0
BEGIN
UPDATE dbo.YourTable
SET Balance = @NewBalance
WHERE AccountID = @AccountID;
END;
现在,
CONSTRAINT
根本无法阻止UPDATE
。事实上,Balance
的值将是Balance
before线程1完成的值减去线程2的传输值。
对于这样一个简单的
UPDATE
,您只需在同一个语句中处理所有内容并使用CONSTRAINT
,是的。所以 CONSTRAINT
就是
ALTER TABLE dbo.YourTable ADD CONSTRAINT chk_YourTable_PositiveBalance CHECK (Balance >= 0);
然后你可以执行以下语句:
UPDATE dbo.YourTable
SET Balance = Balance - @TransferValue
WHERE AccountID = @AccountID;
但是,如果您因“原因”需要“离开”并事先获取余额的价值,那么您需要应用适当的锁定并使用交易。比如:
SET XACT_ABORT ON;
BEGIN TRANSACTION;
DECLARE @NewBalance decimal(12,4);
SELECT @NewBalance = Balance - @TransferValue
FROM dbo.YourTable WITH (UPDLOCK)
WHERE AccountID = @AccountID;
--Do something(s)
UPDATE dbo.YourTable
SET Balance = @NewBalance
WHERE AccountID = @AccountID;
--Do something(s) else?
COMMIT;
然后,当线程 1 仍在执行其工作时,线程 2 将无法为
@NewBalance
赋值。