.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的文档,我可以简单在这里总结一下,帮助其他还没有理解的朋友,如果有错误也麻烦帮忙指正. 先讲一点 ...
随机推荐
- —软测试—(5)计算机系统CPU组成
事实上,我们不得不很早就接触到电脑系统的知识,但仍然会出现不起眼,现象清醒的认识,非常严重丢分. 要我们花功夫去理解,由于非常多东西我们接触不到,比方校验码.码制等.假设你不去理解而是去记,就非常难參 ...
- 从[java.lang.OutOfMemoryError: Java heap space]恢复
出现java.lang.OutOfMemoryError: Java heap space该错误或者是程序问题,或者被分配到JVM内存真的是不够的. 一般来说都是能够事前可控解决的. 可是假设不可控的 ...
- CocoaPods 建立私有仓库
CocoaPods是iOS,Mac下优秀的第三方包管理工具,类似于java的maven,给我们项目管理带来了极大的方便. [个人或公司在开发过程中,会积累很多可以复用的代码包,有些我们不想开源,又想像 ...
- SignalR的实时高频通讯
SignalR的实时高频通讯 第五章SignalR的实时高频通讯 概述:本例子演示了如果创建一个对象与其他浏览器共享实时状态的应用程序.我们要创建的应用程序为“MoveShape”,该MoveShap ...
- UIImage分类:返回一个可以拉伸的图片
// 返回一个可拉伸的图片 // UIImage的分类 + (UIImage *)resizedImage:(NSString *)name { UIImage *image = [self imag ...
- Android的5样的调试信息
Android的5样的调试信息 华清2014-10-23 北京海淀区 张俊浩 verbose:只是滤全部的信息. 啰嗦的意思. debug:debug调试的意思. info:一般提示的信息inf ...
- OpenGL入门【1 高速入门】
// OpenGL.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include<iostream> #include &l ...
- UiAutomator源码分析之UiAutomatorBridge框架
上一篇文章<UIAutomator源码分析之启动和运行>我们描述了uitautomator从命令行运行到加载测试用例运行测试的整个流程,过程中我们也描述了UiAutomatorBridge ...
- 对于GetBuffer() 与 ReleaseBuffer() 的一些分析
先 转载一段别人的文章 CString类的这几个函数, 一直在用, 但总感觉理解的不够透彻, 不时还实用错的现象. 今天抽时间和Nico一起分析了一下, 算是拨开了云雾: GetBuffer和Rele ...
- java这些东西发展(4)-------无穷time of error
今天,有些郁闷的心情啊.空指针下午折磨.到现在为止仍然没有得到解决,专家的招募结果没拿到,我们必须继续自己的,进而改变一点点一点点地找到它,但现在我不想搞,准备回家,这浪费了一个多小时,之前记录的下一 ...