存储过程不更新记录

Stored procedure not updating record

我有一个存储过程的问题,它正在更新一个 table,而不是另一个,而且它只是偶尔发生,而不是一直发生。这是存储过程:

ALTER PROCEDURE [dbo].[AddTransaction]
    @BeneficiaryName VARCHAR(200),
    @Amount DECIMAL(18,2),
    @Reference VARCHAR(200),
    @Direction INT,
    @Currency INT,
    @CurrencyCulture VARCHAR(5),
    @Status INT,
    @Month INT,
    @Year INT,
    @BankAccountId INT,
    @BusinessId INT,
    @FeeType INT,
    @IncomingPaymentsPercentage DECIMAL(18,2),
    @IncomingPaymentFee DECIMAL(18,2),
    @CompletedPaymentFee DECIMAL(18,2),
    @DateProcessed DATETIME
AS
BEGIN
    
    DECLARE @oldBalance DECIMAL(18,2)
    DECLARE @newBalance DECIMAL(18,2)
    DECLARE @fee DECIMAL(18,2)
    DECLARE @amountAfterFee DECIMAL(18,2)

    BEGIN TRANSACTION
        -- SET NOCOUNT ON;
        
        -- lock table 'BankAccounts' till end of transaction
        SELECT @oldBalance = availableBalance
        FROM BankAccounts
        WITH (TABLOCKX, HOLDLOCK)
        WHERE Id = @BankAccountId

        -- set the new balance
        SET @newBalance = @oldBalance + @Amount

        UPDATE BankAccounts SET AvailableBalance = @newBalance WHERE Id = @BankAccountId

        -- insert a new transaction
        IF @FeeType = 1
        BEGIN
            IF @Direction = 1
            BEGIN
                SET @fee = @Amount * (@IncomingPaymentsPercentage / 100)
                SET @amountAfterFee = @Amount - @fee;
            END;
            ELSE
            BEGIN
                SET @fee = 0.00
                SET @amountAfterFee = @Amount
            END;
        END;
        ELSE
        BEGIN
            IF @Direction = 1
            BEGIN
                SET @amountAfterFee = @Amount - @IncomingPaymentFee
                SET @fee = @IncomingPaymentFee
            END;
            ELSE
            BEGIN
                SET @amountAfterFee = @Amount - @CompletedPaymentFee
                SET @fee = @CompletedPaymentFee
            END;
        END;

        INSERT INTO Transactions
            (BeneficiaryName, amount, Reference, Direction, Currency, CurrencyCulture, DateAdded, [Status], DateProcessed, [Month], [Year], Balance, BankAccountId, AmountAfterFee, Fee)
        VALUES
            (@BeneficiaryName, @Amount, @Reference, @Direction, @Currency, @CurrencyCulture, @DateProcessed, @Status, @DateProcessed, @Month, @Year, @newBalance, @BankAccountId, @amountAfterFee, @fee)
    
    -- now commit the transaction
    COMMIT

插入交易 table 一直有效,但偶尔,BankAccounts table 的更新不会发生。这行在这里:

UPDATE BankAccounts SET AvailableBalance = @newBalance WHERE Id = @BankAccountId

我不确定这是否与锁定有关,但我认为您仍然可以在同一事务中更新锁定的 table。有关详细信息,同一秒内可能会多次调用此存储过程,可能不超过 10 次,但通常问题是没有其他任何东西正在访问它正在更新的 BankAccount 记录。

首先,SELECT 语句中的 TABLOCKX 不使用 X 锁。 SELECT 语句永远不会使用 X 锁。此外,锁定整个 table.

是不必要的,而且效率极低。

其次,这里即使HOLDLOCK也不够,因为如果没有修改数据,就不会拿锁。 必须使用UPDLOCK才能按预期工作。

老实说,您实际上并不需要 SELECT,因为整个事情可以在一个原子语句中完成:

I note that @oldBalance isn't actually used.

UPDATE BankAccounts WITH (HOLDLOCK)
SET AvailableBalance +=  @amount,
    @newBalance = AvailableBalance + @amount
WHERE Id = @BankAccountId;

您甚至可以使用 OUTPUT 子句组合 Transaction 插入,这使您能够完全删除 BEGIN TRANSACTION/COMMIT

ALTER PROCEDURE [dbo].[AddTransaction]
    @BeneficiaryName VARCHAR(200),
    @Amount DECIMAL(18,2),
    @Reference VARCHAR(200),
    @Direction INT,
    @Currency INT,
    @CurrencyCulture VARCHAR(5),
    @Status INT,
    @Month INT,
    @Year INT,
    @BankAccountId INT,
    @BusinessId INT,
    @FeeType INT,
    @IncomingPaymentsPercentage DECIMAL(18,2),
    @IncomingPaymentFee DECIMAL(18,2),
    @CompletedPaymentFee DECIMAL(18,2),
    @DateProcessed DATETIME
AS

SET NOCOUNT ON;
    
DECLARE @fee DECIMAL(18,2);
DECLARE @amountAfterFee DECIMAL(18,2);

SET @fee = CASE WHEN @FeeType = 1 THEN
    CASE WHEN @Direction = 1
        THEN @Amount * (@IncomingPaymentsPercentage / 100)
        ELSE 0.0 END
    ELSE
    CASE WHEN @Direction = 1
        THEN @IncomingPaymentFee
        ELSE @CompletedPaymentFee END
    END;
SET @amountAfterFee = @Amount - @fee;

UPDATE BankAccounts WITH (HOLDLOCK)
SET AvailableBalance += @amount

OUTPUT
    @BeneficiaryName, @Amount, @Reference, @Direction, @Currency,
    @CurrencyCulture, @DateProcessed, @Status, @DateProcessed,
    @Month, @Year, inserted.AvailableBalance, @BankAccountId, @amountAfterFee, @fee

INTO Transactions
   (BeneficiaryName, amount, Reference, Direction, Currency,
    CurrencyCulture, DateAdded, [Status], DateProcessed, [Month], [Year],
    Balance, BankAccountId, AmountAfterFee, Fee)

WHERE Id = @BankAccountId;

GO