2013-07-21 14 views

回答

25

我已經使用TIMESTAMP數據類型(ROWVERSION,SQL2005 +),以避免lost update problem

丟失的更新問題:一個第二事務的頂部寫入的 第二值的數據項(數據)第一個值由第一個 併發事務寫入,並且第一個值會丟失到其他同時運行的 事務中,這些事務需要按其優先級以 讀取第一個值。讀取錯誤值 的交易以錯誤的結果結束。

舉例:lost update

t : User 1 read payment order (PO) #1 (amount 1000) 
t+1: User 2 read payment order (PO) #1 (amount 1000) 

t+2: User 1 change the amount for PO #1 to 1005 
t+3: User 2 change the amount for PO #1 to 1009 (change make by User 1 is lost because is overwritten by change make by User 2) 
t+4: The amount is **1009**. 

示例:如何防止lost update

t : User 1 read payment order (PO) #1 (amount 1000, timestamp 0x00000000000007D1) 
t+1: User 2 read payment order (PO) #1 (amount 1000, timestamp 0x00000000000007D1) 

t+2: User 1 change the amount for PO #1 to 1005 and it checks if row has the same `timestamp` (column `RW` in this case; 0x00000000000007D1). The check succeeds and the change is `COMMIT`ed. This will change, also, the timestamp (column 'RW'). The new timestamp is 0x00000000000007D4. 
t+3: User 2 change the amount for PO #1 to 1009 and it checks if row has the same `timestamp` (column `RW` in this case; 0x00000000000007D4). The checks fails because the initial timestamp (@rw=0x00000000000007D1) is <> than current timestamp (column `RW`=0x00000000000007D4). An error is raised the catch block "intercepts" the error and this transaction is cancelled (`ROLLBACK`). 
t+4: The amount {remains|is} **1005**. 

示例:How to prevent the lost update(警告T-SQL腳本:你必須使用兩個SSMS窗口/兩節)

CREATE DATABASE TestRowVersion; 
GO 
USE TestRowVersion; 
GO 

CREATE TABLE dbo.PaymentOrder(
    PaymentOrderID INT IDENTITY(1,1) PRIMARY KEY, 
    PaymentOrderDate DATE NOT NULL, 
    Amount NUMERIC(18,2) NOT NULL, 
    CreateDate DATETIME NOT NULL DEFAULT (GETDATE()), 
    UpdateDate DATETIME NULL, 
    RW ROWVERSION NOT NULL -- R[ow] V[ersion] 
); 
GO 

INSERT dbo.PaymentOrder (PaymentOrderDate,Amount) 
VALUES ('2013-07-21',1000); 
INSERT dbo.PaymentOrder (PaymentOrderDate,Amount) 
VALUES ('2013-07-22',2000); 
INSERT dbo.PaymentOrder (PaymentOrderDate,Amount) 
VALUES ('2013-07-23',3000); 
GO 

SELECT * FROM dbo.PaymentOrder; 
/* 
PaymentOrderID PaymentOrderDate Amount CreateDate    UpdateDate RW 
-------------- ---------------- ------- ----------------------- ---------- ------------------ 
1    2013-07-21  1000.00 2013-07-21 09:35:38.750 NULL  0x00000000000007D1 
2    2013-07-22  2000.00 2013-07-21 09:35:38.750 NULL  0x00000000000007D2 
3    2013-07-23  3000.00 2013-07-21 09:35:38.750 NULL  0x00000000000007D3 
*/ 
GO 

-- User 1 (SQL Server Management Studio/SSMS window #1) 
    -- [t] Client app, user 1: it loads first PO 
    SET NOCOUNT ON; 
    GO 
    DECLARE @PaymentOrderID INT=1; -- parameter 

    SELECT po.PaymentOrderID, 
      po.PaymentOrderDate, 
      po.Amount, 
      po.RW 
    FROM dbo.PaymentOrder po 
    WHERE [email protected]; 

    -- Client app, user 1: during 15 seconds it edit the amount from 1000.00 to 1005.00 
    WAITFOR DELAY '00:00:15'; 
    GO 

    -- [t+2] Client app, user 1: it sends this change (new amount) from client app to database server 
    -- with the old row version value 
    DECLARE @PaymentOrderID INT=1;    -- parameter 
    DECLARE @rw BINARY(8)=0x00000000000007D1; -- parameter 
    DECLARE @NewAmount NUMERIC(18,2)=1005.00; -- parameter 

    BEGIN TRY 
     BEGIN TRANSACTION 
      UPDATE dbo.PaymentOrder 
      SET  [email protected] 
      WHERE [email protected] 
      AND  [email protected]; -- it checks the timestamp (current timestamp versus original timestamp) 
      DECLARE @rowcount [email protected]@ROWCOUNT; -- How many rows were affected by the last statement (UPDATE in this case) ? 
      SELECT @rowcount AS [@@ROWCOUNT]; 
      IF @rowcount<>1 
       RAISERROR('Lost update or row deleted.', 16, 1); 
     COMMIT TRANSACTION 
     PRINT 'UPDATE succeded'; 
    END TRY 
    BEGIN CATCH 
     IF @@TRANCOUNT>0 
      ROLLBACK; 

     DECLARE @ErrMsg NVARCHAR(2002); 
     SET @ErrMsg=ERROR_MESSAGE(); 
     RAISERROR(@ErrMsg,16,1); 
    END CATCH; 
    GO 

    -- [t+4] Client app, user 1: it reloads first PO 
    DECLARE @PaymentOrderID INT=1; -- parameter 

    SELECT po.PaymentOrderID, 
      po.PaymentOrderDate, 
      po.Amount, 
      po.RW 
    FROM dbo.PaymentOrder po 
    WHERE [email protected]; 
    GO 

-- User 2 (warning: run this script in another SQL Server Management Studio window: File > New Database Engine Query !; SSMS window #2) 
    -- [t+1] Client app, user 1: it loads first PO 
    SET NOCOUNT ON; 
    GO 
    DECLARE @PaymentOrderID INT=1; -- parameter 

    SELECT po.PaymentOrderID, 
      po.PaymentOrderDate, 
      po.Amount, 
      po.RW 
    FROM dbo.PaymentOrder po 
    WHERE [email protected]; 

    -- Client app, user 1: during 20 seconds it edit the amount from 1000.00 to 1005.00 
    WAITFOR DELAY '00:00:20'; 
    GO 

    -- [t+4] Client app, user 1: it sends this change (new amout) from client app to database server 
    -- with the old row version value 
    DECLARE @PaymentOrderID INT=1;    -- parameter 
    DECLARE @rw BINARY(8)=0x00000000000007D1; -- parameter 
    DECLARE @NewAmount NUMERIC(18,2)=1009.00; -- parameter 

    BEGIN TRY 
     BEGIN TRANSACTION 
      UPDATE dbo.PaymentOrder 
      SET  [email protected] 
      WHERE [email protected] 
      AND  [email protected]; -- it checks the timestamp (current timestamp versus original timestamp) 
      DECLARE @rowcount [email protected]@ROWCOUNT; -- How many rows were affected by the last statement (UPDATE in this case) ? 
      SELECT @rowcount AS [@@ROWCOUNT]; 
      IF @rowcount<>1 
       RAISERROR('Lost update or row deleted.', 16, 1); 
     COMMIT TRANSACTION 
     PRINT 'UPDATE succeded'; 
    END TRY 
    BEGIN CATCH 
     IF @@TRANCOUNT>0 
      ROLLBACK; 

     DECLARE @ErrMsg NVARCHAR(2002); 
     SET @ErrMsg=ERROR_MESSAGE(); 
     RAISERROR(@ErrMsg,16,1); 
    END CATCH; 
    GO 

    -- [t+4] Client app, user 1: it reloads first PO 
    DECLARE @PaymentOrderID INT=1; -- parameter 

    SELECT po.PaymentOrderID, 
      po.PaymentOrderDate, 
      po.Amount, 
      po.RW 
    FROM dbo.PaymentOrder po 
    WHERE [email protected]; 
    GO 

結果對用戶1(金額1000 - > 1005):

PaymentOrderID PaymentOrderDate Amount         RW 
-------------- ---------------- --------------------------------------- ------------------ 
1    2013-07-21  1000.00         0x00000000000007D1 

@@ROWCOUNT <- Timestamp check succeeds 
----------- 
1 

UPDATE succeded 
PaymentOrderID PaymentOrderDate Amount         RW 
-------------- ---------------- --------------------------------------- ------------------ 
1    2013-07-21  1005.00         0x00000000000007D4 

結果用戶2(金額1000 - > 1009):

PaymentOrderID PaymentOrderDate Amount         RW 
-------------- ---------------- --------------------------------------- ------------------ 
1    2013-07-21  1000.00         0x00000000000007D1 

@@ROWCOUNT <- Timestamp check fails 
----------- 
0 

Msg 50000, Level 16, State 1, Line 27 
Lost update. 
PaymentOrderID PaymentOrderDate Amount         RW 
-------------- ---------------- --------------------------------------- ------------------ 
1    2013-07-21  1005.00         0x00000000000007D4 

注:改變了錯誤消息RAISERROR('Lost update or row deleted.', 16, 1);

+0

修改日期不會少數,以避免丟失更新問題? – Imad

+0

@Imad:這可能是一個解決方案,但開發人員應該小心更新此列('[Modified Date]')。 'timestamp' /'rowversion'列具有很大的值,當連接執行'UPDATE'語句時,SQL Server數據庫引擎會自動更新它 –

23

讓我們舉一個銷售訂單表來舉例說明什麼時間戳。

create table saleorder (ordernumber int, amount int, timestamp); 
insert into saleorder (ordernumber, amount) values (1, 100), (2, 100), (3, 200); 
select * from saleorder 

注意timestamp列中的數據。 (SQL Server 2005)說:這個(即時間戳)跟蹤數據庫中的相對時間,而不是與時鐘關聯的實際時間......每次修改或插入帶有時間戳列的行時,遞增的數據庫時間戳值被插入到時間戳列中。

讓我們來看看數據如何看起來像:

ordernumber amount timestamp 
1   100  0x00000000000007D1 
2   100  0x00000000000007D2 
3   200  0x00000000000007D3 

好吧。訂單1是先添加的,訂單3是最後輸入的。如果我們要更新訂單1的數量會發生什麼?

update saleorder set amount = 200 where ordernumber = 1 
select * from saleorder 

啊,請注意,訂單1的時間戳現在是0x7D4(Decimal 2004)。相對於其他行,我們知道最近更新了1號訂單。但是,更重要的是,時間戳的價值來自併發寫入時。

ordernumber amount timestamp 
1   200  0x00000000000007D4 
2   100  0x00000000000007D2 
3   200  0x00000000000007D3 

比方說,約翰和瑪麗都在銷售訂單3,使用.NET開發的Web應用程序。約翰拉起訂單並進行更改。 John尚未保存數據。瑪麗拉同樣的順序,並改變它。約翰先救。 Mary試圖保存數據。 .NET應用程序可以首先查看瑪麗拉的時間戳是否仍然與數據庫對於訂單3的時間戳相同。

如果Mary用3號訂單拉的時間戳現在不同(因爲John保存的數據和時間戳自動得到更改),.NET應用程序可以提醒Mary並要求她刷新屏幕上的記錄以查看最新更改(或者可能突出顯示屏幕上的更改)。

將時間戳考慮爲行版本。有趣的是,SQL Server的最新版本使用rowversion數據類型,它與timestamp數據類型是同義的。 Documentation of rowversion(SQL Server 2012)有一些有趣的例子。

+0

訂單編號1的金額值需要在第二個輸出列表中更改爲200。 – ulty4life

+0

@ ulty4life - 敏銳的眼睛!我已經做了更新。感謝您的改進。 – zedfoxus

+0

完整答案,謝謝:)先生:) – Imad

6

我已經使用timestamp列來跟蹤數據更改的時間,特別是需要同步到一個或多個移動應用程序的數據。您可以使用timestamp列僅返回自某個時間點以來發生更改的行(通過提供前一個時間戳)。