2

當使用客戶機框架,ORM或類似的構建查詢(不支持查詢提示,如WITH(NOLOCK))時,爲各個事務實現不同隔離級別的最佳方式是什麼? ?假設一個應用程序使用ReadUncommitted級別進行一些複雜和長時間運行的查詢(瞭解相關風險),並且它應該與NHibernate一起運行並且它的查詢條件(或QueryOver/LINQ,只是沒有字符串級聯!)。僅在單個ADO.NET事務中更改隔離級別

NHibernate不支持with(nolock)提示(除非使用本機SQL,目前在許多情況下使用)。

因此,爲了替換本機SQL字符串及其繁瑣的構建代碼,我想使用IsolationLevel.ReadUncommitted事務來替換'with(nolock)'。

但是即使在提交/回滾之後,連接仍保持在更改的隔離級別中,從而在新級別運行所有內容。即使在connection.Close()之後,它也會返回到連接池並在更改後的隔離級別重用。

我最初注意到了這一點,因爲我測試了打開與快照隔離級別的連接併發送一個簡單的查詢,以在數據庫上啓用快照模式時禁用讀未提交(一般情況下無法輕鬆切換到快照)。測試數據庫禁用了快照模式,所以我得到一個異常並在catch塊中將我的UseReadUncommitted變量設置爲'true',但後來來自「新」/重用連接的查詢仍然得到相同的異常。

我寫了一個簡單的類來封裝使用塊中的事務處理,自動重置.Dispose()中的IsolationLevel。但是這似乎會給數據庫帶來兩個額外的往返行程,而且我不確定更改的隔離級別是否可以在某些情況下「存活」處置並影響其他查詢。代碼在第一次嘗試中工作,它用於純ADO.NET連接/事務(如果好的話,我會爲NHibernate會話做另一次)。

有什麼建議嗎?

public class TransactionContainerTempIsolationLevel : IDisposable 
{ 
    public IsolationLevel OldIsolationLevel { get; private set; } 

    public IsolationLevel TempIsolationLevel { get; private set; } 

    public IDbTransaction Transaction { get; private set; } 

    private readonly IDbConnection _conn; 

    public TransactionContainerTempIsolationLevel(IDbConnection connection, IsolationLevel tempIsolationLevel) 
    { 
     _conn = connection; 
     LocalIsolationLevel = localIsolationLevel; 

     var checkTran = _conn.BeginTransaction(); 
     if (checkTran.IsolationLevel == tempIsolationLevel) 
     { 
      Transaction = checkTran; 
     } 
     else 
     { 
      OldIsolationLevel = checkTran.IsolationLevel; 
      checkTran.Dispose(); 
      Transaction = _conn.BeginTransaction(tempIsolationLevel); 
     } 
    } 

    public void Dispose() 
    { 
     Transaction.Dispose(); 
     if (OldIsolationLevel != TempIsolationLevel) 
     { 
      using (var restoreTran = _conn.BeginTransaction(OldIsolationLevel)) 
      { 
       restoreTran.Commit(); 
      } 
     } 
    } 
} 
+0

如果隔離級別不匹配,您似乎回滾事務。 – usr

+0

如果不應該回退,則主事務必須在Transaction屬性上顯式提交,就像任何事務一樣。 CheckTran只是獲取默認/當前隔離級別(如果默認級別匹配所需的隔離級別,它將用作主事務)。 –

+0

當我發表評論時,不確定我在想什麼。 – usr

回答

1

許多ORM不支持(動態)查詢提示的事實是一個恥辱。設置隔離級別或編寫包裝視圖和TVF是常用的解決方法。

但是,即使在提交/回滾之後,連接仍處於更改的隔離級別,在新級別運行所有內容。即使在 connection.Close()之後,它也會返回到連接池,並在更改後的隔離級別重用 。

Yes, this is a design flaw in SQL Server that was fixed in 2014.

測試數據庫已經快照模式下被禁止,所以我得到了一個異常

這正是我發現了這一點。一個令人不安的發現。

您發佈的代碼應該可以正常工作。正如你所說的那樣,它確實需要額外的往返數據庫。事實上,恢復舊的隔離級別會導致兩次往返。根據等級是否變化,總共我會在代碼中計算2或6次往返。

處理2014年<中的隔離級別泄漏的唯一方法是我總是對每次訪問數據庫都使用顯式事務。無論如何,在我看來,這在大多數情況下都是一個好主意。您通常需要選擇隔離級別並提供原子性。如果您可以轉換到SNAPSHOT(我建議),您可能希望在一個快照事務中運行多個查詢,以便所有查詢都能看到相同的數據。

In> = 2014新打開的連接的默認級別是READ COMMITTED。

我不明白爲什麼要恢復舊的隔離級別。看起來您的代碼必須處理打開連接時隔離級別是任意的這一事實。這意味着僅在某些代碼路徑中(而不是全部)恢復到舊的級別不會消除防禦任意級別的需要。如果您在所有路徑中恢復舊的級別,那麼您可以在任何地方使用單個事務,而無需任何恢復邏輯。

因此,您可以簡單地使用單個事務並讓級別泄漏。如果你真的想恢復,我會建議這個T-SQL:

SELECT isolation_level FROM sys.sessions WHERE session_id = @@SPID 
SET TRANSACTION ISOLATION LEVEL X 
BEGIN TRAN 

希望,這表現很好。這是一次往返。你需要再來一次才能恢復舊的水平。

如果你真的熱衷於性能,你可以保持你自己的簡單連接池與連接處於已知狀態。

或者,在每個隔離級別使用一個連接字符串。使用AppName使它們獨一無二。

如果你只是在RUC或RC下閱讀,你甚至不需要這樣的交易。您可能最終只需往返一次。

我建議您使用這些適合您的最簡單可能的解決方案。