Net Core中数据库事务隔离详解——以Dapper和Mysql为例

事务隔离级别

.NET Core中的IDbConnection接口提供了BeginTransaction方法作为执行事务,BeginTransaction方法提供了两个重载,一个不需要参数BeginTransaction()默认事务隔离级别为RepeatableRead;另一个BeginTransaction(IsolationLevel il)可以根据业务需求来修改事务隔离级别。由于Dapper是对IDbConnection的扩展,所以Dapper在执行增删除改查时所有用到的事务需要由外部来定义。事务执行时与数据库之间的交互如下:

从WireShark抓取的数据包来看程序和数据交互步骤依次是:建立连接-->设置数据库隔离级别-->告诉数据库一个事务开始-->执行数据增删查改-->提交事务-->断开连接


准备工作

准备数据库:Mysql (笔者这里是:MySql 5.7.20 社区版)

创建数据库并创建数据表,创建数据表的脚本如下:

CREATE TABLE `posts` (  `Id` varchar(255) NOT NULL ,  `Text` longtext NOT NULL,  `CreationDate` datetime NOT NULL,  `LastChangeDate` datetime NOT NULL,  `Counter1` int(11) DEFAULT NULL,  `Counter2` int(11) DEFAULT NULL,  `Counter3` int(11) DEFAULT NULL,  `Counter4` int(11) DEFAULT NULL,  `Counter5` int(11) DEFAULT NULL,  `Counter6` int(11) DEFAULT NULL,  `Counter7` int(11) DEFAULT NULL,  `Counter8` int(11) DEFAULT NULL,  `Counter9` int(11) DEFAULT NULL,  PRIMARY KEY (`Id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

创建.NET Core Domain类:

[Table("Posts")]public class Post{[Key]    public string Id { get; set; }    public string Text { get; set; }    public DateTime CreationDate { get; set; }    public DateTime LastChangeDate { get; set; }    public int? Counter1 { get; set; }    public int? Counter2 { get; set; }    public int? Counter3 { get; set; }    public int? Counter4 { get; set; }    public int? Counter5 { get; set; }    public int? Counter6 { get; set; }    public int? Counter7 { get; set; }    public int? Counter8 { get; set; }    public int? Counter9 { get; set; }}

具体怎样使用Dapper,请看上篇。


Read uncommitted 读未提交

允许脏读,即不发布共享锁,也不接受独占锁。意思是:事务A可以读取事务B未提交的数据。

优点:查询速度快

缺点:容易造成脏读,如果事务A在中途回滚

以下为执行脏读的测试代码片断:

public static void RunDirtyRead(IsolationLevel transaction1Level,IsolationLevel transaction2Level){    var id = Guid.NewGuid().ToString();    using (var connection1 = new MySqlConnection(connStr)){connection1.Open();Console.WriteLine("transaction1 {0} Start",transaction1Level);        var transaction1 = connection1.BeginTransaction(transaction1Level);Console.WriteLine("transaction1 插入数据 Start");        var sql = "insert into posts (id,text,CreationDate,LastChangeDate) values(@Id,@Text,@CreationDate,@LastChangeDate)";        var detail1 = connection1.Execute(sql,        new Post{Id = id,Text = Guid.NewGuid().ToString(),CreationDate = DateTime.Now,LastChangeDate = DateTime.Now},transaction1);Console.WriteLine("transaction1 插入End 返回受影响的行:{0}", detail1);        using (var connection2 = new MySqlConnection(connStr)){connection2.Open();Console.WriteLine("transaction2 {0} Start",transaction2Level);            var transaction2 = connection2.BeginTransaction(transaction2Level);Console.WriteLine("transaction2 查询数据 Start");            var result = connection2.QueryFirstOrDefault<Post>("select * from posts where id=@Id", new { id = id }, transaction2);            //如果result为Null 则程序会报异常Console.WriteLine("transaction2 查询结事 返回结果:Id={0},Text={1}", result.Id, result.Text);transaction2.Commit();Console.WriteLine("transaction2 {0} End",transaction2Level);}transaction1.Rollback();Console.WriteLine("transaction1 {0} Rollback ",transaction1Level);}}

1、当执行RunDirtyRead(IsolationLevel.ReadUncommitted,IsolationLevel.ReadUncommitted),即事务1和事务2都设置为ReadUncommitted时结果如下:

当事务1回滚以后,数据库并没有事务1添加的数据,所以事务2获取的数据是脏数据。

2、当执行RunDirtyRead(IsolationLevel.Serializable,IsolationLevel.ReadUncommitted),即事务1隔离级别为Serializble,事务2的隔离级别设置为ReadUncommitted,结果如下:

3、当执行RunDirtyRead(IsolationLevel.ReadUncommitted,IsolationLevel.ReadCommitted);,即事务1隔离级别为ReadUncommitted,事务2的隔离级别为Readcommitted,结果如下:

结论:当事务2(即取数据事务)隔离级别设置为ReadUncommitted,那么不管事务1隔离级别为哪一种,事务2都能将事务1未提交的数据得到;但是测试结果可以看出当事务2为ReadCommitted则获取不到事务1未提交的数据从而导致程序异常。


Read committed 读取提交内容

这是大多数数据库默认的隔离级别,但是,不是MySQL的默认隔离级别。读取数据时保持共享锁,以避免脏读,但是在事务结束前可以更改数据。

优点:解决了脏读的问题

缺点:一个事务未结束被另一个事务把数据修改后导致两次请求的数据不一致

测试重复读代码片断:

public static void RunRepeatableRead(IsolationLevel transaction1Level, IsolationLevel transaction2Level){    using (var connection1 = new MySqlConnection(connStr)){connection1.Open();        var id = "c8de065a-3c71-4273-9a12-98c8955a558d";Console.WriteLine("transaction1 {0} Start", transaction1Level);        var transaction1 = connection1.BeginTransaction(transaction1Level);Console.WriteLine("transaction1 第一次查询开始");        var sql = "select * from posts where id=@Id";        var detail1 = connection1.QueryFirstOrDefault<Post>(sql, new { Id = id }, transaction1);Console.WriteLine("transaction1 第一次查询结束,结果:Id={0},Counter1={1}", detail1.Id, detail1.Counter1);        using (var connection2 = new MySqlConnection(connStr)){connection2.Open();Console.WriteLine("transaction2  {0} Start", transaction2Level);            var transaction2 = connection2.BeginTransaction(transaction2Level);            var updateCounter1=(detail1.Counter1 ?? 0) + 1;Console.WriteLine("transaction2  开始修改Id={0}中Counter1的值修改为:{1}", id,updateCounter1);            var result = connection2.Execute(                "update posts set Counter1=@Counter1 where id=@Id",                new { Id = id, Counter1 = updateCounter1 },transaction2);Console.WriteLine("transaction2 修改完成 返回受影响行:{0}", result);transaction2.Commit();Console.WriteLine("transaction2 {0} End", transaction2Level);}Console.WriteLine("transaction1 第二次查询 Start");        var detail2 = connection1.QueryFirstOrDefault<Post>(sql, new { Id = id }, transaction1);Console.WriteLine("transaction1 第二次查询 End 结果:Id={0},Counter1={1}", detail2.Id, detail2.Counter1);transaction1.Commit();Console.WriteLine("transaction1 {0} End", transaction1Level);}
}

在事务1中detail1中得到的Counter1为1,事务2中将Counter1的值修改为2,事务1中detail2得到的Counter1的值也会变为2

下面分几种情况来测试:

1、当事务1和事务2都为ReadCommitted时,结果如下:

2、当事务1和事务2隔离级别都为RepeatableRead时,执行结果如下:

3、当事务1隔离级别为RepeatableRead,事务2隔离级别为ReadCommitted时执行结果如下:

4、当事务1隔离级别为ReadCommitted,事务2隔离级别为RepeatableRead时执行结果如下:

结论:当事务1隔离级别为ReadCommitted时数据可重复读,当事务1隔离级别为RepeatableRead时可以不可重复读,不管事务2隔离级别为哪一种不受影响。

注:在RepeatableRead隔离级别下虽然事务1两次获取的数据一致,但是事务2已经是将数据库中的数据进行了修改,如果事务1对该条数据进行修改则会对事务2的数据进行覆盖。

Repeatable read (可重读)

这是MySQL默认的隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行(目标数据行不会被修改)。

优点:解决了不可重复读和脏读问题

缺点:幻读

测试幻读代码

 public static void RunPhantomRead(IsolationLevel transaction1Level, IsolationLevel transaction2Level){    
using (var connection1 = new MySqlConnection(connStr)){connection1.Open();Console.WriteLine("transaction1 {0} Start", transaction1Level);    
   var transaction1 = connection1.BeginTransaction(transaction1Level);Console.WriteLine("transaction1 第一次查询数据库 Start");      
    var detail1 = connection1.Query<Post>("select * from posts").ToList();Console.WriteLine("transaction1 第一次查询数据库 End 查询条数:{0}", detail1.Count);    
        using (var connection2 = new MySqlConnection(connStr)){connection2.Open();Console.WriteLine("transaction2 {0} Start", transaction2Level);          
         var transaction2 = connection2.BeginTransaction(transaction2Level);Console.WriteLine("transaction2 执行插入数据 Start");          
           var sql = "insert into posts (id,text,CreationDate,LastChangeDate) values(@Id,@Text,@CreationDate,@LastChangeDate)";            var entity = new Post{Id = Guid.NewGuid().ToString(),Text = Guid.NewGuid().ToString(),CreationDate = DateTime.Now,LastChangeDate = DateTime.Now};            var result = connection2.Execute(sql, entity, transaction2);Console.WriteLine("transaction2 执行插入数据 End 返回受影响行:{0}", result);transaction2.Commit();Console.WriteLine("transaction2 {0} End", transaction2Level);}Console.WriteLine("transaction1 第二次查询数据库 Start");      
       var detail2 = connection1.Query<Post>("select * from posts").ToList();Console.WriteLine("transaction1 第二次查询数据库 End 查询条数:{0}", detail2.Count);transaction1.Commit();Console.WriteLine("transaction1 {0} End", transaction1Level);} }

分别对几种情况进行测试:

1、事务1和事务2隔离级别都为RepeatableRead,结果如下:

2、事务1和事务2隔离级别都为Serializable,结果如下:

3、当事务1的隔离级别为Serializable,事务2的隔离级别为RepeatableRead时,执行结果如下:

4、当事务1的隔离级别为RepeatableRead,事务2的隔离级别为Serializable时,执行结果如下:

结论:当事务隔离级别为RepeatableRead时虽然两次获取数据条数相同,但是事务2是正常将数据插入到数据库当中的。当事务1隔离级别为Serializable程序异常,原因接下来将会讲到。

Serializable 序列化

这是最高的事务隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。

优点:解决幻读

缺点:在每个读的数据行上都加了共享锁,可能导致大量的超时和锁竞争

当执行RunPhantomRead(IsolationLevel.Serializable, IsolationLevel.Serializable)或执行RunPhantomRead(IsolationLevel.Serializable, IsolationLevel.RepeatableRead)时代码都会报异常,是因为Serializable隔离级别下强制事务以串行方式执行,由于这里是一个主线程上第一个事务未完时执行了第二个事务,但是第二个事务必须等到第一个事务执行完成后才参执行,所以就会导致程序报超时异常。这里将代码作如下修改:

using (var connection1 = new MySqlConnection(connStr))
{connection1.Open();Console.WriteLine("transaction1 {0} Start", transaction1Level);   
 var transaction1 = connection1.BeginTransaction(transaction1Level);Console.WriteLine("transaction1 第一次查询数据库 Start");  
  var detail1 = connection1.Query<Post>("select * from posts").ToList();Console.WriteLine("transaction1 第一次查询数据库 End 查询条数:{0}", detail1.Count);Thread thread = new Thread(new ThreadStart(() =>{        using (var connection2 = new MySqlConnection(connStr)){connection2.Open();Console.WriteLine("transaction2 {0} Start", transaction2Level);    
          var transaction2 = connection2.BeginTransaction(transaction2Level);Console.WriteLine("transaction2 执行插入数据 Start");    
            var sql = "insert into posts (id,text,CreationDate,LastChangeDate) values(@Id,@Text,@CreationDate,@LastChangeDate)";          
            var entity = new Post{Id = Guid.NewGuid().ToString(),Text = Guid.NewGuid().ToString(),CreationDate = DateTime.Now,LastChangeDate = DateTime.Now};            var result = connection2.Execute(sql, entity, transaction2);Console.WriteLine("transaction2 执行插入数据 End 返回受影响行:{0}", result);transaction2.Commit();Console.WriteLine("transaction2 {0} End", transaction2Level);}}));thread.Start();    //为了证明两个事务是串行执行的,这里让主线程睡5秒Thread.Sleep(5000);Console.WriteLine("transaction1 第二次查询数据库 Start");  
    var detail2 = connection1.Query<Post>("select * from posts").ToList();Console.WriteLine("transaction1 第二次查询数据库 End 查询条数:{0}", detail2.Count);transaction1.Commit();Console.WriteLine("transaction1 {0} End", transaction1Level); }

执行结果如下:


结论:当事务1隔离级别为Serializable时对后面的事务的增删改改操作进行强制排序。避免数据出错造成不必要的麻烦。

注:在.NET Core中IsolationLevel枚举值中还提供了另外三种隔离级别:ChaosSnapshotUnspecified由于这种事务隔离级别MySql不支持设置时会报异常:

总结

本节通过Dapper对MySql中事务的四种隔离级别下进行测试,并且指出事务之间的相互关系和问题以供大家参考。

1、事务1隔离级别为ReadUncommitted时,可以读取其它任何事务隔离级别下未提交的数据

2、事务1隔离级别为ReadCommitted时,不可以读取其它事务未提交的数据,但是允许其它事务对数据表进行查询、添加、修改和删除;并且可以将其它事务增删改重新获取出来。

3、事务1隔离级别为RepeatableRead时,不可以读取其它事务未提交的数据,但是允许其它事务对数据表进行查询、添加、修改和删除;但是其它事务的增删改不影响事务1的查询结果

4、事务1隔离级别为Serializable时,对其它事务对数据库的修改(增删改)强制串行处理。


脏读重复读幻读
Read uncommitted
Read committed不会
Repeatable read不会不会
Serializable不会不会不会


原文地址:http://www.cnblogs.com/vipyoumay/p/8134434.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com


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

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

相关文章

2017年,我的身边发生了那些事?

不知不觉&#xff0c;运营独具.NET跨平台特色的微信公众号&#xff1a;dotNet跨平台至今已经整整三年光景了&#xff0c;这三年里微软开源.NET也满三周年了。三年时间说长不长&#xff0c;说短也不短了&#xff0c;然而我还是保持着每天三篇的更新 &#xff0c;或有感而原创&am…

ABP .Net Core Entity Framework迁移使用MySql数据库

一、迁移说明ABP模板项目Entity Framework Core默认使用的是Sql Server&#xff0c;也很容易将数据库迁移到MySQL&#xff0c;步骤如下。二、迁移MySQL步骤1、 下载项目请到 http://aspnetboilerplate.com/Templates 下载一个新的项目&#xff0c;选择ASP.NET Core 2.x标签&…

(四)十大经典排序算法(动画图解,代码完全)

排序算法是《数据结构与算法》中最基本的算法之一 1. 冒泡排序 1.1 算法步骤 比较相邻的元素。如果第一个比第二个大&#xff0c;就交换他们两个。 对每一对相邻元素作同样的工作&#xff0c;从开始第一对到结尾的最后一对。这步做完后&#xff0c;最后的元素会是最大的数。…

(五)SpringBoot 能挣钱的几个项目!!!

不得不佩服 Spring Boot 的生态如此强大&#xff0c;今天给大家推荐几款 Gitee 上优秀的后台开源版本的管理系统&#xff0c;小伙伴们再也不用从头到尾撸一个项目了&#xff0c;简直就是接私活&#xff0c;挣钱的利器啊。SmartAdmin我们开源一套漂亮的代码和一套整洁的代码规范…

手把手引进门之 ASP.NET Core Entity Framework Core(官方教程翻译版 版本3.2.5)

以下是手把手引进门教程&#xff0c;基于 ASP.NET Core&#xff0c; Entity Framework Core &#xff0c;ABP 框架 创建Web 应用&#xff0c; PS&#xff1a; 自带自动的测试模块哦。样例下载 &#xff08;上 github 的请自便&#xff09;介绍这是系列文章的第一部分&#xff1…

图像识别:微信跳一跳机器人

准备IDE&#xff1a;VisualStudioLanguage&#xff1a;VB.NET/C#GitHub&#xff1a;AutoJump.NET本文将向你介绍一种通过图像识别实现“跳一跳”机器人的方法。 第一节 图像识别文中提到的所有方法和步骤均仅涉及简单的向量计算。需要哪些计算&#xff1f;比较像素点的颜色求向…

(七)HTML和CSS 、JavaScript 和Java到底有什么区别,今天终于明白了!!!

有人曾经问过&#xff0c;“HTML CSS 和 JavaScript 以及 Java有什么区别” TCP VS UDP Java VS C 功能先上了再说 高级开发人员作为一个团队进行编程 调试CSS 高级开发人员重构代码 能一个手指完成的绝不用两只手 看实习生编码的时候&#xff0c;我的表情…… 当我尝试进入B…

基于Accord.Audio和百度语言识别

目标需求使用录音形式&#xff0c;模拟微信语音聊天。按住录音&#xff0c;松开发送语音&#xff0c;并完成语音识别。ps&#xff1a;百度的语言识别有60秒长度限制&#xff0c;需要自己做好控制。实现方案采用C# winform 程序实现桌面版&#xff0c;采用Accord 实现语音录制停…

(八)Spring与MyBatis整合

持久层 目录 Mybatis 开发步骤回顾Mybatis 开发中存在的问题Spring 与 Mybatis 整合思路Spring 与 Mybatis 整合的开发步骤Spring 与 Mybatis 整合的编码搭建开发环境 pom.xmlSpring 配置文件的配置编码Spring 与 Mybatis 整合细节持久层整合总述 1、Spring 框架为什么要与持…

Git 企业开发者教程

为什么要写这样一个面向企业开发者的Git教程&#xff1f;这个问题也困扰我自己很久。其实我使用git的时间也不短了&#xff0c;但是就和正在阅读本文的每一位一样&#xff0c;常用的基本就是那么几个(git clone, git push)等等。然而git其实有着非常强大的功能&#xff0c;如果…

基于百度理解与交互技术实现机器问答

一、前言我们都知道现在聊天对话机器是一个很有意思的东西&#xff0c;比如说苹果siri&#xff0c;比如说微软的小冰。聊天对话机器的应用场景也很广泛&#xff0c;比如说&#xff1a;银行的自助办卡机器人、展会讲解解说等等。我们对机器人说句话&#xff0c;机器人从听取&…

(十)Spring 与 MVC 框架整合

Spring 整合 MVC 目录 MVC 框架整合思想为什么要整合 MVC 框架搭建 Web 运行环境Spring 整合 MVC 框架的核心思路1. 准备工厂2. 代码整合Spring 整合 Struts2MVC 框架整合思想 为什么要整合 MVC 框架 MVC 框架提供了控制器&#xff08;Controller&#xff09;调用 Servlet …

利用VSTS跟Kubernetes整合进行CI/CD

为什么VSTS要搭配Kubernetes&#xff1f;通常我们在开发管理软件项目的时候都会碰到一个很头痛的问题&#xff0c;就是开发、测试、生产环境不一致&#xff0c;导致开发人员和测试人员甚至和运维吵架。因为常见的物理环境甚至云环境中&#xff0c;这些部署环境都是由运维人员提…

(十一)Spring 基础注解(对象创建相关注解、注入相关注解)

注解编程 目录 注解基础概念注解的作用Spring 注解的发展历程Spring 基础注解&#xff08;Spring 2.x&#xff09;对象创建相关注解ComponentRepository、Service、ContollerScopeLazy生命周期注解 PostConstruct、PreDestroy注入相关注解用户自定义类型 AutowiredJDK 类型注…

使用 ASP.NET Core, Entity Framework Core 和 ABP 创建N层Web应用 第二篇

介绍这是“使用 ASP.NET Core &#xff0c;Entity Framework Core 和 ASP.NET Boilerplate 创建N层 Web 应用”系列文章的第二篇。以下可以看其他篇目&#xff1a;使用 ASP.NET Core &#xff0c;Entity Framework Core 和 ASP.NET Boilerplate 创建N层 Web 应用 第一篇 &…

揭秘微软6万工程师DevOps成功转型的技术「武器」

在微软&#xff0c;通过其自身数年的 DevOps 转型&#xff0c; 6 万名工程师实现了更好的软件平台创新和快速迭代。微软有庞大的技术产品矩阵&#xff0c;同时也具有每天发布的能力&#xff0c;其中&#xff0c;微软研发云是支撑整个开发过程与运维最重要的基础平台。微软研发云…

Flowable学习笔记(一、入门)

转载自 Flowable学习笔记&#xff08;一、入门&#xff09; 一、Flowable简介 1、Flowable是什么 Flowable是一个使用Java编写的轻量级业务流程引擎。Flowable流程引擎可用于部署BPMN 2.0流程定义&#xff08;用于定义流程的行业XML标准&#xff09;&#xff0c; 创建这些流…

01-MyBatis入门程序

MyBatis入门程序 目录 1. 下载 Mybatis 核心包2. 创建工程&#xff0c;引入 MyBatis 核心包及依赖包3. 创建 customer 表&#xff0c;建立与表对应的 domain使用 lombok&#xff0c;开启注解创建 Customer 类4. 创建 MyBatis 核心配置文件 SqlMappingConfig.xml5. 创建表对象…

角落的开发工具集之Vs(Visual Studio)2017插件推荐

“ 工具善其事&#xff0c;必先利其器&#xff01;装好这些插件让vs更上一层楼”因为最近录制视频的缘故&#xff0c;很多朋友都在QQ群留言&#xff0c;或者微信公众号私信我&#xff0c;问我一些工具和一些插件啊&#xff0c;怎么使用的啊&#xff1f;那么今天我忙里偷闲整理一…

02-MyBatis配置SQL打印

MyBatis 配置SQL打印 在 SqlMappingConfig.xml 中配置以下代码&#xff1a; <!--配置sql打印--> <settings><setting name"logImpl" value"STDOUT_LOGGING"/> </settings>运行效果&#xff1a;会显示 SQL 语句&#xff0c;查询结…