分散式交易聽起來很不錯,其實不然。它只是儘可能的降低資料不一致的可能性,並不能完全避免。從我的應用程式中來看,總數約5千萬的操作,錯了十幾個。當然,這個錯誤率完全可以忍受了。不能忍受的是當你的DB在cluster(叢集)當中,msdtc也會被作為一項資源出現,cluster的某些問題會詭異的導致msdtc不可用,問題排查起來是非常鬱悶的。大家都知道,作為大型系統,不太可能不用cluster,所以msdtc的問題會顯得很突出,伊給我的感覺實在是脆弱…… 事務這個東東用來保證非常critical的資料的一致性,是否使用事務,要看你的業務需求.但是.NET 2.0在分散式交易支援上面不夠彪悍,這使得某些情況下,你要犧牲一些東西,如:某些可以並存執行的部分,但是不能並存執行。System.Transactions中偶還沒發現支援並存執行事務的Transactioin對象,所以如果想讓代碼比較“傻瓜”,就不得不串列執行所有的操作步驟,當然無法獲得並存執行可並行步驟的快速響應。當然,系統的吞吐率可能因為串列提高了一點點,看你的需求了。 如果你不得不用分散式交易,那也得琢磨琢磨:1. 這步操作一定得在事務當中嗎?這步操作如果沒完成或者失敗了,值得復原整個事務嗎?難道沒有優雅的補償措施或者容錯措施?2. 分散式交易涉及到的點,必須的這麼多?必須得即時的操作這一大串?不能通過通知類操作去精簡掉某些點?3. 在發起分散式交易之後,你是不是做了事務無關的操作,儘管這些操作跟事務無關?(如,讀取資料、計算、等使用者返回訊息、等其他模組的調用返回等等)要知道事務應該儘快結束。4. 你沒有把一些讀操作也算在事務裡面了吧?這是很容易犯的錯誤,你在事務中Enlist了一個select 操作。5. 你的操作,某些步驟可以等全部操作完成之後再執行.這類操作具有明顯的通知類特點。通知類操作是說,我給你一個通知,並且我保證通知到了你;你必須吃下這個通知,並且保證處理成功,但是你不必我一通知你你就處理。這樣的操作很明顯可以用另外一個任務去搞。
TransactionScope在文檔中宣稱只在“必要”情況下才提升事務層級(多資料庫時才使用分散式交易,如果是同一個資料庫,最好使用SqlTransaction),但是事實上不是這樣。在TransactionScope內,只要你用不同的SqlConnection對象操作DB一次以上(不管你的目標是不是同一個執行個體、同一個庫),都會提升事務層級到分散式交易。很猥瑣吧?當然,從SqlClient目前實現上可以理解這個事情:我們知道,在事務提交或復原前,事務拿著的串連是不被釋放的,不管你在代碼中有沒有調用Close或Dispose(這裡要稍微提一下Dispose模式。對這兩個方法的調用沒有本質上的不同)。
所以就算是用相同的連接字串產生的倆SqlConnection,其內部引用的串連池中的internal串連也不是一個,也就是說,從DB的角度看,是兩個不同的Session.不同的Session不能共用一個本地事務,因為串連不是同一個,只能通過分散式交易管理。但是微軟不能最佳化它嗎?能。連接字串完全可以解析成一堆field,就像SqlConnectionStringBuilder那樣,然後通過比較前面一個internal的串連中資訊來判斷是重用前一個串連還是從串連池裡面拿一個新的。當然還有一個坎,
有任何一點不同的連接字串都會產生不同的串連池,就算是只多一個空格也是這樣,串連池可能也得改造改造。但是微軟沒有做這種改造,那我們就得自己做了——執行相同執行個體相同庫上操作都在一個SqlConnection上執行好了。 值得一提的是,DbConnection對象有一個EnlistTransaction方法,它給了我們手工分散式交易的機會。現在我們設計東西,大多數情況下是採用從上到下的設計,最後才會關心persistent方式。這個時候DbConnection.EnlistTransaction就顯得特別的有用。相比之下,TransactionScope過於死板。而且,手工控制Enlist給了我們並存執行一堆操作,最後大家一起提交或復原的可能(只要你的需求能忍受稍高的錯誤率。分散式交易的兩階段交易認可方式理論上註定這個所謂的“稍高”不會比直接使用分散式交易高多少。) 最後稍微提提MSDTC的配置和DTCPing工具。MSDTC配置主要是“安全配置”,並且要在應用程式伺服器(應用程式伺服器也是分散式交易的一個節點)、所有相關的DB伺服器上進行配置。怎麼配置google一把,到處都是。配置完之後,應該把DTCPing拷貝到所有這些伺服器上,並且進行雙向的連通性確認。雙向是說A->B得通,B->A也得通。注意DTCPing的用法,上面寫的明明白白,就不多說了。如果某台機器不能解析別的機器名,修改一把hosts檔案就OK了。
2) SqlTransaction 對象用Connection對象的BeginTransaction方法產生 SqlTransaction對象指定SqlCommand對象的SqlTransaction對象SqlTransaction對象的Commit() 方法和Rollback方法 在提交或復原
SqlTransaction 時,應始終使用 Try/Catch 進行異常處理。如果串連終止或事務已在伺服器上復原,則 Commit 和 Rollback 都會產生 InvalidOperationException。
成員函數Save(string rollbackstring) 在事務中建立儲存點(它可用於復原事務的一部分),並指定儲存點名稱。如 SqlTransaction.save("NoUpdate") // 建立復原點。 SqlTransaction.Rollback("NoUpdate")//復原到復原點
public void RunSqlTransaction(string myConnString)
{
SqlConnection myConnection = new SqlConnection(myConnString);
myConnection.Open();
SqlCommand myCommand = new SqlCommand();
SqlTransaction myTrans;
// Start a local transaction
myTrans = myConnection.BeginTransaction(IsolationLevel.ReadCommitted,"SampleTransaction");
// Must assign both transaction object and connection
// to Command object for a pending local transaction
myCommand.Connection = myConnection;
myCommand.Transaction = myTrans;
try
{
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription)
VALUES (100, 'Description')";
myCommand.ExecuteNonQuery();
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription)
VALUES (101, 'Description')";
myCommand.ExecuteNonQuery();
myTrans.Commit();
Console.WriteLine("Both records are written to database.");
}
catch(Exception e)
{
myTrans.Rollback("SampleTransaction");
Console.WriteLine(e.ToString());
Console.WriteLine("Neither record was written to database.");
}
finally
{
myConnection.Close();
}
}一.資料庫事務的ACID屬性
交易處理可以確保除非事務性單元內的所有操作都成功完成,否則不會永久更新面向資料的資源。通過將一組相關操作組合為一個要麼全部成功要麼全部失敗的單元,可以簡化錯誤恢複並使應用程式更加可靠。一個邏輯工作單元要成為事務,必須滿足所謂的ACID(原子性、一致性、隔離性和持久性)屬性:
原子性
事務必須是原子工作單元;對於其資料修改,要麼全都執行,要麼全都不執行。通常,與某個事務關聯的操作具有共同的目標,並且是相互依賴的。如果系統只執行這些操作的一個子集,則可能會破壞事務的總體目標。原子性消除了系統處理操作子集的可能性。
一致性
事務在完成時,必須使所有的資料都保持一致狀態。在相關資料庫中,所有規則都必須應用於事務的修改,以保持所有資料的完整性。事務結束時,所有的內部資料結構(如 B 樹索引或雙向鏈表)都必須是正確的。某些維護一致性的責任由應用程式開發人員承擔,他們必須確保應用程式已強制所有已知的完整性條件約束。例如,當開發用於轉帳的應用程式時,應避免在轉帳過程中任意移動小數點。
隔離性
由並發事務所作的修改必須與任何其它並發事務所作的修改隔離。事務查看資料時資料所處的狀態,要麼是另一併發事務修改它之前的狀態,要麼是另一事務修改它之後的狀態,事務不會查看中間狀態的資料。這稱為可串列性,因為它能夠重新裝載起始資料,並且重播一系列事務,以使資料結束時的狀態與原始事務執行的狀態相同。當事務可序列化時將獲得最高的隔離等級。在此層級上,從一組可並存執行的事務獲得的結果與通過連續運行每個事務所獲得的結果相同。由於高度隔離會限制可並存執行的事務數,所以一些應用程式降低隔離等級以換取更大的輸送量。
持久性
事務完成之後,它對於系統的影響是永久性的。該修改即使出現致命的系統故障也將一直保持。
DBMS的責任和我們的任務
企業級的資料庫管理系統(DBMS)都有責任提供一種保證事務的物理完整性的機制。就常用的SQL Server2000系統而言,它具備鎖定裝置隔離事務、記錄裝置保證事務持久性等機制。因此,我們不必關心資料庫事務的物理完整性,而應該關注在什麼情況下使用資料庫事務、事務對效能的影響,如何使用事務等等。
隔離等級的概念
企業級的資料庫每一秒鐘都可能應付成千上萬的並發訪問,因而帶來了並發控制的問題。由資料庫理論可知,由於並發訪問,在不可預料的時刻可能引發如下幾個可以預料的問題:
髒讀:包含未提交資料的讀取。例如,事務1 更改了某行。事務2 在事務1 提交更改之前讀取已更改的行。如果事務1 復原更改,則事務2 便讀取了邏輯上從未存在過的行。
不可重複讀取:當某個事務不止一次讀取同一行,並且一個單獨的事務在兩次(或多次)讀取之間修改該行時,因為在同一個事務內的多次讀取之間修改了該行,所以每次讀取都產生不同值,從而引發不一致問題。
幻象:通過一個任務,在以前由另一個尚未提交其事務的任務讀取的行的範圍中插入新行或刪除現有行。帶有未提交事務的任務由於該範圍中行數的更改而無法重複其原始讀取。
如你所想,這些情況發生的根本原因都是因為在並發訪問的時候,沒有一個機制避免交叉存取所造成的。而隔離等級的設定,正是為了避免這些情況的發生。事務準備接受不一致資料的層級稱為隔離等級。隔離等級是一個事務必須與其它事務進行隔離的程度。較低的隔離等級可以增加並發,但代價是降低資料的正確性。相反,較高的隔離等級可以確保資料的正確性,但可能對並發產生負面影響。
根據隔離等級的不同,DBMS為並行訪問提供不同的互斥保證。在SQL Server資料庫中,提供四種隔離等級:未提交讀、提交讀、可重複讀、可串列讀。這四種隔離等級可以不同程度地保證並發的資料完整性:
| 隔離等級 |
髒 讀 |
不可重複讀取 |
幻 像 |
| 未提交讀 |
是 |
是 |
是 |
| 提交讀 |
否 |
是 |
是 |
| 可重複讀 |
否 |
否 |
是 |
| 可串列讀 |
否 |
否 |
否 |
可以看出,“可串列讀”提供了最進階別的隔離,這時並發事務的執行結果將與串列執行的完全一致。如前所述,最進階別的隔離也就意味著最低程度的並發,因此,在此隔離等級下,資料庫的服務效率事實上是比較低的。儘管可串列性對於事務確保資料庫中的資料在所有時間內的正確性相當重要,然而許多事務並不總是要求完全的隔離。例如,多個作者工作於同一本書的不同章節。新章節可以在任意時候提交到項目中。但是,對於已經編輯過的章節,沒有編輯人員的批准,作者不能對此章節進行任何更改。這樣,儘管有未編輯的新章節,但編輯人員仍可以確保在任意時間該書籍項目的正確性。編輯人員可以查看以前編輯的章節以及最近提交的章節。這樣,其它的幾種隔離等級也有其存在的意義。
在.net架構中,事務的隔離等級是由枚舉System.Data.IsolationLevel所定義的:
[Flags] [Serializable] public enum IsolationLevel |
其成員及相應的含義如下:
| 成 員 |
含 義 |
| Chaos |
無法改寫隔離等級更高的事務中的暫止的變更。 |
| ReadCommitted |
在正在讀取資料時保持共用鎖定,以避免髒讀,但是在事務結束之前可以更改資料,從而導致不可重複的讀取或幻像資料。 |
| ReadUncommitted |
可以進行髒讀,意思是說,不發布共用鎖定,也不接受獨佔鎖。 |
| RepeatableRead |
在查詢中使用的所有資料上放置鎖,以防止其他使用者更新這些資料。防止不可重複的讀取,但是仍可以有幻像行。 |
| Serializable |
在DataSet上放置範圍鎖,以防止在事務完成之前由其他使用者更新行或向資料集中插入行。 |
| Unspecified |
正在使用與指定隔離等級不同的隔離等級,但是無法確定該層級。 |
顯而意見,資料庫的四個隔離等級在這裡都有映射。
預設的情況下,SQL Server使用ReadCommitted(提交讀)隔離等級。
關於隔離等級的最後一點就是如果你在事務執行的過程中改變了隔離等級,那麼後面的命名都在最新的隔離等級下執行——隔離等級的改變是立即生效的。有了這一點,你可以在你的事務中更靈活地使用隔離等級從而達到更高的效率和並發安全性。