一直在使用mysql, 但是对于mysql 的一些核心原理一直有些模糊。出问题总是想着公司有专职的DBA, 专业的事情交给专业的人来做嘛,所以一直没有研究(其实主要是懒)。趁着假期,闲着也是闲着,就想简单了解一下。人嘛,总是嫌麻烦,总想找些现成的文章或者课程。找了极客时间的两套mysql相关课程 (基础)SQL必知必会、(进阶)MySQL实战45讲,看了个大概,只总结出来mysql最核心的应该是事务跟索引。
但是看完之后,思维陷入了混乱,每一节都是一些知识点的堆积,没有形成体系,自己想不明白彼此之间到底是什么关系。自己又找了拉钩上的《高性能mysql实战》,有个大概了。我整理了两个核心问题 :1、“事务的特性是如何实现的?”;2、“事务四种隔离级别是怎么结合锁来解决并发产生的三种经典问题的?” 。
本文是mysql 事务的第一部分-四个特性及实现。自己不是专职的DBA,理解难免有偏差,只是简单地总结一下。希望对你有所帮助,如果有纰漏,也请留言指出,感谢~
文章主要内容如下:
- 事务的定义
- 事务的四个特性以及背后的技术原理
1 事务是什么
做工程实现,想必大家都接触过事务。事务,一组逻辑执行单元,要么全部做完,要不都不做,不会处于一个中间状态。事务是为了保证DB操作正确性跟完整性的保障。
2 事务的四个特性
按照上一小节的描述,更多的像是在描述事务原子性这一特性。其实事务并不仅仅是只有原子性这一个特性。是由四部分组成 ACID。下面我们介绍一下这部分:
2.1 原子性(Atomicity)
原子性:事务的所有操作,要么全部完成,要么全部不完成,不会结束在某个中间环节。
这个是最基本的要求。
2.2 一致性(Consistency)
一致性:事务完成之后,事务所做的修改进行持久化保存,不会丢失。
一致性更像是其他三个特性综合之后的结果。需要依赖于它们。一致性可以从两个角度思考:一个是约束的一致性(比如主键、外键约束),另一种是业务逻辑上的一致性。
2.3 隔离性(Isolation)
隔离性:当多个事务并发访问数据库中的同一数据时,所表现出来的相互关系。
隔离性关系到多个事务并发操作时数据的准确性。innodb有四个常见的隔离性,这些隔离性会借助于锁机制 解决一些并发常见的问题。当然,因为隔离性强弱的不同,有些问题在某些场景下是解决不了的。下一节,我们将重点讲解这个问题。
2.4 持久性(Durability)
持久性:事务开始之前和事务结束之后,数据库的完整性限制未被破坏。
这儿主要是指持久化的问题。数据写入之后,要保证不会丢失,在系统崩溃之后,也能恢复。
3 四个特性背后是怎么实现的?
大多数的文章,讲完事务的四个特性就结束了。如果只是停留在这个层面,我们就没有写这篇文章的必要了。在这部分,我们会讲解一下四个特性是如何实现的?在讲解特性实现原理之前,我们先讲一下用到的几个知识点。
3.1 redo、undo与binlog
3.1.1 redo
redo, 重做日志,保证了事务的原子性与持久性。通常是物理日志,用于恢复事务修改的页操作。
redo 包括两部分:
- 1 内存中的重做日志缓冲,易失,一般在事务开始时,就要写入这部分
- 2 第二部分是重做日志,这个是要刷如磁盘的。一般在事务提交时会刷如磁盘。有些场景,也可以设置没用每次事务提交就刷,而是交给master thread 定期刷盘,但是这样如果在这期间,宕机了,则没有刷盘的数据就要丢失了。
redo 日志里面存储的是操作指令,每个指令根据操作类型的不同,存储格式也是不同的。
redo 在内存中,结合了checkpoint 以及LSN(log sequence number) 来保证刷入磁盘的数据不丢。checkpoint记录了刷磁盘刷到哪儿了,LSN记录了当前内存中的日志编号。二者之间的内容就是没有刷新到磁盘的日志。
3.1.2 undo
undo保证了事务的一致性。undo 并不是物理日志,而是逻辑上的日志,有一个专门的内存字段存储undo 日志。
undo 主要有两个作用:1是回滚操作,将数据库逻辑地恢复到原来的样子,但是已经分配的页、或者数据结果可能回滚不了了; 2 是MVCC(多版本并发控制),实现并发控制的一种很通用的机制,在下面的一节中,我们会讲到。
当事务提交的时候,innodb不会立即删除undo log,因为后续还可能会用到undo log,如隔离级别为repeatable read时,事务读取的都是开启事务时的最新提交行版本,只要该事务不结束,该行版本就不能删除,即undo log不能删除。
但是在事务提交的时候,会将该事务对应的undo log放入到删除列表中,未来通过purge来删除。并且提交事务时,还会判断undo log分配的页是否可以重用,如果可以重用,则会分配给后面来的事务,避免为每个独立的事务分配独立的undo log页而浪费存储空间和性能。
3.1.3 binlog
binlog基本定义:二进制日志,也成为二进制日志,记录对数据发生或潜在发生更改的SQL语句,并以二进制的形式保存在磁盘中;
作用:Mysql的作用类似于ORACLE的归档日志,可以用来查看数据库的变更历史(具体的时间点所有的SQL操作)、数据库增量备份和恢复(增量备份和基于时间点的恢复)、Mysql的复制(主主数据库的复制、主从数据库的复制)
3.1.4 更新操作中 redolog 与 binlog的顺序
3.2 WAL (write ahead logging) 又称 ARIES三原则
ARIES三原则,是指write ahead logging。
1 先写日之后写磁盘,日志成功写入后就不会丢失,后续由checkpoint机制来保证磁盘物理文件与redo日志达到一致性。
2 利用Redo 记录变更后的数据,即redo记录事务数据变更后的值
3 利用Undo 记录变更前的数据,用于回滚和其他事务多版本读。
3.3 特性的实现
3.3.1 各特性间的关系
在讲解具体实现之前,我们先来张图,参考自 《高性能mysql实战》
3.3.2 原子性的实现
每一个写事务,都会修改 Buffer Pool,从而产生相应的 Redo 日志,这些日志信息会被记录到 ib_logfiles 文件中。因为 Redo 日志是遵循 Write Ahead Log 的方式写的,所以事务是顺序被记录的。
任何 Buffer Pool 中的页被刷到磁盘之前,都会先写入到日志文件中。
回滚(undo日志)
要保证原子性,就必须在异常发生时,对已经执行的操作进行回滚,此时就用到了undo 日志
。
未刷盘数据提交(redo日志)
除了回滚之外,还有一种场景是事务提交了,日志写入到buffer pool 了,但是buffer pool的脏页 并没有刷盘,那此时怎么恢复呢?就需要用到redo日志恢复数据。
综合上述两种case,其实原子性的保证就是用到了WAL的原则。
3.3.3 持久性的实现
持久性是表示一个事务一旦提交,它对数据的改变就是永久的。通过原子性可以保证的一旦事务提交,即使遇到宕机,也可以从逻辑上将数据找回来,再次写入到物理存储空间。
因为redo日志是有限的,那么宕机之后,redo日志之前的数据怎么恢复呢,这就结合binlog日志。
3.3.4 隔离性的实现
innodb隔离性有四种,我们简单看一下四种隔离级别都是怎么实现的。下一篇文章中会详细介绍。
1、读未提交:没做任何控制。能够读到一个事务中的中间状态,是违背ACID的,所以在MySQL中基本不用。
2、读已提交(RC):通过对数据加了写锁,在写的过程中数据是不能被其他事务看到的。但是会存在不可重复读的问题。
3、可重复读(RR):通过增加间隙锁,解决了不可重读的问题,但是并不能对未存在的数据进行加锁操作,所以会存在幻读的问题。
4、可串行化:通过加锁,所有的操作都是单版本,串行化的。
3.3.5 一致性的实现
一致性可以归纳为完整性。而数据的完整性是通过上面三个特性来保证的,包括原子性、隔离性、持久性,而这三个特性又是通过Redo/Undo/binlog 来保证的。
4 总结
本文根据我自己的学习过程,简单整理了事务,事务特性,以及事务特性背后的技术原理。
5 参考文献
浅入浅出mysql https://draveness.me/mysql-innodb
浅入深出MySQL中事务的实现 https://draveness.me/mysql-transaction
详细分析MySQL事务日志(redo log和undo log) https://www.cnblogs.com/f-ck-need-u/archive/2018/05/08/9010872.html#auto_id_16
拉勾网的《高性能mysql实战》课程 https://kaiwu.lagou.com/course/courseInfo.htm?courseId=5#/content?courseId=5
6 其他
本文是mysql学习的第一篇-事务及其特性,希望对你有所帮助~
如果有疑问,可以直接留言,也可以关注公众号 “链人成长chainerup” 提问留言,或者加入知识星球“链人成长” 与我深度链接~