Sql-Server

在我的 SQL Azure 數據庫中觀察到失去更新的原因是什麼?

  • February 23, 2014

我有一個帶有下表的 SQL Azure 數據庫:

CREATE TABLE [dbo].[UserBalances]
 (
    [UserId]         UNIQUEIDENTIFIER UNIQUE NOT NULL DEFAULT NEWID(),
    [AvailableMoney] INT NOT NULL DEFAULT(0)
 );

GO

CREATE UNIQUE CLUSTERED INDEX [UserBalancesIndex]
 ON [dbo].[UserBalances]([UserId] ASC);

GO

CREATE TABLE [dbo].[UserBalanceChanges]
 (
    [EntryId]      UNIQUEIDENTIFIER NOT NULL DEFAULT NEWID(),
    [UserId]       UNIQUEIDENTIFIER NOT NULL,
    [PriorBalance] INT NOT NULL,
    [NewBalance]   INT NOT NULL,
    [Time]         DATETIME DEFAULT(GETUTCDATE())
 )

GO

CREATE CLUSTERED INDEX UserBalanceChangesIndex
 ON [UserBalanceChanges]( EntryId )

GO 

並且通過數據庫的多個連接同時執行以下程式碼:

CREATE PROCEDURE [dbo].[usp_ChangeUserBalance] @userId UNIQUEIDENTIFIER,
                                              @change INT
AS
   BEGIN TRANSACTION

   DECLARE @priorBalance INT;
   DECLARE @newBalance INT;
   DECLARE @updateTime DATETIME;

   UPDATE dbo.UserBalances
   SET    @updateTime = GETUTCDATE(),
          @priorBalance = AvailableMoney,
          @newBalance = AvailableMoney = AvailableMoney + @change
   WHERE  UserId = @userId;

   INSERT INTO UserBalanceChanges
               (UserId,
                PriorBalance,
                NewBalance,
                Time)
   VALUES     ( @userId,
                @priorBalance,
                @newBalance,
                @updateTime );

   COMMIT TRANSACTION

   RETURN 0 

然後我執行以下查詢:

SELECT TOP(1000) PriorBalance,
                NewBalance
FROM   UserBalanceChanges
WHERE  UserId = SomeSpecificId
ORDER  BY Time DESC 

我經常看到這樣的事情:

1000 995
1005 1000 <<identical
1005 1000 <<changes
1010 1005
1015 1010

看起來兩個同時執行的更新讀取了相同的初始值,然後一個更新實際上失去了。

失去的更新是一個已知的異常,但是在 SQL Server 中失去更新是不可能的,並且大概在 SQL Azure 中也是不可能的(至少使用我擁有的單個“SELECT from UPDATE”語句。

然而,看起來我實際上看到了失去的更新。

為什麼我觀察到“不可能”失去的更新?

問題是因為“其他解釋”失去更新 - 當數據首先被讀入本地記憶體然後用該數據更新表時。還有另一個程式碼很少用於重置*某些使用者的餘額。*是這樣的:

DECLARE @BalancesToReset TABLE
(
  UserId uniqueidentifier,
  PriorBalance int,
  NewBalance int
)

INSERT INTO @BalancesToReset( UserId, PriorBalance, NewBalance )
   SELECT UserId, AvailableMoney, dbo.ufn_ComputeNewBalance(some params)
   FROM UserBalances WHERE SomeIncorrectConditionHere

UPDATE UserBalances SET 
   AvailableMoney = BR.NewBalance FROM
      @BalancesToReset BR INNER JOIN UserBalances
      ON UserBalances.UserId = BR.UserId;

INSERT INTO UserBalanceChanges( PriorBalance, NewBalance )
   SELECT PriorBalance, NewBalance
   FROM @BalancesToReset WHERE NewBalance < PriorBalance;

並且此程式碼將與相關程式碼同時執行。該程式碼僅適用於INSERT from SELECT一小部分使用者餘額,而且只是偶爾,但由於部分中的條件不正確,SELECT它會經常選擇大部分餘額。無意中選擇的餘額NewBalance等於PriorBalance,因此最終INSERT from SELECT不會記錄更改。

UPDATE這段程式碼的中間部分會強制將許多AvailableMoney使用者的

引用自:https://dba.stackexchange.com/questions/56875