轉載至:http://www.cnblogs.com/zhenyulu/articles/330494.html
ReadUnCommitted是最低的隔離等級,這個層級的隔離允許讀入別人尚未提交的髒資料,除此之外,在這種交易隔離等級下還存在不可重複讀取的問題。
ReadCommitted是許多資料庫的預設層級,這個隔離等級上,不會出現讀取未提交的資料問題,但仍然無法避免不可重複讀取(包括幻影讀)的問題。當你的系統對並發控制的要求非常嚴格時,這種預設的隔離等級可能無法提供資料有效保護,但對於決大多數應用來講,這種隔離等級就夠用了。
我們使用下面的實驗來進行測試:
首先配置SQL Server 2000資料庫,附加DBApp資料庫。然後在Visual Studio .net中建立一管理主控台應用程式,添加必要的命名空間引用:
using System;using System.Data;using System.Data.SqlClient;using System.Configuration;
然後建立兩個資料庫連結,並分別採用不同的交易隔離等級:
private static SqlConnection conn1;private static SqlConnection conn2;private static SqlTransaction tx1;private static SqlTransaction tx2;private static void Setup(){conn1 = new SqlConnection(connectionString);conn1.Open();tx1 = conn1.BeginTransaction(IsolationLevel.ReadUncommitted);conn2 = new SqlConnection(connectionString);conn2.Open();tx2 = conn2.BeginTransaction(IsolationLevel.ReadCommitted);}
其中事務1允許讀入未提交的資料,而事務2隻允許讀入已提交資料。
在主程式中,我們類比兩個人先後的不同操作,以產生並發一致性問題:
public static void Main(){Setup();try{ReadUnCommittedDataByTransaction1();UnCommittedUpdateByTransaction2();ReadUnCommittedDataByTransaction1();tx2.Rollback();Console.WriteLine("\n-- Transaction 2 rollbacked!\n");ReadUnCommittedDataByTransaction1();tx1.Rollback();}catch{……}}
第一步,使用ReadUnCommittedDataByTransaction1方法利用事務1從資料庫中讀入id值為1的學生資訊。此時的資訊是資料庫的初始資訊。
第二步,調用UnCommittedUpdateByTransaction2方法,從第2個事務中發送一UPDATE命令更新資料庫,但尚未提交。
第三步,再次調用ReadUnCommittedDataByTransaction1,從事務1中讀取資料庫資料,你會發現由事務2發布的尚未提交的更新被事務1讀取出來(ReadUnCommitted)。
第四步,事務2放棄提交,復原事務tx2.Rollback();。
第五步,再次調用ReadUnCommittedDataByTransaction1();,讀取資料庫中的資料,此次是已經復原後的資料。
程式運行結果如下:
-- Read age from database:Age:20-- Run an uncommitted command:UPDATE student SET age=30 WHERE id=1-- Read age from database:Age:30-- Transaction 2 rollbacked!-- Read age from database:Age:20
關於ReadUnCommittedDataByTransaction1()與UnCommittedUpdateByTransaction2()的方法定義如下:
private static void UnCommittedUpdateByTransaction2()
{
string command = "UPDATE student SET age=30 WHERE id=1";
Console.WriteLine("\n-- Run an uncommitted command:\n{0}\n", command);
SqlCommand cmd = new SqlCommand(command, conn2);
cmd.Transaction = tx2;
cmd.ExecuteNonQuery();
}
private static void ReadUnCommittedDataByTransaction1()
{
Console.WriteLine("-- Read age from database:");
SqlCommand cmd = new SqlCommand("SELECT age FROM student WHERE id = 1", conn1);
cmd.Transaction = tx1;
try
{
int age = (int)cmd.ExecuteScalar();
Console.WriteLine("Age:{0}", age);
}
catch(SqlException e)
{
Console.WriteLine(e.Message);
}
}
從上面的實驗可以看出,在ReadUnCommitted隔離等級下,程式可能讀入未提交的資料,但此隔離等級對資料庫資源鎖定最少。
本實驗的完整代碼可以從"SampleCode\Chapter 2\Lab 2-6"下找到。
讓我們再來做一個實驗(這個實驗要求動作要快的,否則可能看不到預期效果)。首先修改上面代碼中的Setup()方法代碼,將
tx1 = conn1.BeginTransaction(IsolationLevel.ReadUncommitted);
改為:
tx1 = conn1.BeginTransaction(IsolationLevel.ReadCommitted);
再次運行代碼,你會發現程式執行到第三步就不動了,如果你有足夠的耐心等下去的話,你會看到"逾時時間已到。在操作完成之前逾時時間已過或伺服器未響應。"的一條提示,這條提示究竟是什麼意思呢?讓我們探察一下究竟發生了什麼:
第一步,在做這個實驗之前,先將SQL Server 2000的企業管理器開啟,然後再將SQL Server事件探察器開啟並處於探察狀態。
第二步,運行改動後的程式,程式執行到一半就暫停了。此時迅速切換到企業管理器介面,右擊"管理"下面的"當前活動",選擇"重新整理"(整個過程應在大約15秒內完成即可, 2-8所示),我們便得到了資料庫當前進程的一個快照。
圖 2-8 使用企業管理器查看當前活動
我們發現此時進程出現了阻塞,被阻塞者是52號進程,而阻塞者是53號進程。也就是說53號進程的工作妨礙了52號進程繼續工作。(不同實驗時進程號可能各不相同)
第三步,為了進一步查明原因真相,我們切換到事件探察器視窗,看看這兩個進程都是幹什麼的。 2-9所示,事件探察器顯示了這兩個進程的詳細資料。我們可以看出,52號進程對應我們的事務1,53號進程對應我們的事務2。事務2執行了UPDATE命令,但尚未提交,此時事務1去讀尚未提交的資料便被阻塞住。我們可以看出52號進程是被阻塞者。
此時如果事務2完成提交,52號進程便可以停止等待,得到需要的結果。然而我們的程式沒有提交資料,因此52號進程就要無限等下去。所幸SQL Server 2000檢測到事務2的已耗用時間過長(這就是上面的錯誤提示"逾時時間已到。在操作完成之前逾時時間已過或伺服器未響應。"),所以將事務2復原以釋放佔用的資源。資源被釋放後,52號進程便得以執行。
圖 2-9 事件探察器探察阻塞命令
第四步,瞭解了上面發生的事情後,我們現在可以深入討論一下共用鎖定和排它鎖的使用方式了。重新回到企業管理器介面,讓我們查看一下兩個進程各佔用了什麼資源。從圖 2-10中我們可以看出,53號進程(事務2)在執行更新命令前對相應的鍵加上了排它鎖(X鎖),按照前文提到的1級封鎖協議,該排它鎖只有在事務2提交或復原後才釋放。現在52號進程(事務1)要去讀同一行資料,按照2級封鎖協議,它要首先對該行加共用鎖定,然而 該行資料已經被事務2加上了排它鎖,因此事務1隻能處於等待狀態,等待排它鎖被釋放。因此我們就看到了前面的"阻塞"問題。
圖 2-10 進程執行寫操作前首先加了排它鎖
圖 2-11 進程讀操作前要加共用鎖定,但被阻塞
當事務1的交易隔離等級是ReadUnCommitted時,讀資料是不加鎖的,因此排它鎖對ReadUnCommitted不起作用,進程也不會被阻塞,不過確讀到了"髒"資料。