一、基礎知識
1) 使用事務級別ReadUnCommited 會產生臟讀現像,意味着讀取到的為UnCommited(未提交)的數據。怎么理解呢?在使用該隔離級別的事務開始后。更新了數據庫某一行的數據,但是事務的工作量比較大,后續還有一大堆代碼還沒執行完呢。不巧的是有個哥們過來讀數據了,這個時候讀到的就是未提交的值,如果后繼工作一切正常,也沒什么影響。一旦后面的代碼執行中出錯,就會產生不一致的錯誤,適用於對事務極度自信的情況下,特點為可讀不可改。關於不可改需解釋一下,MS SQL中一條語句為最基本的執行單元了,如果一個事務中,對同條數據的更新語句未Commited的情況下,其它事務是需要等待的。
2) 使用事務級別ReadCommited 會消除臟讀現像,意味着讀取到的為Commited(已提交)的數據。在使用該隔離級別的事務開始后,除了該事務其它的查詢均會一直等待直到該事務提交。特點為不可讀不可改,
3) 使用事務級別Repeatable Read,可重復讀。它不會像ReadCommited一樣阻止Select查詢,但它會阻止Update語句。使用該隔離級別的事務開始后,會阻止其它事務更改查詢。但依然可以新增數據。
4) 使用事務級別Serializable 最嚴的一種了,與Repeatable Read相比就是在這種級別下不能新增數據。
5) 下表為簡單說明
其中增刪除改查為其它事務中對已在事務中的數據行進行增刪改查的可能行性,其中刪,改,查操作多個事務中的同一條數據。√指該隔離級別下操作可未被阻塞可立即響應。
隔離級別 |
增(其他事務) |
刪(其他事務) |
改(其他事務) |
查(其他事務) |
臟讀 |
讀提交 |
可重復讀 |
幻影讀 |
數據過期 |
ReadUnCommited |
√ |
× |
× |
√ |
√ |
× |
× |
√ |
√ |
ReadCommited |
√ |
× |
× |
× |
× |
√ |
× |
√ |
× |
Repeatable Read |
√ |
× |
× |
√ |
× |
√ |
√ |
√ |
√ |
Serializable |
× |
× |
× |
√ |
× |
√ |
√ |
× |
√ |
二、C#中的事務
1) ADO.NET中的事務與EF中的事務,它們是一類事物。可以說是同一個東西,均為封裝了 SQL語句中的 Begin Tran Commit等。
2) 分布式事務TranscationCope需引用System.data.Transcations.dll才能使用
3) 分布式事務與事務二者區別,主要區別在於前者用於一個數據連接中控制數據一致性,后者在多個數據庫連接中控制數據一致性,如果不能理解,可以記憶為一個數據庫使用事務用前者,多個數據庫中使用事務用后者,如果在一個數據連接中使用TranscationCope,可以把它為理解為簡化版的事務,因為它只有Complete方法。
4) 注意TranscationCope進行多事務協調時需安裝與設置MSDTC組件。
5) 並行事務:指在同一個DBConnection中啟用二個事務,這個不被ADO.NET與EF支持(目前),如果程序運行過程中出現了 Connection不支持並行事務的時候,檢查一下是否在一個數據庫連接中使用了二個事務。
三、事務的應用與驗證。
1) 下面依次驗證(末完成)
驗證時注意事項:連接字符串中設置 Pooling = False 既關掉數據連接池,特別是多次測試之后會有點小問題干擾結論。;如果需要驗證多個事務進行引發的異常還是阻塞進程,需要把超時時間設置為更多一些超過程序預計運行時間。EF建議大家使用EF6.0,在低版本的 EF 中不能指定SaveChanges時使用的事務。
驗證時使用3個線程向同一表中插入10條數據,如果順序插入,表明事務是順序執行,下面上代碼
class Program { static string connectionString = "server=127.0.0.1;database=xxxxxx;user=sa;password=xxxxxx;Pooling=False"; static System.Data.IsolationLevel tranLevel = System.Data.IsolationLevel.ReadUncommitted; static string commandText = "update [Locker] set [id] = 100" ; static void Main(string[] args) { ClearTestData(); tranLevel = System.Data.IsolationLevel.ReadUncommitted; commandText = "update [Locker] set [id] = 100"; Thread th1 = new Thread(new ThreadStart(() => { test(); })); Thread th2 = new Thread(new ThreadStart(() => { test(); })); Thread th3 = new Thread(new ThreadStart(() => { test(); })); th1.Name = "線程1"; th2.Name = "線程2"; th3.Name = "線程3"; th2.Start(); th3.Start(); th1.Start(); Console.ReadLine(); } static void test() { using (var db = new SupplierPortalEntities()) { var connection = db.Database.Connection; if (connection.State != ConnectionState.Open) { connection.Open(); } var tran = connection.BeginTransaction(tranLevel); try { db.Database.UseTransaction(tran); var command = commandText; db.Database.ExecuteSqlCommand(command); for (int i = 0; i < 20; i++) { Random r = new Random(); var t = new TranLocker(); t.Content = Thread.CurrentThread.Name; db.TranLocker.Add(t); Console.WriteLine("線程{0}加入數據 - lockid: {1}", Thread.CurrentThread.Name, t.LockID); Thread.Sleep(50); } db.SaveChanges(); //if (Thread.CurrentThread.Name.EndsWith("3")) //引發一個異常 . //{ // throw new Exception("3為故障碼"); //} tran.Commit(); Console.WriteLine("線程{0}:事務已提交", Thread.CurrentThread.Name); } catch (Exception er) { tran.Rollback(); Console.WriteLine("線程{0}:發生錯誤,事務已回滾:{1}", Thread.CurrentThread.Name, er.Message); } } } static void ClearTestData() { //清除數據 var connClearInsertDatas = new SqlConnection(connectionString); connClearInsertDatas.Open(); var cmd = connClearInsertDatas.CreateCommand(); cmd.CommandText = "delete from TranLocker"; cmd.ExecuteNonQuery(); connClearInsertDatas.Close(); } }