如何正确处理对同一变量的并发访问?

时间:2018-06-21 18:23:21

标签: c# .net sql-server dapper

在我的系统中,我想创建与银行帐户非常相似的功能。我有一些可以操作的余额。让我们通过一个例子来说明它。

我有两个用于余额和操作的表:

Balances
(
   Id int,
   Balance decimal
)

Operations
(
    Id int,
    FK_BalancesId int,
    Value decimal
)

当我想将500添加到特定余额时,我只需要保存带有BalanceId和要添加的值的操作。同样,我要减去500,我只保存值“ -500”的操作。当然,在保存减法运算之前,我必须检查添加到余额上的所有操作中的余额是否高于减法值。

我的问题是,当两个人在差不多相同的时间想减去“ -500”时。 当某人想添加运算“ -500”时,我只是获得所有特定余额的运算,将它们与余额相加,然后检查总和是否大于或等于500。(我不想在余额上获得负数) 。但是,当两个人想要在差不多相似的时间进行这样的手术时的情况呢?这是我不知道如何处理的情况:

A wants add operation "-500"
B wants add operation "-500"
System check for A - balance is 750 operation is allowed
System check for B - balance is 750 operation is allowed
A save operation "-500"
B save operation "-500"

然后该余额低于0(-250)。如何正确清洁这种情况?

1 个答案:

答案 0 :(得分:0)

假设整个事情都应该在SQL Server中完成,并且这些是下面的真实表定义-我将Operations表中的“ Id”更改为Identity列。以下过程将检查余额,以确保交易不会产生负余额,用给定的金额更新余额,并返回插入的操作ID的值。如果余额将变为负数,则将返回NULL作为操作ID,并且余额保持不变。由于您在问题和示例中的值两边加上了引号,因此该过程允许您以字符串形式传递操作。

CREATE TABLE Balances
(
   Id INT,
   Balance DECIMAL
)

CREATE TABLE Operations
(
    Id INT IDENTITY(1,1) NOT NULL,
    FK_BalancesId INT,
    Value DECIMAL
)
GO 

--insert the beginning balance 
insert into Balances values (1, 750)
GO 


CREATE PROCEDURE sp_SaveOperation
    @BalanceId INT
    , @OperationAmount VARCHAR(100)
AS

DECLARE @OperationsId INT
DECLARE @Balance DECIMAL
DECLARE @Amount DECIMAL

SET @Amount = CAST(@OperationAmount AS DECIMAL)

BEGIN TRANSACTION 

    SET @Balance = (SELECT B.Balance FROM Balances WITH (SERIALIZABLE) WHERE Id = @BalanceId)

    -- [+] operation assuming amount will be a negative number
    IF (@Balance + @Amount > 0)
        BEGIN

        UPDATE Balances SET Balance = Balance + @Amount

        INSERT INTO Operations (FK_BalancesId, Value) VALUES (@BalanceId, @Amount)

        SET @OperationsId = SCOPE_IDENTITY()

        END

COMMIT TRANSACTION 

SELECT @OperationsId

用法:

exec sp_SaveOperation 1, '-500';
_________________________________
Returns 1

exec sp_SaveOperation 1, '-500';
_________________________________
Returns NULL


select * from balances;
____________________________
Id    Balance
1     250

select * from operations;
____________________________
Id    FK_BalancesId    Value
1     1                -500