第六节:深究事务的相关性质、隔离级别及对应的问题、死锁相关

一. 相关概念 

   前面系列中的章节的: 第二十二节: 以SQLServer为例介绍数据库自有的锁机制(共享锁、更新锁、排它锁等)和事务隔离级别  介绍了各种锁以及事务的隔离级别,是从数据库的角度进行介绍的,本章节是通过EF Core为载体,介绍事务隔离级别和相关问题,与上述章节有些许重复的内容。

1. 什么是事务

  事务(Transaction)是由一系列对系统中数据进行访问与更新的操作所组成的一个程序执行逻辑单元。

2. 事务的特征

  事务具有 4 个基本特征,分别是:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Duration),简称:ACID。

  (1).原子性:指事务必须是一个原子的操作序列单元。事务中包含的各项操作在一次执行过程中,只允许出现两种状态之一。 • 全部执行成功 • 全部执行失败,任何一项操作都会导致整个事务的失败,同时其它已经被执行的操作都将被撤销并回滚,只有所有的操作全部成功,整个事务才算是成功完成.

  (2).一致性:事务的一致性是指事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处以一致性状态。比如:如果从 A 账户转账到 B 账户,不可能因为 A 账户扣了钱,而 B 账户没有加钱,无论 A 和 B 怎么转账,系统中总额是固定的,不可能因为 A 和 B 转账导致系统总额缺斤少两。

  (3).隔离性:指在并发环境中,并发的事务是互相隔离的,一个事务的执行不能被其它事务干扰。也就是说,不同的事务并发操作相同的数据时,每个事务都有各自完整的数据空间。一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务是不能互相干扰的。(详见下面隔离级别)

  (4).持久性:事务的持久性是指事务一旦提交后,数据库中的数据必须被永久的保存下来。即使服务器系统崩溃或服务器宕机等故障。只要数据库重新启动,那么一定能够将其恢复到事务成功结束后的状态。

二. 事务隔离级别以及引发的问题

1. 隔离级别

 (1).读未提交(READ_UNCOMMITTED):读未提交,该隔离级别允许脏读取,其隔离级别是最低的。换句话说,如果一个事务正在处理某一数据,并对其进行了更新,但同时尚未完成事务, 因此还没有提交事务;而以此同时,允许另一个事务也能够访问该数据。

  【引发的问题:脏读】

 (2).读已提交(READ_COMMITTED) :事务执行的时候只能获取到其它事务已经提交的数据,获取不到未提交的数据。

  【解决了“脏读”,但是解决不了“不可重复读”】

 (3).可重复读(REPEATABLE_READ):保证在事务处理过程中,多次读取同一个数据时,该数据的值和事务开始时刻是一致的。

  【解决了“脏读”和“不可重复度”,但是解决不了“幻读”】

 (4).顺序读(SERIALIZABLE):最严格的事务隔离级别。它要求所有的事务排队顺序执行,即事务只能一个接一个地处理,不能并发。

  【解决上述所有情况】

注:4 种事务隔离级别从上往下,级别越高,并发性越差,安全性就越来越高。一般数据默认级别是读已提交或可重复读。

PS:常见数据库的默认级别:

 ①:MySQL 数据库的默认隔离级别是 Repeatable read 级别。

 ②:Oracle数据库中,只支持 Seralizable 和 Read committed级别,默认的是 Read committed 级别。

 ③:SQL Server 数据库中,默认的是 Read committed(读已提交) 级别。

2.引发的问题

 (1).脏读(Dirty Read):第一个事务读取第二个事务正在更新的数据,如果第二个事务还没有更新完成,那么第一个事务读取的数据将是一半为更新过的,一半还没更新过的数据。

 (2).不可重复读(Unrepeatable Read):如果一个用户在一个事务中多次读取一条数据,而另外一个用户则同时更新啦这条数据,造成第一个用户多次读取数据不一致。

 (3).幻读(Phantom Read):指同样的事务操作,在前后两个时间段内执行对同一个数据项的读取,可能出现不一致的结果集。

3. 案例测试

(前提:初始值userAge均为1000的且id为01 和 02 两条数据)

 (1).脏读测试:事务1两条数据分别-500,正常事务提交后,这两条数据的userAge的值应该均为500;将事务2设置成读未提交(IsolationLevel.ReadUncommitted 即允许脏读), 查出来的结果是:500,1000,即脏读数据。

代码分享

 

 1             {
 2                 //1.事先准备删除所有数据,插入两条指定数据
 3                 using (EFDB01Context db = new EFDB01Context())
 4                 {
 5                     db.Database.ExecuteSqlCommand("truncate table T_UserInfor");
 6                     db.Database.ExecuteSqlCommand("insert into T_UserInfor values('01','ypf1','男',1000,'2019-08-08')");
 7                     db.Database.ExecuteSqlCommand("insert into T_UserInfor values('02','ypf2','男',1000,'2019-08-08')");
 8                 }
 9                 //事务1
10                 Task.Run(() =>
11                 {
12                     using (var db = new EFDB01Context())
13                     {
14                         using (var transaction = db.Database.BeginTransaction())
15                         {
16                             try
17                             {
18                                 var data1 = db.T_UserInfor.Find("01");
19                                 data1.userAge -= 500;
20                                 db.SaveChanges();
21 
22                                 Task.Delay(TimeSpan.FromSeconds(10)).Wait();
23 
24                                 var data2 = db.T_UserInfor.Find("02");
25                                 data2.userAge -= 500;
26                                 db.SaveChanges();
27 
28                                 transaction.Commit();
29 
30                             }
31                             catch (Exception ex)
32                             {
33 
34                                 Console.WriteLine(ex.Message);
35                             }
36                         }
37                     }
38                 });
39                 //事务2
40                 Task.Run(() =>
41                 {
42                     using (var db = new EFDB01Context())
43                     {
44                         //设置成“读未提交”
45                         using (var transaction = db.Database.BeginTransaction(IsolationLevel.ReadUncommitted))
46                         {
47                             try
48                             {
49                                 Task.Delay(TimeSpan.FromSeconds(5)).Wait();
50                                 var data1 = db.T_UserInfor.Find("01");
51                                 var data2 = db.T_UserInfor.Find("02");
52 
53                                 Console.WriteLine($"01 userAge is {data1.userAge}");
54                                 Console.WriteLine($"02 userAge is {data2.userAge}");
55 
56                             }
57                             catch (Exception ex)
58                             {
59 
60                                 Console.WriteLine(ex.Message);
61                             }
62                         }
63                     }
64                 });
65             }
View Code

 

避免脏读:将事务2设置成读已提交(IsolationLevel.ReadCommitted 或者不设置,SQLServer默认就是读已提交),则事务2需要等待事务1执行完才能读取,读出来的两条数据的均为500,即避免了脏读。

代码分享

 

 1   {
 2                 //1.事先准备删除所有数据,插入两条指定数据
 3                 using (EFDB01Context db = new EFDB01Context())
 4                 {
 5                     db.Database.ExecuteSqlCommand("truncate table T_UserInfor");
 6                     db.Database.ExecuteSqlCommand("insert into T_UserInfor values('01','ypf1','男',1000,'2019-08-08')");
 7                     db.Database.ExecuteSqlCommand("insert into T_UserInfor values('02','ypf2','男',1000,'2019-08-08')");
 8                 }
 9                 //事务1
10                 Task.Run(() =>
11                 {
12                     using (var db = new EFDB01Context())
13                     {
14                         using (var transaction = db.Database.BeginTransaction())
15                         {
16                             try
17                             {
18                                 var data1 = db.T_UserInfor.Find("01");
19                                 data1.userAge -= 500;
20                                 db.SaveChanges();
21 
22                                 Task.Delay(TimeSpan.FromSeconds(10)).Wait();
23 
24                                 var data2 = db.T_UserInfor.Find("02");
25                                 data2.userAge -= 500;
26                                 db.SaveChanges();
27 
28                                 transaction.Commit();
29 
30                             }
31                             catch (Exception ex)
32                             {
33 
34                                 Console.WriteLine(ex.Message);
35                             }
36                         }
37                     }
38                 });
39                 //事务2
40                 Task.Run(() =>
41                 {
42                     using (var db = new EFDB01Context())
43                     {
44                         //设置成“读已提交”,或者不设置,SQLServer默认就是读已提交
45                         using (var transaction = db.Database.BeginTransaction(IsolationLevel.ReadCommitted))
46                         {
47                             try
48                             {
49                                 Task.Delay(TimeSpan.FromSeconds(5)).Wait();
50                                 var data1 = db.T_UserInfor.Find("01");
51                                 var data2 = db.T_UserInfor.Find("02");
52 
53                                 Console.WriteLine($"01 userAge is {data1.userAge}");
54                                 Console.WriteLine($"02 userAge is {data2.userAge}");
55 
56                             }
57                             catch (Exception ex)
58                             {
59 
60                                 Console.WriteLine(ex.Message);
61                             }
62                         }
63                     }
64                 });
65             }
View Code

 

 (2).不可重复读测试:事务1中5s后将01数据的userAge的值由1000改为500,事务2中在“读已提交”的情况下两次读取的01的数据分别是1000,500,即为不可重复读。

代码分享

 

 1   {
 2                 {
 3                     //1.事先准备删除所有数据,插入两条指定数据
 4                     using (EFDB01Context db = new EFDB01Context())
 5                     {
 6                         db.Database.ExecuteSqlCommand("truncate table T_UserInfor");
 7                         db.Database.ExecuteSqlCommand("insert into T_UserInfor values('01','ypf1','男',1000,'2019-08-08')");
 8                         db.Database.ExecuteSqlCommand("insert into T_UserInfor values('02','ypf2','男',1000,'2019-08-08')");
 9                     }
10                     //事务1
11                     Task.Run(() =>
12                     {
13                         using (var db = new EFDB01Context())
14                         {
15                             using (var transaction = db.Database.BeginTransaction())
16                             {
17                                 try
18                                 {
19                                     Task.Delay(TimeSpan.FromSeconds(5)).Wait();
20 
21                                     var data1 = db.T_UserInfor.Find("01");
22                                     data1.userAge -= 500;
23                                     db.SaveChanges();
24 
25                                     transaction.Commit();
26 
27                                 }
28                                 catch (Exception ex)
29                                 {
30 
31                                     Console.WriteLine(ex.Message);
32                                 }
33                             }
34                         }
35                     });
36                     //事务2
37                     Task.Run(() =>
38                     {
39                         using (var db = new EFDB01Context())
40                         {
41                             //设置成“读已提交”,或者不设置,SQLServer默认就是读已提交
42                             using (var transaction = db.Database.BeginTransaction(IsolationLevel.ReadCommitted))
43                             {
44                                 try
45                                 {
46                                     //一定要加上这句,否则下面的第二个Find不读取数据库
47                                     db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
48 
49                                     var data1 = db.T_UserInfor.Find("01");
50                                     Console.WriteLine($"01 userAge is {data1.userAge}");
51 
52                                     Task.Delay(TimeSpan.FromSeconds(6)).Wait();
53 
54                                     var data2 = db.T_UserInfor.Find("01");
55                                     Console.WriteLine($"01 userAge is {data2.userAge}");
56 
57                                 }
58                                 catch (Exception ex)
59                                 {
60 
61                                     Console.WriteLine(ex.Message);
62                                 }
63                             }
64                         }
65                     });
66                 }
67             }
View Code

 

避免不可重复读:将事务2设置成“可重复读”(IsolationLevel.RepeatableRead),事务2两次读取的数据均为1000,避免了不可重复读。(但第二次数据和数据库已经不一样了,数据库中是500)

代码分享

 

 1  {
 2                 {
 3                     //1.事先准备删除所有数据,插入两条指定数据
 4                     using (EFDB01Context db = new EFDB01Context())
 5                     {
 6                         db.Database.ExecuteSqlCommand("truncate table T_UserInfor");
 7                         db.Database.ExecuteSqlCommand("insert into T_UserInfor values('01','ypf1','男',1000,'2019-08-08')");
 8                         db.Database.ExecuteSqlCommand("insert into T_UserInfor values('02','ypf2','男',1000,'2019-08-08')");
 9                     }
10                     //事务1
11                     Task.Run(() =>
12                     {
13                         using (var db = new EFDB01Context())
14                         {
15                             using (var transaction = db.Database.BeginTransaction())
16                             {
17                                 try
18                                 {
19                                     Task.Delay(TimeSpan.FromSeconds(5)).Wait();
20 
21                                     var data1 = db.T_UserInfor.Find("01");
22                                     data1.userAge -= 500;
23                                     db.SaveChanges();
24 
25                                     transaction.Commit();
26 
27                                 }
28                                 catch (Exception ex)
29                                 {
30 
31                                     Console.WriteLine(ex.Message);
32                                 }
33                             }
34                         }
35                     });
36                     //事务2
37                     Task.Run(() =>
38                     {
39                         using (var db = new EFDB01Context())
40                         {
41                             //设置成“可重复读”
42                             using (var transaction = db.Database.BeginTransaction(IsolationLevel.RepeatableRead))
43                             {
44                                 try
45                                 {
46                                     //一定要加上这句,否则下面的第二个Find不读取数据库
47                                     db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
48 
49                                     var data1 = db.T_UserInfor.Find("01");
50                                     Console.WriteLine($"01 userAge is {data1.userAge}");
51 
52                                     Task.Delay(TimeSpan.FromSeconds(6)).Wait();
53 
54                                     var data2 = db.T_UserInfor.Find("01");
55                                     Console.WriteLine($"01 userAge is {data2.userAge}");
56 
57                                 }
58                                 catch (Exception ex)
59                                 {
60 
61                                     Console.WriteLine(ex.Message);
62                                 }
63                             }
64                         }
65                     });
66                 }
67             }
View Code

 

 (3).幻读测试

有点问题,需要在什么场景下测试??

 

三. 死锁

 

   详见开头之前的章节,此处不再重复介绍了

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 本人才疏学浅,用郭德纲的话说“我是一个小学生”,如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。

转载于:https://www.cnblogs.com/yaopengfei/p/11394728.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/248209.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

js(Dom+Bom)第五天(2)

webAPI 01-事件监听 为什么要学事件监听 之前给元素注册事件的时候,同一个事件会被覆盖掉事件监听的本质 通过另外一种方式给元素注册事件, 同时可以解决同一个事件不会被覆盖掉.知识点-通过 on 方式给元素注册事件 之前注册事件的方式:事件源.onclick function() {}on是一种…

UIAutomator简介

简介 Android 4.3发布的时候包含了一种新的测试工具–uiautomator,uiautomator是用来做UI测试的。也就是普通的手工测试,点击每个控件元素 看看输出的结果是否符合预期。比如 登陆界面 分别输入正确和错误的用户名密码然后点击登陆按钮看看是否能否登陆以…

从零开始实现ASP.NET Core MVC的插件式开发(五) - 插件的删除和升级

标题:从零开始实现ASP.NET Core MVC的插件式开发(五) - 使用AssemblyLoadContext实现插件的升级和删除 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/11395828.html 源代码:https://github.com/lamondlu/Mystique 前景回…

【JavaWeb】石家庄地铁搭乘系统——第一版(功能尚未完善)

小组成员:高达,程超然 项目目的:能算出地铁搭乘的最优路线并显示在地图上 个人任务:调用队友写好的java代码,将结果显示在网页上 新的知识:百度地图API,使用JQuery的AJAX异步提交 进度&#xff…

扫描枪连接zebra打印机打印条码标签无需电脑

在一些流水线生产的现场,需要及时打印条码标签,由于现场环境和空间限制,无法摆放电脑或者通过连接电脑来打印条码标签的速度太慢, 瑞科条码特提供了一套扫描枪直接连接条码打印机,扫描枪扫描条码之后直接打印输出条码标…

简单的动画函数封装(1)

//创建简单的动画函数封装效果(目标对象&#xff0c;目标位置) function animate(obj,target){var id setInterval(function(){if(obj.offsetLeft > target){clearInterval(id);}else{obj.style.left obj.offsetLeft 5 px;}},30) }可以实现如下效果&#xff1a; <div…

NodeMCU学习(三) : 进入网络世界

阅读原文可以访问我的个人博客 把NodeMCU连接到路由器网络上 NodeMCU可以被配置为Station模式和softAP模式或者Station AP模式&#xff0c;当它被配置为Station模式时&#xff0c;就可以去连接Access Point&#xff08;如路由器&#xff09;。当它被配置为Soft Access Point模…

操作系统原理之进程调度与死锁(三)

一、进程调度的功能与时机 进程调度&#xff1a;进程调度的功能由操作系统的进程调度程序完成 具体任务&#xff1a;按照某种策略和算法从就绪态进程中为当前空闲的CPU选择在其上运行的新进程。 进程调度的时机&#xff1a;进程正常或异常结束、进程阻塞、有更高优先级进程到来…

模拟京东侧边栏

思路&#xff1a; // 1. 获取元素 // 1.1 获取一组li // 1.2 获取一组类名为item的div // 1.3 获取类名为slide的div// 2. 循环遍历给每一个li注册onmouseenter&#xff0c;并且每一个li添加一个index表示索引 // 2.1 循环遍历把所有的li的类名设置为,把所有的item的display设…

ViewPager + TabLayout + Fragment + MediaPlayer的使用

效果图 在gradle里导包 implementation com.android.support:design:28.0.0 activity_main <?xml version"1.0" encoding"utf-8"?><LinearLayout xmlns:android"http://schemas.android.com/apk/res/android" xmlns:tools"http…

vs code打开文件显示的中文乱码

这种情况下&#xff0c;一般是编码格式导致的&#xff0c;操作办法&#xff1a; 鼠标点击之后&#xff0c;上面会弹出这个界面&#xff0c;双击选中 然后从UTF-8换到GB2312&#xff0c;或者自己根据情况&#xff0c;更改编码格式 转载于:https://www.cnblogs.com/132818Creator…

操作系统原理之内存管理(第四章第一部分)

内存管理的⽬标&#xff1a;实现内存分配和回收&#xff0c;提高内存空间的利用率和内存的访问速度 一、存储器的层次结构 寄存器&#xff1a;在CPU内部有一组CPU寄存器&#xff0c;寄存器是cpu直接访问和处理的数据&#xff0c;是一个临时放数据的空间。 高速缓冲区&#xff1…

自写图片遮罩层放大功能jquery插件源代码,photobox.js 1.0版,不兼容IE6

版权声明&#xff1a;本文为博主原创文章。未经博主同意不得转载。 https://blog.csdn.net/u010480479/article/details/27362147 阿嚏~~~ 话说本屌丝没啥开发插件的经验&#xff0c;可是天公不作美&#xff0c;公司须要让我自己开发个图片放大的插件 但公司老大的话&#xff0…

黑白两客进入页面(1)

<div><span>欢</span><span>迎</span><span>来</span><span>到</span><span><strong>黑白两客</strong></span><span>的</span><span>博</span><span>客</sp…

zookeeper学习之原理

一、zookeeper 是什么 Zookeeper是一个分布式协调服务&#xff0c;可用于服务发现&#xff0c;分布式锁&#xff0c;分布式领导选举&#xff0c;配置管理等。这一切的基础&#xff0c;都是Zookeeper提供了一个类似于Linux文件系统的树形结构&#xff08;可认为是轻量级的内存文…

前端js基础智能机器人

<script>var flag true;while(flag) {//获取用户输入信息 var code prompt(你好,我是小娜\n请输入编号或者关键词选择功能,输入Q(q)退出聊天\n1.计算\n2.时间\n3.笑话);switch( code ) {case q:case Q:alert(狠心的抛弃了小娜);flag false;break;case 1:case 计算:var…

2018-2019-2 《Java程序设计》第6周学习总结

20175319 2018-2019-2 《Java程序设计》第6周学习总结 教材学习内容总结 本周学习《Java程序设计》第七章和第十章&#xff1a; 内部类&#xff1a; 1.内部类可以使用外嵌类的成员变量和方法。 2.类体中不可以声明类变量和类方法。 3.内部类仅供外嵌类使用。 4.类声明可以使用s…

Hbase基本原理

一、hbase是什么 HBase 是一种类似于数据库的存储层&#xff0c;也就是说 HBase 适用于结构化的存储。并且 HBase 是一种列式的分布式数据库&#xff0c;是由当年的 Google 公布的 BigTable 的论文而生。HBase 底层依旧依赖 HDFS 来作为其物理存储。 二、hbase的列式存储结构 行…

最终的动画函数封装(2)

<button>点击触发1</button><button>点击触发2</button><div></div><style>*{margin: 0;padding: 0;}div{width: 100px;height: 100px;background-color: red;position: relative;top: 100px;left: 0;}.div1{display: block;width: …

第二次JAVA作业

感觉和C语言后面都差不多&#xff0c;就是开头的定义和输入输出有点差别&#xff0c;多写几次应该能搞清楚开头的定义&#xff0c;接下来是四道题目的截图。 第一题&#xff1a; 第二题&#xff1a; 第三题&#xff1a; 第四题&#xff1a; 转载于:https://www.cnblogs.com/YSh…