MySql学习笔记之事务隔离级别详解


Posted in MySQL onMay 12, 2021

背景

说的事务,大家应该都不陌生,开发用到 MySql 数据库的时候,通常会用到事务。其中比较经典的例子就是转账,比如你要给小明转 50 块钱,而此时你的银行卡也就只有 50 块钱。

对于转账过程在代码程序里会有一系列的操作,比如查询账户余额、余额加减、更新余额等,这些操作必须保证是一起处理的,不然等程序查完之后,如果账号 50 块钱还在,然后再给另外一个朋友转账,如果银行也处理的话,没有保证整个流程数据一致性的话,这不就乱套了吗?这时就要用到“事务”了。

事务介绍

简单来说,事务就是要保证一组数据库操作,要么全部执行成功,要么全部都失败。在 MySQL 中,事务支持是在引擎层(InnoDB)实现的。我们知道,MySQL 是一个支持多引擎的系统,但并不是所有的引擎都支持事务。比如 MySQL 原生的 MyISAM 引擎就不支持事务,这也是 MyISAM 被 InnoDB 取代的重要原因之一。

在这篇文章里,将会以 InnoDB 为例,说说 MySQL 在事务支持方面的一些实现,并基于原理给出相应的实践建议,通过这些说明,可以加深对 MySQL 事务原理的理解。

事务的隔离级别

提到事务,你肯定会想到 ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性),下面我们就来说说其中 I,也就是“隔离性”。

当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有了“隔离级别”的概念。

在谈隔离级别之前,你首先要知道,你隔离得越严实,效率就会越低。因此很多时候,我们都要在二者之间寻找一个平衡点。SQL 标准的事务隔离级别包括:读未提交(readuncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。下面逐一为你解释:

  • 读未提交是指,一个事务还没提交时,它做的变更就能被别的事务看到。
  • 读提交是指,一个事务提交之后,它做的变更才会被其他事务看到。
  • 可重复读是指,一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
  • 串行化,顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

其中“读提交”和“可重复读”比较难理解,所以打算用一个例子说明这几种隔离级别。假设数据表 t_student 中只有一列,其中一行的值为 21,下面是按照时间顺序执行两个事务的行为。

mysql> create table t_student(age int) engine=InnoDB;
mysql> insert into t_student(age) values(21);

MySql学习笔记之事务隔离级别详解

在不同的隔离级别下,事务 A 会有哪些不同的返回结果,也就是图中 V1、V2、V3 的返回值分别是什么。若隔离级别是“读未提交”, 则 V1 的值就是 22。这时候事务 B 虽然还没有提交,但是结果对于 A 来说是可见的。因此,V2、V3 也都是 22。

若隔离级别是“读提交”,则 V1 是 21,V2 的值是 22。事务 B 的更新在提交后对 A 才是可见的。所以, V3 的值也是 22。

若隔离级别是“可重复读”,则 V1、V2 是 21,V3 是 22。之所以 V2 还是 21,遵循的就是这个原则:事务在执行期间看到的数据前后必须是一致的。

若隔离级别是“串行化”,则在事务 B 执行“将 21 改成 22”的时候,会被锁住。直到事务 A 提交后,事务 B 才可以继续执行。所以从 A 的角度看, V1、V2 值是 21,V3 的值是 22。

在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。在“可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。

在“读提交”隔离级别下,这个视图是在每个 SQL 语句开始执行的时候创建的。这里需要注意的是,“读未提交”隔离级别下直接返回记录上的最新值,没有视图概念;而“串行化”隔离级别下直接用加锁的方式来避免并行访问。

可以看到在不同的隔离级别下,数据库行为是有所不同的。Oracle 数据库的默认隔离级别其实就是“读提交”,因此对于一些从 Oracle 迁移到 MySQL 的应用,为保证数据库隔离级别的一致,你一定要记得将 MySQL 的隔离级别设置为“读提交”。

配置的方式是,将启动参数 transaction-isolation 的值设置成 READ-COMMITTED。你可以用 showvariables 来查看当前的值。

mysql> show variables like 'transaction_isolation';

总的来说,存在即合理,哪个隔离级别都有它自己的使用场景,要根据自己的业务情况来定。有些人可能会问那什么时候需要“可重复读”的场景呢?我们来看一个数据校对逻辑的案例。

假设你在管理一个银行账户表,一个表存了每个月月底的余额,一个表存了账单明细。这时候你要做数据校对,也就是判断上个月的余额和当前余额的差额,是否与本月的账单明细一致。你一定希望在校对过程中,即使有用户发生了一笔新的交易,也不影响你的校对结果。这时候使用“可重复读”隔离级别就很方便。事务启动时的视图可以认为是静态的,不受其他事务更新的影响。

事务隔离的实现

理解了事务的隔离级别,我们再来看看事务隔离具体是怎么实现的。这里我们展开说明“可重复读”。在 MySQL 中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。

假设一个值从 1 被按顺序改成了 2、3、4,在回滚日志(undo log)里面就会有类似下面的记录:

MySql学习笔记之事务隔离级别详解

当前值是 4,但是在查询这条记录的时候,不同时刻启动的事务会有不同的 read-view。如图中看到的,在视图 A、B、C 里面,这一个记录的值分别是 1、2、4,同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。对于 read-view A,要得到 1,就必须将当前值依次执行图中所有的回滚操作得到。

同时你会发现,即使现在有另外一个事务正在将 4 改成 5,这个事务跟 read-viewA、B、C 对应的事务是不会冲突的。
有人可能会问,回滚日志总不能一直保留吧,什么时候删除呢?当然在不需要的时候才删除,也就是说,系统会判断,当没有事务再需要用到这些回滚日志时,回滚日志会被删除。

那么问题来了,什么时候才不需要了呢?就是当系统里没有比这个回滚日志更早的 read-view 的时候。

基于上面的说明,我们来讨论一下为什么建议你尽量不要使用长事务。

首先长事务意味着系统里面会存在很老的事务视图,由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间。

在 MySQL 5.5 及以前的版本,回滚日志是跟数据字典一起放在 ibdata 文件里的,即使长事务最终提交,回滚段被清理,文件也不会变小。我见过数据只有 10GB,而回滚段有 100GB 的库,最终只好为了清理回滚段,需要重建整个库。
除了对回滚段的影响,长事务还占用锁资源,也可能拖垮整个库。

事务启动方式

前面提到的长事务有这些潜在风险,建议当然是尽量避免。其实很多时候业务开发同学并不是有意使用长事务,通常是由于误用所致。MySQL 的事务启动方式有以下几种:

  1. 显式启动事务语句, begin 或 start transaction。配套的提交语句是 commit,回滚语句是 rollback。
  2. set autocommit=0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个 select 语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在直到你主动执行 commit 或 rollback 语句,或者断开连接。

有些客户端连接框架会默认连接成功后先执行一个 set autocommit=0 的命令。这就导致接下来的查询都在事务中,如果是长连接,就导致了意外的长事务。

因此,建议总是使用 set autocommit=1, 通过显式语句的方式来启动事务。但是有的开发同学会纠结“多一次交互”的问题。对于一个需要频繁使用事务的业务,第二种方式每个事务在开始时都不需要主动执行一次 “begin”,减少了语句的交互次数。如果你也有这个顾虑,建议使用 commit work and chain 的语法。

在 autocommit 为 1 的情况下,用 begin 显式启动的事务,如果执行 commit 则提交事务。如果执行 commit work and chain,则是提交事务并自动启动下一个事务,这样也省去了再次执行 begin 语句的开销。同时带来的好处是从程序开发的角度明确地知道每个语句是否处于事务中。你可以在 information_schema 库的 innodb_trx 这个表中查询长事务。

总结

主要讲了 MySQL 的事务隔离级别的现象和实现,根据实现原理分析了长事务存在的风险,以及如何用正确的方式避免长事务。理解了这些事务的原理,可以更好地使用 MySQL 的事务特性。

到此这篇关于MySql学习笔记之事务隔离级别的文章就介绍到这了,更多相关MySql事务隔离级别内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

MySQL 相关文章推荐
简单了解 MySQL 中相关的锁
May 25 MySQL
正确使用MySQL update语句
May 26 MySQL
你知道哪几种MYSQL的连接查询
Jun 03 MySQL
Mysql systemctl start mysqld报错的问题解决
Jun 03 MySQL
浅谈MySQL next-key lock 加锁范围
Jun 07 MySQL
MySQL实例精讲单行函数以及字符数学日期流程控制
Oct 15 MySQL
Mysql分库分表之后主键处理的几种方法
Feb 15 MySQL
MySQL 主从复制数据不一致的解决方法
Mar 18 MySQL
mysql 获取时间方式
Mar 20 MySQL
MySQL创建管理KEY分区
Apr 13 MySQL
MySQL下载安装配置详细教程 附下载资源
Sep 23 MySQL
MySQL索引失效十种场景与优化方案
May 08 MySQL
MySQL 分组查询的优化方法
May 12 #MySQL
JDBC连接的六步实例代码(与mysql连接)
May 12 #MySQL
MySQL索引知识的一些小妙招总结
MySQL COUNT函数的使用与优化
May 10 #MySQL
解读MySQL的客户端和服务端协议
MySQL 重写查询语句的三种策略
May 10 #MySQL
详解MySQL 联合查询优化机制
You might like
php中通过smtp发邮件的类,测试通过
2007/01/22 PHP
php设计模式之命令模式的应用详解
2013/05/21 PHP
解析php addslashes()与addclashes()函数的区别和比较
2013/06/24 PHP
php去除换行(回车换行)的三种方法
2014/03/26 PHP
windows服务器中检测PHP SSL是否开启以及开启SSL的方法
2014/04/25 PHP
YII路径的用法总结
2014/07/09 PHP
PHP如何将log信息写入服务器中的log文件
2015/07/29 PHP
初识通用数据库操作类――前端easyui-datagrid,form(php)
2015/07/31 PHP
兼容IE与firefox火狐的回车事件(js与jquery)
2010/10/20 Javascript
基于jQuery的input输入框下拉提示层(自动邮箱后缀名)
2012/06/14 Javascript
jquery的ajax()函数传值中文乱码解决方法介绍
2012/11/08 Javascript
JavaScript NodeTree导航栏(菜单项JSON类型/自制)
2013/02/01 Javascript
jquery实现在页面加载完毕后获取图片高度或宽度
2014/06/16 Javascript
javascript实现漂亮的拖动层,窗口拖拽特效
2015/04/24 Javascript
jQuery遍历DOM的父级元素、子级元素和同级元素的方法总结
2016/07/07 Javascript
AngularJS基于ui-route实现深层路由的方法【路由嵌套】
2016/12/14 Javascript
Jquery Easyui自定义下拉框组件使用详解(21)
2020/12/31 Javascript
vue中$set的使用(结合在实际应用中遇到的坑)
2018/07/10 Javascript
JS实现带阴历的日历功能详解
2019/01/24 Javascript
小程序怎样让wx.navigateBack更好用的方法实现
2019/11/01 Javascript
node.js实现http服务器与浏览器之间的内容缓存操作示例
2020/02/11 Javascript
jQuery 淡入/淡出效果函数用法分析
2020/05/19 jQuery
基于aotu.js实现微信自动添加通讯录中的联系人功能
2020/05/28 Javascript
[47:31]完美世界DOTA2联赛PWL S3 INK ICE vs DLG 第一场 12.12
2020/12/16 DOTA
python实现人人网登录示例分享
2014/01/19 Python
Python二次规划和线性规划使用实例
2019/12/09 Python
Python数据可视化处理库PyEcharts柱状图,饼图,线性图,词云图常用实例详解
2020/02/10 Python
举例详解CSS3中的Transition
2015/07/15 HTML / CSS
Born鞋子官网:Born Shoes
2017/04/06 全球购物
英语专业推荐信
2013/11/16 职场文书
生产部经理岗位职责
2013/12/16 职场文书
医药学专业大学生职业生涯规划书论文
2014/01/21 职场文书
学习优秀党员杨宗兴先进事迹材料思想汇报
2014/09/14 职场文书
2015年全国爱耳日活动总结
2015/02/27 职场文书
开业庆典致辞
2015/08/01 职场文书
面试中canvas绘制图片模糊图片问题处理
2022/03/13 Javascript