目录

背景脏读原因重现和避免不可重复读原因重现和避免幻读原因重现和避免嵌套事务导致的死锁备注

背景返回目录

昨天才发现如果一条数据被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:脏读、不可重复读和幻读测试的更多相关文章

  1. SQL Server 中的事务与事务隔离级别以及如何理解脏读, 未提交读,不可重复读和幻读产生的过程和原因

    原本打算写有关 SSIS Package 中的事务控制过程的,但是发现很多基本的概念还是需要有 SQL Server 事务和事务的隔离级别做基础铺垫.所以花了点时间,把 SQL Server 数据库中 ...

  2. Spring 事务与脏读、不可重复读、幻读

    索引: 目录索引 参看代码 GitHub: 1.Spring 事务 2.事务行为 一.Spring 事务: Spring 的事务机制是用统一的机制来处理不同数据访问技术的事务处理. Spring 的事 ...

  3. [MySQL]对于事务并发处理带来的问题,脏读、不可重复读、幻读的理解

    一.缘由 众所周知MySQL从5.5.8开始,Innodb就是默认的存储引擎,Innodb最大的特点是:支持事务.支持行级锁. 既然支持事务,那么就会有处理并发事务带来的问题:更新丢失.脏读.不可重复 ...

  4. Hibernate中的事务隔离问题(脏读、不可重复读、幻读)

    Hibernate中的事务隔离问题(脏读.不可重复读.幻读) 1.事务的特性 事务的四个特性: 1)原子性:事务是进行数据库操作的最小单位,所以组成事务的各种操作是不可分割的 2)一致性:组成事务的各 ...

  5. SQL Server中的事务与其隔离级别之脏读, 未提交读,不可重复读和幻读

    原本打算写有关 SSIS Package 中的事务控制过程的,但是发现很多基本的概念还是需要有 SQL Server 事务和事务的隔离级别做基础铺垫.所以花了点时间,把 SQL Server 数据库中 ...

  6. MySQL事务(脏读、不可重复读、幻读)

    1. 什么是事务? 是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作:这些操作作为一个整体一起向系统提交,要么都执行.要么都不执行:事务是一组不可再分割的操作集合(工作逻辑单元): ...

  7. hibernate事务并发问题(脏读,不可重复读,幻读)

    脏读  dirty read:  读了别的事务没有提交的事务, 可能回滚, 数据可能不对. 不可重复读 non repeatable read: 同一个事务里前后读出来的数据不一样, 被另一个事务影响 ...

  8. mysql系列:加深对脏读、脏写、可重复读、幻读的理解

    关于相关术语的专业解释,请自行百度了解,本文皆本人自己结合参考书和自己的理解所做的阐述,如有不严谨之处,还请多多指教. 事务有四种基本特性,叫ACID,它们分别是: Atomicity-原子性,Con ...

  9. 如何理解SQL的可重复读和幻读之间的区别?

    从本源来理解比较容易理解,如果只是描述概念和定义,容易让人云里雾里找不到方向.正好这两天在浏览mysql的文档,我可以简单在这里总结一下,帮助其他还没有理解的朋友,如果有错误也麻烦帮忙指正. 先讲一点 ...

随机推荐

  1. ScrollView 嵌套ListView 幻灯冲突,和显示不全

    import android.content.Context; import android.util.AttributeSet; import android.widget.ListView; /* ...

  2. 表单验证 jquery-validation

    表单验证首选:jquery-validation 参见 http://jqueryvalidation.org/ 下载之后 在 demo  的index 页面有各种API 详细的案例. 还可以参考 h ...

  3. C#备份还原MySql数据库

    原文:C#备份还原MySql数据库 项目结束,粘点代码出来让Google或Baidu一下,原因是现在还搜不到这么现成的 调用MySql的工具mysqldump来实现. 类Cmd来实现调用cmd命令, ...

  4. WPF 读写TxT文件

    原文:WPF 读写TxT文件 文/嶽永鹏 WPF 中读取和写入TxT 是经常性的操作,本篇将从详细演示WPF如何读取和写入TxT文件. 首先,TxT文件希望逐行读取,并将每行读取到的数据作为一个数组的 ...

  5. Apache无法启动解决 the requested operation has failed

    Apache不能启动解决办法 原因一:80端口占用例如IIS,另外就是迅雷. 原因二:软件冲突装了某些软件会使apache无法启动如Dr.com 你打开网络连接->TcpIp属性->高级- ...

  6. gtest框架

    解析gtest框架运行机制   1.前言 Google test是一款开源的白盒单元测试框架,据说目前在Google内部已在几千个项目中应用了基于该框架的白盒测试. 最近的工作是在搞一个基于gtest ...

  7. nginx+lua+redis高并发应用建设

    ngx_lua将lua嵌nginx,让nginx运行lua脚本.高并发,非堵塞过程中的各种请求. url要求nginxserver,然后lua查询redis,返回json数据. 一.安装lua-ngi ...

  8. jQuery获取Radio选择的Value值||两个select之间option的互相添加操作(jquery实现)

    jQuery获取Radio选择的Value值: 1. $("input[name='radio_name'][checked]").val();  //选择被选中Radio的Val ...

  9. wcf消息模式(随记)

    ----------------------------------------------消息模式:1.request\reply(默认)2.one-way(单工)[Isoneway=true]客户 ...

  10. Javascript多线程引擎(五)

    Javascript多线程引擎(五)之异常处理 C语言没有提供一个像Java一样的异常处理机制, 这就带来了一个问题, 对于一个子函数中发生异常后, 需要在父函数调用子函数的位置进行Check, 如果 ...