.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的文档,我可以简单在这里总结一下,帮助其他还没有理解的朋友,如果有错误也麻烦帮忙指正. 先讲一点 ...
随机推荐
- 6.跑步者--并行编程框架 ForkJoin
本文如果您已经了解一般并行编程知识.了解Java concurrent部分如ExecutorService等相关内容. 虽说是Java的ForkJoin并行框架.但不要太在意Java,当中的思想在其他 ...
- 多线程编程 (2) -NSOperation
一.NSInvocationOperation 二.NSBlockOperation 三.NSOperation的其他用法 四.自定义NSOperation 1.上一讲简单介绍了NSThread的使用 ...
- lib库实现loadrunner驱动mysql性能测试
一.添加mysql驱动链接文件到loadrunner的bin和include目录下 以下链接为本人云盘分享,也可百度自行寻找下载源. http://yunpan.cn/cfTxbANSvipGi ...
- 那些必须要知道的Javascript
原文:那些必须要知道的Javascript JavaScript是前端必备,而这其中的精髓也太多太多,最近在温习的时候发现有些东西比较容易忽略,这里记录一下,一方面是希望自己在平时应用的时候能够得心应 ...
- 转载:Linux Used内存到底到哪里去了?
转自:http://blogread.cn/it/article/6264?f=wb2 有时在Linux下会碰到这样的问题:ps aux看到的RSS内存只有不到30M,但是free看到内存却已经使用了 ...
- Spring IOC之BeanFactory
BeanFactory提供了SpringIOC功能的基础但是它只是直接在用在和第三方框架的整合中,而且现在对于大部分的Spring用户来讲这一句成为了过去.BeanFactory和相关的接口,例如Be ...
- 个人实现的一个简单的蜗牛矩阵(c语言)
#include<stdio.h> #include<stdlib.h> int main(void) { int n,m; int x,y; int **array; int ...
- js获取非行间样式或定义样式
<!--DOCTYPE html--> <html> <head> <meta charset="utf-8" /> <sty ...
- AJAX入门——工作原理
同步和异步交互,了解互动 对于一个样本:一般B/S模式(同步) AJAX技术(异步) * 同步: 提交请求->等待server处理->处理完成返回 ...
- jQuery小例
jQuery小例子 使用前,请先引用jquery 1,map遍历数组 2,jQuery对象与DOM对象才做元素和互转 3,prevall与nextall 4,jquery版的星星评分控件 5,jq ...