.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,并不代表读取就不采用事务了,默认的“读已提交”还是会起作用,可以在嵌套事务中采用“读未提交”解决这个问题。
备注返回目录
线程池和数据库级别的锁我还不是非常了解,有待继续挖掘,有熟悉的朋友请给个链接或提示,不胜感激。
.NET:脏读、不可重复读和幻读测试的更多相关文章
- SQL Server 中的事务与事务隔离级别以及如何理解脏读, 未提交读,不可重复读和幻读产生的过程和原因
原本打算写有关 SSIS Package 中的事务控制过程的,但是发现很多基本的概念还是需要有 SQL Server 事务和事务的隔离级别做基础铺垫.所以花了点时间,把 SQL Server 数据库中 ...
- Spring 事务与脏读、不可重复读、幻读
索引: 目录索引 参看代码 GitHub: 1.Spring 事务 2.事务行为 一.Spring 事务: Spring 的事务机制是用统一的机制来处理不同数据访问技术的事务处理. Spring 的事 ...
- [MySQL]对于事务并发处理带来的问题,脏读、不可重复读、幻读的理解
一.缘由 众所周知MySQL从5.5.8开始,Innodb就是默认的存储引擎,Innodb最大的特点是:支持事务.支持行级锁. 既然支持事务,那么就会有处理并发事务带来的问题:更新丢失.脏读.不可重复 ...
- Hibernate中的事务隔离问题(脏读、不可重复读、幻读)
Hibernate中的事务隔离问题(脏读.不可重复读.幻读) 1.事务的特性 事务的四个特性: 1)原子性:事务是进行数据库操作的最小单位,所以组成事务的各种操作是不可分割的 2)一致性:组成事务的各 ...
- SQL Server中的事务与其隔离级别之脏读, 未提交读,不可重复读和幻读
原本打算写有关 SSIS Package 中的事务控制过程的,但是发现很多基本的概念还是需要有 SQL Server 事务和事务的隔离级别做基础铺垫.所以花了点时间,把 SQL Server 数据库中 ...
- MySQL事务(脏读、不可重复读、幻读)
1. 什么是事务? 是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作:这些操作作为一个整体一起向系统提交,要么都执行.要么都不执行:事务是一组不可再分割的操作集合(工作逻辑单元): ...
- hibernate事务并发问题(脏读,不可重复读,幻读)
脏读 dirty read: 读了别的事务没有提交的事务, 可能回滚, 数据可能不对. 不可重复读 non repeatable read: 同一个事务里前后读出来的数据不一样, 被另一个事务影响 ...
- mysql系列:加深对脏读、脏写、可重复读、幻读的理解
关于相关术语的专业解释,请自行百度了解,本文皆本人自己结合参考书和自己的理解所做的阐述,如有不严谨之处,还请多多指教. 事务有四种基本特性,叫ACID,它们分别是: Atomicity-原子性,Con ...
- 如何理解SQL的可重复读和幻读之间的区别?
从本源来理解比较容易理解,如果只是描述概念和定义,容易让人云里雾里找不到方向.正好这两天在浏览mysql的文档,我可以简单在这里总结一下,帮助其他还没有理解的朋友,如果有错误也麻烦帮忙指正. 先讲一点 ...
随机推荐
- Spring + Spring MVC + Hibernate项目开发集成(注解)
在自己从事的项目中都是使用xml配置的方式来进行的,随着项目的越来越大,会发现配置文件会相当的庞大,这个不利于项目的进行和后期的维护.于是考虑使用注解的方式来进行项目的开发,前些日子就抽空学习了一下. ...
- Java数据结构与算法(4) - ch04队列(Queue和PriorityQ)
队列: 先进先出(FIFO). 优先级队列: 在优先级队列中,数据项按照关键字的值有序,关键字最小的数据项总在对头,数据项插入的时候会按照顺序插入到合适的位置以确保队列的顺序,从后往前将小于插入项的数 ...
- input的width和padding-left同时存在时IE兼容问题
总的来说,text-indent不影响元素的最终宽度但是有兼容性问题,padding-left在中国主流浏览器IE低版本下影响最终宽度,但在chrome和firefox下不影响宽度,但是可以通过CSS ...
- C语言学习之路,第一篇章。
看的书是 C primer plus ,这本书好评很多, 学过C#,没有精通,了解Java,所以看这本书会很容易上手,编译器用的是VC++6.0,因为VS2010好像不支持C99标准,有些代码功能 ...
- POJ 1565 Skew Binary(简单的问题)
[简要题意]:第二个是数字系统的代表性的定义.而给了你这个号码系统提示的形式和十进制转换之间的关系.现在给你一些这样的系统.让你把它变成2二进制输出. [分析]:当中 base[k] = 2^(k ...
- Windows下用C语言连接Mysql注意问题
原文:Windows下用C语言连接Mysql注意问题 环境是:在VS6.0 安装Mysql后,我们需要相应的头文件以及lib文件,所以安装过程必须是完整安装.否则不会生成include文件夹哦~ 具体 ...
- Android中利用Handler实现消息的分发机制(三)
在第二篇文章<Android中利用Handler实现消息的分发机制(一)>中,我们讲到主线程的Looper是Android系统在启动App的时候,已经帮我们创建好了,而假设在子线程中须要去 ...
- windows下使用pthread
有的时候需要使用多线程来测试代码啥的,在Linux下有pthread,在windows也有. 我这儿是使用MingW作为编译工具,具体如何下载MingW自行在网上搜索. 而pthread是在这里下载的 ...
- 用lucene.net根据关键字检索本地word文档
目前在做一个winform小软件,其中有一个功能是能根据关键字检索本地保存的word文档.第一次是用com读取word方式(见上一篇文章),先遍历文件夹下的word文档,读取每个文档时循环关键字查找, ...
- 解决Ubuntu Adobe Reader 菜单栏空白
sudo gedit /usr/local/share/applications/AdobeReader.desktop将 ”Exec=acroread“ 用 ”Exec=env UBUNTU_ME ...