Sql-Server
在我的 SQL Azure 數據庫中觀察到失去更新的原因是什麼?
我有一個帶有下表的 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
使用者的