1. 程式人生 > >.NET:髒讀、不可重複讀和幻讀測試

.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