2017-02-13 107 views
0

我想使用樂觀併發與TransactionScope。以下是我想出迄今代碼:TransactionScope和樂觀併發

var options = new TransactionOptions {IsolationLevel = IsolationLevel.ReadCommitted}; 
using (var scope = new TransactionScope(TransactionScopeOption.Required, options)) 
{ 
    using (var connection = new SqlConnection(_connectionString)) 
    { 
     // ... execute some sql code here 

     // bump up version 
     var version = connection.ExecuteScalar<DateTime>(@" 
      DECLARE @version datetime2 = SYSUTCDATETIME(); 
      UPDATE [Something].[Test] 
       SET [Version] = @version 
       WHERE Id = @Id 
      SELECT @version 
     ", new {Id = id}); 

     // ... execute more sql code here 

     // check if version has not changed since bump up 
     // NOTE: version is global for the whole application, not per row basis 
     var newVersion = connection.ExecuteScalar<DateTime>("SELECT MAX([Version]) FROM [Something].[Test]"); 
     if (newVersion == version) scope.Complete(); // looks fine, mark as completed 
    } 
} // what about changes between scope.Complete() and this line? 

不幸的代碼有一個嚴重的問題。在版本檢查和事務提交之間,數據庫可能會發生一些變化。這是一個標準的time of check to time of use錯誤。我可以看到解決它的唯一方法是將版本檢查和事務提交作爲單個命令執行。

是否有可能使用TransactionScope執行一些SQL代碼以及事務提交?如果沒有,那麼還有什麼其他解決方案可以使

EDIT1: 版本需要是每個應用程序,而不是每行。

編輯2: 我可以使用可序列化的隔離級別,但它不是一個選項,因爲這會導致性能問題。

+0

對我來說似乎最簡單的解決方案是將所有這些SQL代碼移動到存儲過程並將其放入事務中。無論如何,誠實地將sql移出應用程序是一個好主意,可以創建分層體系結構並將代碼與數據實現分離。 –

+0

@SeanLange此更改需要在現有的系統上進行,並且將整個邏輯移動到存儲過程不是一種選擇。 –

回答

3

不幸的是,這段代碼有一個嚴重的問題。在版本檢查和事務提交之間,數據庫可能會發生一些變化。

這根本不是真的。默認構造函數TransactionSope(如您在發佈的代碼中)使用的隔離級別爲Serializable。雖然這可以說是problem,但確實有preventing any modification to any row you queried的副作用。這是悲觀的併發控制。

儘管如此,您應該使用樂觀併發控制。您需要使用a TransactionScope constructor that accepts TransactionOptions並通過選項使用更體面的isolation level,例如。閱讀承諾。至於行版本,使用一個簡單的int,隨着應用程序中的每次寫入而增加。

UPDATE [Something].[Test] 
SET ..., [Version] = @new_version 
OUTPUT Inserted.Id 
WHERE Id = @Id AND [Version] = @old_version; 

@old_version是您在查詢時在記錄中找到的版本。 @new_version@old_version+1。如果該行在讀取後被修改,那麼WHERE將不會找到它,並且結果將爲空集,因此您知道必須讀取,刷新並再次嘗試(發生衝突)。這是a well known optimistic control scheme

注意,儘管樂觀併發控制在讀取和寫入跨越兩個不同事務(例如,在T1中讀取,在用戶窗體中顯示,然後在T2中寫入)中更有意義。當讀取和寫入發生在相同的事務中時,最好將它留給引擎。我只需使用snapshot isolation level即可解決問題。

+0

說實話,在真正的代碼中,我將隔離級別更改爲ReadCommitted。雖然沒有包含在這個示例中。將在此刻編輯。 –

+0

關於您的解決方案。看起來我沒有很好地描述我的問題。我需要有一個全球計數器,而不是每行。我不能使用可序列化,因爲將會有大量的讀取操作,並且在每次寫入時阻塞幾個表不是一個選項。稍後將改變問題描述。 –

+0

'我需要有一個全球性的櫃檯,而不是每行basis':下併發保持這樣的計數器,基本上不可能* *。每行計數器有什麼問題,爲什麼你不能使用它? –