.NET:髒讀、不可重複讀和幻讀測試
背景
昨天才發現如果一條資料被A事務修改但是未提交,B事務如果採用“讀已提交”或更嚴格的隔離級別讀取改資料,會導致鎖等待,考慮到資料庫預設的隔離級別是“讀已提交”,在巢狀事務 + 子事務中有複雜的SQL查詢,很可能會出現死鎖,後面會給出巢狀事務導致死鎖的示例。
先來看看:髒讀、不可重複讀和幻讀。
髒讀
原因
當B事務在A事務修改和提交之間讀取被A事務修改的資料時,且B事務,採用了“讀未提交”隔離級別。
重現和避免
測試程式碼
1 public static void 髒讀測試() 2 { 3 Console.WriteLine("\n***************重現髒讀***************。"); 4 髒讀測試(IsolationLevel.ReadUncommitted); 5 6 Console.WriteLine("\n***************避免髒讀***************。"); 7 髒讀測試(IsolationLevel.ReadCommitted); 8 } 9 10 private static void 髒讀測試(IsolationLevel readIsolationLevel)11 { 12 var autoResetEvent = new AutoResetEvent(false); 13 var writeTransactionOptions = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted, Timeout = TimeSpan.FromSeconds(120) }; 14 var readTransactionOptions = new TransactionOptions { IsolationLevel = readIsolationLevel, Timeout = TimeSpan.FromSeconds(5) }; 15 16 using (var ts1 = new TransactionScope(TransactionScopeOption.Required, writeTransactionOptions)) 17 { 18 #region 新增一條髒讀測試資料 19 20 using (var context = new TestContext()) 21 { 22 Console.WriteLine("\nA事務新增資料,未提交事務。"); 23 context.Users.AddOrUpdate(x => x.Title, new User() { Title = "髒讀測試資料" }); 24 context.SaveChanges(); 25 } 26 27 #endregion 28 29 #region 在另外一個執行緒讀取 30 31 ThreadPool.QueueUserWorkItem(data => 32 { 33 try 34 { 35 using (var ts3 = new TransactionScope(TransactionScopeOption.RequiresNew, readTransactionOptions)) 36 { 37 using (var context = new TestContext()) 38 { 39 Console.WriteLine("\nB事務讀取資料中..."); 40 var user = context.Users.FirstOrDefault(x => x.Title == "髒讀測試資料"); 41 Console.WriteLine("B事務讀取資料:" + user); 42 } 43 } 44 } 45 catch (Exception ex) 46 { 47 Console.WriteLine(ex.Message); 48 } 49 finally 50 { 51 autoResetEvent.Set(); 52 } 53 }); 54 55 autoResetEvent.WaitOne(); 56 autoResetEvent.Dispose(); 57 58 #endregion 59 } 60 }
輸出結果
結果分析
B事務採用“讀未提交”會出現髒讀,採用更高的隔離級別會避免髒讀。在避免中,因為還使用了執行緒同步,這裡出現了死鎖,最終導致超時。
不可重複讀
原因
B事務在A事務的兩次讀取之間修改了A事務讀取的資料,且A事務採用了低於“可重複讀”隔離級別的事務。
重現和避免
測試程式碼
1 public static void 不可重複讀測試() 2 { 3 Console.WriteLine("\n***************重現不可重複讀***************。"); 4 不可重複讀測試(IsolationLevel.ReadCommitted); 5 6 Console.WriteLine("\n***************避免不可重複讀***************。"); 7 不可重複讀測試(IsolationLevel.RepeatableRead); 8 } 9 10 private static void 不可重複讀測試(IsolationLevel readIsolationLevel) 11 { 12 //測試資料準備-開始 13 using (var context = new TestContext()) 14 { 15 context.Users.AddOrUpdate(x => x.Title, new User() { Title = "不可重複讀測試資料" }); 16 context.SaveChanges(); 17 } 18 //測試資料準備-完成 19 20 var autoResetEvent = new AutoResetEvent(false); 21 var readTransactionOptions = new TransactionOptions { IsolationLevel = readIsolationLevel, Timeout = TimeSpan.FromSeconds(120) }; 22 var writeTransactionOptions = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted, Timeout = TimeSpan.FromSeconds(5) }; 23 24 using (var ts1 = new TransactionScope(TransactionScopeOption.Required, readTransactionOptions)) 25 { 26 using (var context = new TestContext()) 27 { 28 var user = context.Users.FirstOrDefault(x => x.Title.Contains("不可重複讀測試資料")); 29 Console.WriteLine("\nA事務第一次讀取:" + user.Title); 30 } 31 32 ThreadPool.QueueUserWorkItem(data => 33 { 34 try 35 { 36 using (var ts2 = new TransactionScope(TransactionScopeOption.Required, writeTransactionOptions)) 37 { 38 using (var context = new TestContext()) 39 { 40 Console.WriteLine("\nB事務中間修改,並提交事務。"); 41 var user = context.Users.FirstOrDefault(x => x.Title.Contains("不可重複讀測試資料")); 42 user.Title = user.Title + "-段光偉"; 43 context.SaveChanges(); 44 } 45 ts2.Complete(); 46 } 47 } 48 catch (Exception ex) 49 { 50 Console.WriteLine(ex.Message); 51 } 52 finally 53 { 54 autoResetEvent.Set(); 55 } 56 }); 57 58 autoResetEvent.WaitOne(); 59 autoResetEvent.Dispose(); 60 61 using (var context = new TestContext()) 62 { 63 var user = context.Users.FirstOrDefault(x => x.Title.Contains("不可重複讀測試資料")); 64 Console.WriteLine("\nA事務第二次讀取:" + user.Title); 65 } 66 } 67 68 //測試資料清理-開始 69 using (var context = new TestContext()) 70 { 71 var user = context.Users.FirstOrDefault(x => x.Title.Contains("不可重複讀測試資料")); 72 context.Users.Remove(user); 73 context.SaveChanges(); 74 } 75 //測試資料清理-完成 76 }
輸出結果
結果分析
A事務採用低於“可重複讀”隔離級別會導致“不可重複讀”,高於或等於“可重複讀”級別就可以避免這個問題。在避免中,因為還使用了執行緒同步,這裡出現了死鎖,最終導致超時。
幻讀
原因
B事務在A事務的兩次讀取之間添加了資料,且A事務採用了低於“可序列化”隔離級別的事務。就像老師點了兩次名,人數不一樣,感覺自己出現了幻覺。
重現和避免
測試程式碼
1 public static void 幻讀測試() 2 { 3 Console.WriteLine("\n***************重現幻讀***************。"); 4 幻讀測試(IsolationLevel.RepeatableRead); 5 6 Console.WriteLine("\n***************避免幻讀***************。"); 7 幻讀測試(IsolationLevel.Serializable); 8 } 9 10 private static void 幻讀測試(IsolationLevel readIsolationLevel) 11 { 12 var autoResetEvent = new AutoResetEvent(false); 13 var readTransactionOptions = new TransactionOptions { IsolationLevel = readIsolationLevel, Timeout = TimeSpan.FromSeconds(120) }; 14 var writeTransactionOptions = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted, Timeout = TimeSpan.FromSeconds(5) }; 15 16 using (var ts1 = new TransactionScope(TransactionScopeOption.Required, readTransactionOptions)) 17 { 18 using (var context = new TestContext()) 19 { 20 var user = context.Users.FirstOrDefault(x => x.Title.Contains("幻讀測試資料")); 21 Console.WriteLine("\nA事務第一次讀取:" + user); 22 } 23 24 ThreadPool.QueueUserWorkItem(data => 25 { 26 try 27 { 28 using (var ts2 = new TransactionScope(TransactionScopeOption.Required, writeTransactionOptions)) 29 { 30 using (var context = new TestContext()) 31 { 32 Console.WriteLine("\nB事務中間新增,並提交事務。"); 33 context.Users.Add(new User() { Title = "幻讀測試資料" }); 34 context.SaveChanges(); 35 } 36 ts2.Complete(); 37 } 38 } 39 catch (Exception ex) 40 { 41 Console.WriteLine(ex.Message); 42 } 43 finally 44 { 45 autoResetEvent.Set(); 46 } 47 }); 48 49 autoResetEvent.WaitOne(); 50 autoResetEvent.Dispose(); 51 52 using (var context = new TestContext()) 53 { 54 var user = context.Users.FirstOrDefault(x => x.Title.Contains("幻讀測試資料")); 55 Console.WriteLine("\nA事務第二次讀取:" + user); 56 } 57 } 58 59 //測試資料清理-開始 60 using (var context = new TestContext()) 61 { 62 var user = context.Users.FirstOrDefault(x => x.Title.Contains("幻讀測試資料")); 63 if (user != null) 64 { 65 context.Users.Remove(user); 66 context.SaveChanges(); 67 } 68 } 69 //測試資料清理-完成 70 }
輸出結果
結果分析
A事務採用低於“序列化”隔離級別會導致“幻讀”,使用“序列化”級別就可以避免這個問題。在避免中,因為還使用了執行緒同步,這裡出現了死鎖,最終導致超時。
巢狀事務導致的死鎖
測試程式碼
1 public static void 巢狀事務導致的死鎖() 2 { 3 Console.WriteLine("\n***************巢狀事務導致的死鎖***************。"); 4 5 var autoResetEvent = new AutoResetEvent(false); 6 var writeTransactionOptions = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted, Timeout = TimeSpan.FromSeconds(120) }; 7 8 using (var ts1 = new TransactionScope(TransactionScopeOption.Required, writeTransactionOptions)) 9 { 10 using (var context = new TestContext()) 11 { 12 Console.WriteLine("\nA事務新增資料,未提交事務。"); 13 context.Users.AddOrUpdate(x => x.Title, new User() { Title = "髒讀測試資料" }); 14 context.SaveChanges(); 15 } 16 17 18 try 19 { 20 using (var ts2 = new TransactionScope(TransactionScopeOption.Suppress, TimeSpan.FromSeconds(5))) 21 { 22 using (var context = new TestContext()) 23 { 24 Console.WriteLine("\nA事務所線上程使用 TransactionScopeOption.Suppress 讀取資料中..."); 25 var user = context.Users.FirstOrDefault(x => x.Title == "髒讀測試資料"); 26 Console.WriteLine("A事務所線上程使用 TransactionScopeOption.Suppress 讀取資料:" + user); 27 } 28 } 29 } 30 catch (Exception ex) 31 { 32 Console.WriteLine(ex.InnerException.Message); 33 } 34 35 { 36 using (var context = new TestContext()) 37 { 38 var user = context.Users.FirstOrDefault(x => x.Title == "髒讀測試資料"); 39 Console.WriteLine("\nA事務讀取資料:" + user); 40 } 41 } 42 } 43 }
輸出結果
原因分析
雖然採用了Suppress,並不代表讀取就不採用事務了,預設的“讀已提交”還是會起作用,可以在巢狀事務中採用“讀未提交”解決這個問題。
備註
執行緒池和資料庫級別的鎖我還不是非常瞭解,有待繼續挖掘,有熟悉的朋友請給個連結或提示,不勝感激。
相關推薦
髒讀,不可重複度,幻讀
【1】髒讀(讀取未提交資料) 事物A讀取事物B尚未提交的資料,此時事物B發生回滾,那麼事物A讀到的資料就是髒資料,俗稱髒讀 這類情況長髮生在轉賬和取款操作中: 【2】不可重複讀(前後多
.NET:髒讀、不可重複讀和幻讀測試
背景 昨天才發現如果一條資料被A事務修改但是未提交,B事務如果採用“讀已提交”或更嚴格的隔離級別讀取改資料,會導致鎖等待,考慮到資料庫預設的隔離級別是“讀已提交”,在巢狀事務 + 子事務中有複雜的SQL查詢,很可能會出現死鎖,後面會給出巢狀事務導致死鎖的示例。 先來看看:髒讀、不可重複讀和幻讀。 髒讀
MySQL技術內幕 InnoDB儲存引擎:鎖問題(髒讀、不可重複讀)
1、髒讀 在理解髒讀(Dirty Read)之前,需要理解髒資料的概念。但是髒資料和之前所介紹的髒頁完全是兩種不同的概念。髒頁指的是在緩衝池中已經被修改的頁,但是還沒有重新整理到磁碟中,即資料庫例項記憶體中的頁和磁碟中的頁的資料是不一致的,當然在重新整理到磁碟之前,日誌都已經被寫人到
資料庫併發訪問、事務與鎖、髒讀、不可重複讀、幻讀
資料庫併發訪問、事務與鎖的關係 一、事務 I : 事務的定義: 首先,讓我們瞭解下什麼是事務?事務是作為單個邏輯單元工作執行的一系列操作。可以是一條 sql語句,也可以是多條 sql 語句 ( 這是它的描述性定義&nb
資料庫事務隔離級別-- 髒讀、幻讀、不可重複讀(清晰解釋)
一、資料庫事務隔離級別 資料庫事務的隔離級別有4個,由低到高依次為Read uncommitted 、Read committed 、Repeatable read 、Serializable ,這四個級別可以逐個解決髒讀 、不可重複讀 、幻讀 這幾類問題。 √: 可能出
髒讀、不可重複讀、幻讀的簡單理解
首先看看“髒讀”,看到“髒”這個字,我就想到了噁心、骯髒。資料怎麼可能髒呢?其實也就是我們經常說的“垃圾資料”了。比如說,有兩個事務,它們在併發執行(也就是競爭)。看看以下這個表格,您一定會明白我在說什麼: 餘額應該為 1100 元才對!請看 T6 時間點,事務 A
資料庫的髒讀、不可重複讀、幻讀
一、引言 “讀現象” 是在多個事務併發執行時,在讀取資料方面可能碰到的狀況。瞭解它們有助於理解各隔離級別的含義,其中包括髒讀、不可重複讀和幻讀。 二、事務的隔離級別 我們知道,在資料庫中,事務是要滿足ACID的四個性質,即要滿足原子性、一致性、永續性以及隔離性。 在資料庫
髒讀、幻讀、不可重複讀 and 樂觀鎖、悲觀鎖 and 事務五種隔離級別
一、髒讀、不可重複讀、幻讀 1、髒讀:髒讀就是指當一個事務正在訪問資料,並且對資料進行了修改,而這種修改還沒有提交到資料庫中,這時,另外一個事務也訪問這個資料,然後使用了這個資料。 例如: 張三的工資為5000,事務A中把他的工資改為8000,但事務A尚未提
資料庫事務隔離級別-- 髒讀、幻讀、不可重複讀
一 、資料庫事務隔離級別 從高到低: 序列化 serilizable 消耗資源比較嚴重 重複讀 repeatable read Oracle 預設的事務隔離級別 讀提交 read committed Mysql 預設的隔離級別
資料庫併發事務存在的問題(髒讀、不可重複讀、幻讀等)
一個數據庫可能擁有多個訪問客戶端,這些客戶端併發訪問資料庫時,若沒有采取必要的隔離措施,存在以下問題,這些問題分為5類,包括3類資料讀問題:髒讀、不可重複讀和幻讀。兩類資料更新問題:第一類丟失更新、第二類丟失更新。 1.髒讀 A事務讀取B事務尚未提交的更
資料庫問題原因詳解(髒讀、不可重複讀、幻讀)
一、髒讀、不可重複讀、幻讀 1、髒讀:髒讀就是指當一個事務正在訪問資料,並且對資料進行了修改,而這種修改還沒有提交到資料庫中,這時,另外一個事務也訪問這個資料,然後使用了這個資料。 例如: 張三的
資料庫事務隔離級別,髒讀、不可重複讀、幻讀
資料庫事務的隔離級別有4個,由低到高依次為Read uncommitted 、Read committed 、Repeatable read 、Serializable ,後面三個可以逐個解決髒讀 、不可重複讀 、幻讀 這幾類問題。 髒讀 不可重複讀 幻讀 Read u
mysql解決髒讀、不可重複讀、虛讀的辦法
上篇文章講了事務隔離性的概念以及會出現的問題,現在來說,應該怎麼避免這些問題的出現。 點選開啟連結http://blog.csdn.net/jjkang_/article/details/54925
資料庫事務隔離級別及髒讀、不可重複讀、幻讀的理解
一、資料庫事務正確執行的四個基本要素 1.1ACID原則。 ACID原則是資料庫事務正常執行的四個基本要素,分別指原子性、一致性、獨立性及永續性。 原子性(Atomicity)是指一個事務要麼全部執行,要麼不執行,也就是說一個事務不可能只執
髒讀、幻讀、不可重複讀
發生事務併發的情況下。髒讀——一個事務讀取到了另一個事務未提交的資料。幻讀——一個事務(A)修改了表中的所有資料行,另一個事務(B)新增了一行 資料,前一個事務(A)提交後檢視資料,發現還有一條未修改
通俗地解釋髒讀、不可重複讀、幻讀
spring(資料庫)事務隔離級別分為四種(級別遞減): 1、Serializable (序列化):最嚴格的級別,事務序列執行,資源消耗最大; 2、REPEATABLE READ(重複讀) :保證了一個事務不會修改已經由另一個事務讀取但未提交(回滾)的資料。
髒讀、不可重複讀 共享鎖、悲觀鎖 和 事務五種隔離級別
一、髒讀、不可重複讀、幻讀 1、髒讀:髒讀就是指當一個事務正在訪問資料,並且對資料進行了修改,而這種修改還沒有提交到資料庫中,這時,另外一個事務也訪問這個資料,然後使用了這個資料。 例如: 張三的工資為5000,事務A中把他的工資改為8000,但事務A尚未提交。 與
事務特性及髒讀、不可重複讀、幻讀(虛讀)
事務是指邏輯上的一組操作,這組操作要麼全部成功,要麼全部失敗。事務的特性(ACID):原子性(A):事務是一個不可分割的工作單位,事務中的操作要麼都發生,要麼都不發生。一致性(C):事務前後資料的完整性
資料庫事務隔離,髒讀、幻讀、不可重複讀
一、資料庫事務隔離級別 資料庫事務的隔離級別有4個,由低到高依次為Read uncommitted 、Read committed 、Repeatable read 、Serializable ,這四個級別可以逐個解決髒讀 、不可重複讀 、幻讀 這幾類問題。
事務的髒讀、幻讀、不可重複讀的理解
一、事務的概念 事務指邏輯上的一組操作,組成這組操作的各個單元,要不全部成功,要不全部不成功。 例如:A——B轉帳,對應於如下兩條sql語句 update from account set money=money+100 where name='B'; update from acc