📫作者简介:小明java问道之路,2022年度博客之星全国TOP3,专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化,文章内容兼具广度、深度、大厂技术方案,对待技术喜欢推理加验证,就职于知名金融公司后端高级工程师。
📫 热衷分享,喜欢原创~ 关注我会给你带来一些不一样的认知和成长。
🏆 2022博客之星TOP3 | CSDN博客专家 | 后端领域优质创作者 | CSDN内容合伙人
🏆 InfoQ(极客邦)签约作者、阿里云专家 | 签约博主、51CTO专家 | TOP红人、华为云享专家
🔥如果此文还不错的话,还请👍关注、点赞、收藏三连支持👍一下博主~
🍅 文末获取联系 🍅 👇🏻 精彩专栏推荐订阅收藏 👇🏻
专栏系列(点击解锁)
学习路线(点击解锁)
知识定位
🔥Redis从入门到精通与实战🔥
Redis从入门到精通与实战
围绕原理源码讲解Redis面试知识点与实战
🔥MySQL从入门到精通🔥
MySQL从入门到精通
全面讲解MySQL知识与企业级MySQL实战 🔥计算机底层原理🔥
深入理解计算机系统CSAPP
以深入理解计算机系统为基石,构件计算机体系和计算机思维
Linux内核源码解析
围绕Linux内核讲解计算机底层原理与并发
🔥数据结构与企业题库精讲🔥
数据结构与企业题库精讲
结合工作经验深入浅出,适合各层次,笔试面试算法题精讲
🔥互联网架构分析与实战🔥
企业系统架构分析实践与落地
行业最前沿视角,专注于技术架构升级路线、架构实践
互联网企业防资损实践
互联网金融公司的防资损方法论、代码与实践
🔥Java全栈白宝书🔥
精通Java8与函数式编程
本专栏以实战为基础,逐步深入Java8以及未来的编程模式
深入理解JVM
详细介绍内存区域、字节码、方法底层,类加载和GC等知识
深入理解高并发编程
深入Liunx内核、汇编、C++全方位理解并发编程
Spring源码分析
Spring核心七IOC/AOP等源码分析
MyBatis源码分析
MyBatis核心源码分析
Java核心技术
只讲Java核心技术
本文目录
本文导读
一、什么是Redo Log、什么是Undo Log
二、MySQL实现ACID的原理
1、原子性(Atomicity)
1.1、MySQL事务原子性的特性
1.2、事务原子性实现原理(Undo log)
2、一致性(Consistency)
2.1、MySQL事务一致性的特点
2.2、一致性(Consistency)的实现原理
3、隔离性(Isolation)
3.1、MySQL事务隔离性的特点
3.2、MySQL事务隔离性的实现原理(锁、MVCC)
3.2.1、MySQL事务隔离性-锁机制
3.2.2、MySQL事务隔离性-MVCC
3.3、MySQL锁辨析
4、持久性(Durability)
4.1、MySQL事务持久性的特点
4.2、持久性的实现原理
4.3、Redo log为什么比直接修改Buffer Pool快
总结
本文导读
本文是《MVCC详解与MVCC实现原理》、《MySQL事务隔离机制与实现原理详解》的后续内容补充,如果不了解MySQL事务隔离机制、MVCC实现原理请先学习上述两篇文章。
一、什么是Redo Log、什么是Undo Log
MySQL InnoDB提供了两种类型的事务日志,Redo Log(重做日志)和undo Log(回滚日志)。Redo Log会保存操作的正向日志,Undo Log则相反。
举个例子,新增一条数据时,Undo Log会新增一条insert SQL,而Undo Log则是生成一条delete where id =id 的SQL 日志,而修改时,Redo Log记录update SQL ,而Undo Log 则是修改内容和条件相反。
简单理解为 Redo Log 是保存要去做的事情,Undo Log就是用来保存撤回之前完成的事情。
二、MySQL实现ACID的原理
1、原子性(Atomicity)
1.1、MySQL事务原子性的特性
事务是作为一个整体执行的,其中包含的数据库的所有SQL语句要么执行,要么不执行。
如果对于一个事务来说其中的SQL语句执行失败,则已经执行的语句也必须回滚,数据库退回到事务之前的状态。
1.2、事务原子性实现原理(Undo log)
实现原子性的关键是当事务回滚时能够撤销所有已经成功执行的SQL语句。
当事务对数据库进行修改时,InnoDB会生成对应的 Undo log;如果事务执行失败或调用了 rollback,导致事务需要回滚,便可以利用 Undo log 中的信息将数据回滚到修改之前的样子。
Undo log 属于逻辑日志,它记录的是SQL执行相关的信息。当发生回滚时,InnoDB 会根据 Undo log 的内容做与之前相反的工作:
对于每个 insert,回滚时会执行 delete;
对于每个 delete,回滚时会执行insert;
对于每个 update,回滚时会执行一个相反的 update,把数据改回去。
以update操作为例:当事务执行update时,其生成的Undo log中会包含被修改行的主键(以便知道修改了哪些行)、修改了哪些列、这些列在修改前后的值等信息,回滚时便可以使用这些信息将数据还原到update之前的状态。
Undo log (回滚日志)是采用段(segment)的方式来记录的,每个Undo操作在记录的时候占用一个Undo log segment。
在数据更改操作时,记录了相对应的Undo log的目的在于:
1、记录事务发生之前的一个版本,用于回滚;
2、通过mvcc + Undo log实现Innodb事务可重复读和读已提交隔离级别。
2、一致性(Consistency)
2.1、MySQL事务一致性的特点
一致性是指事务执行结束后,数据的完整性不能被破坏,业务的一致性不能被破坏,事务执行的前后都是合法的数据状态。
可以说,一致性是事务追求的最终目标,原子性、持久性和隔离性,如果这些特性无法保证,事务的一致性也无法保证。此外除了数据库底层的保障,一致性的实现也需要应用层的保障。
数据库的完整性包括但是不限于:
实体完整性(如行的主键存在且唯一)、列完整性(如字段的类型,大小,长度符合要求),、外键约束(外键约束还存在)
业务的一致性(如转账前后,不管事务成功还是失败,两个账户的和应该是不变的)
事务的一致性决定了一个系统设计和实现的复杂度,因为事务可以有不同程度的一致性:
1、强一致性:无论更新操作实在哪一个数据副本执行,之后所有的读操作都能获得最新的数据。
2、弱一致性:提交的更新操作,不一定立即会被读操作读到,需要一段时间,此种情况会存在一个不一致窗口。
3、最终一致性:事务更新一份数据,最终一致性保证在没有其他事务更新同样的值的话,最终所有的事务都会读到之前事务更新的最新值。如果没有错误发生,不一致窗口的大小依赖于:通信延迟,系统负载等。
2.2、一致性(Consistency)的实现原理
一致性是通过事务的原子性、持久性和隔离性来保证的,所以对于原子性、持久性、隔离性的实现原理会着重讲解
3、隔离性(Isolation)
3.1、MySQL事务隔离性的特点
并发访问数据库时,一个事务不被其他事务所干扰,或者说隔离性是事务所操作的数据在提交之前,对其他事务的可见程度。
在SQL 92标准定义了四个事务隔离级别:
读未提交(RU,Read Uncommitted):允许事务在执行过程中,读取其他事务尚未提交的数据;
读已提交(RC,Read Committed):一个事务提交之后,它做的变更才会被其他事务看到
可重复读(RR,Repeatable Read):在同一个事务内,任意时刻的查询结果都是一致的,无论是否有其他事务对这份数据进行操作,以及这个事务是否提交。(InnoDB默认级别)
串行化读(serializable ):所有事务逐个依次执行,每次读都需要获取表级共享锁,读写会相互阻塞。
并发并发访问数据库时可能发生的问题:
脏读:脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。
不可重复读:不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。
幻读:幻读是指当事务不是独立执行时发生的一种现象,比如事务1对某个数据项做了从“1”修改为“2”的操作,事务2又插入了一行数据项,而这个数据项的数值还是为“1”,操作事务1的用户如果查看刚刚修改的数据,会发现还有一行没有修改。
不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。
幻读和不可重复读的区别是,幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。不可重复度则针对UPDATE,幻读通常针对的是INSERT。
3.2、MySQL事务隔离性的实现原理(锁、MVCC)
主要是运用了锁机制和操作日志和隐藏数据列(mvcc)来实现的。
3.2.1、MySQL事务隔离性-锁机制
MySQL锁机制的基本工作原理就是:事务在修改数据库之前,需要先获得相应的锁,获得锁的事务才可以修改数据;在该事务操作期间,这部分的数据是锁定,其他事务如果需要修改数据,需要等待当前事务提交或回滚后释放锁。
在MySQL事务中,锁的实现与隔离级别有关。
在RR(Repeatable Read)隔离级别下,MySQL使用间隙锁(间隙锁是innodb中行锁的一种,使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据)来防止以并行性为代价写入数据,以解决幻读的问题。
排它锁解决脏读
共享锁解决不可重复读
3.2.2、MySQL事务隔离性-MVCC
MVCC是通过在每行记录后面保存三个隐藏的列来实现的,一个保存了行的事务ID(每次提交事务,事务ID会自增),一个保存了行的回滚段指针
事务开始时刻会把该事务ID放到当前事务影响的行事务ID字段中,而 DB_ROLL_PTR 指向该行回滚段的指针,该行记录上的所有版本数据,都在undo log回滚日志中通过链表形式组织,所以该值实际指向undo log中该行的历史记录链表。
在可重复读(RR) 的隔离级别下,MVCC具体操作:
SELECT操作:InnoDB遵循以下两个规则:只查找数据行的事务ID小于或等于当前事务ID的版本,这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的记录。行的删除版本要未被定义,读取到事务开始之前状态的版本,这可以确保事务读取到的行,在事务开始之前未被删除。只有同时满足的两者的记录,才能返回作为查询结果。
INSERT:InnoDB为新插入的每一行保存当前事务编号作为行版本号。
DELETE:InnoDB为删除的每一行保存当前事务编号作为行删除标识。
UPDATE:InnoDB为插入一行新记录,保存当前事务编号作为行版本号,同时保存当前事务编号到原来的行作为行删除标识。
在并发访问数据库时,对正在事务中的数据做MVCC多版本的管理,以避免写操作阻塞读操作,并且可以通过比较版本解决幻读。
MVCC只在 可重复度(RR) 和 读已提交(RC) 两个隔离级别下才会工作,其中,MVCC实质就是通过保存数据在某个时间点的快照来实现的。
在RC(Read Commited )的隔离级别下,每次快照读取都会生成并获得最新的 readview。在RR(Repeatable Read)隔离级别,只有读取同一事务的第一个快照才能创建 readview。每个后续快照读取都使用相同的 readview,因此每个查询结果都相同。
快照读:不加锁的 select 操作就是快照读,即无锁的非阻塞读取;
当前读:select lock in share mode (共享锁), select for update; update; insert; delete (排他锁)这些操作都是一种当前读。读取最新版本的记录,读取时,它还确保其他并发事务无法修改当前记录,并锁定读取的记录。
3.3、MySQL锁辨析
InnoDB 实现了标准的行级锁,包括两种:共享锁(简称 s 锁)、排它锁(简称 x 锁)。
行锁的实现依赖于索引,一旦某个加锁操作没有使用到索引,那么该锁就会退化为表锁。
记录锁存在于包括主键索引在内的唯一索引中,锁定单条索引记录,记录锁也是排它(X)锁,所以会阻塞其他事务对其插入、更新、删除。记录锁是锁住记录,锁住索引记录,而不是真正的数据记录。
间隙锁存在于非唯一索引中,锁定开区间范围内的一段间隔,它是基于临键锁实现的。
临键锁(Next-Key Locks 是记录锁和间隙锁的组合)于非唯一索引中,该类型的每条记录的索引上都存在这种锁,它是一种特殊的间隙锁,锁一段左开右闭的索引区间。
4、持久性(Durability)
4.1、MySQL事务持久性的特点
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
4.2、持久性的实现原理
持久性的实现关键在于Redo Log日志,在执行SQL时会保存已执行的SQL语句到一个指定的Log文件,当执行recovery时重新执行Redo Log记录的SQL操作。
当向数据库写入数据时,执行过程会首先写入Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中(这一过程叫做刷盘),这整一过程称为Redo Log。
Redo Log 分为:1、Buffer Pool内存中的日志缓冲(Redo Log buffer),该部分日志是易失性的;2、磁盘上的重做日志文件(Redo Log file),该部分日志是持久的。
Buffer Pool的使用可以大大提高了读写数据的效率,但是也带了新的问题:如果MySQL宕机,而此时Buffer Pool中修改的数据在内存还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证。
为了确保事务的持久性,Redo Log被引入来解决这个问题:当数据修改时,除了修改Buffer Pool中的数据,还会在Redo Log记录这次操作;
当事务提交时,会调用fsync接口对Redo Log进行刷盘。
如果MySQL宕机,重启时可以读取Redo Log中的数据,对数据库进行恢复。Redo Log采用的是WAL(Write-ahead Logging,预写式日志),所有修改先写入日志,再更新到Buffer Pool,保证了数据不会因MySQL宕机而丢失,从而满足了持久性要求。
4.3、Redo log为什么比直接修改Buffer Pool快
既然Redo Log也需要在事务提交时将日志写入磁盘,为什么它比直接将Buffer Pool中修改的数据写入磁盘(即刷脏)要快呢?
1、刷脏是随机IO,因为每次修改的数据位置随机,但写Redo Log是追加操作,属于顺序IO。
2、刷脏是以数据页(Page)为单位的,MySQL默认页大小是16KB,一个Page上一个小修改都要整页写入;而Redo Log中只包含真正需要写入的部分,无效IO大大减少。
4.4、RedoLog与BinLog辨析
在MySQL中还存在BinLog(二进制日志)也可以记录写操作并用于数据的恢复,但二者是有着根本的不同的:
作用不同:Redo Log是用于crash recovery的,保证MySQL宕机也不会影响持久性;BinLog是用于point-in-time recovery的,保证服务器可以基于时间点恢复数据,此外BinLog还用于主从复制
层次不同:Redo Log是InnoDB存储引擎实现的,而BinLog是MySQL的服务器层(可以参考文章前面对MySQL逻辑架构的介绍)实现的,同时支持InnoDB和其他存储引擎
内容不同:Redo Log是物理日志,内容基于磁盘的Page;BinLog的内容是二进制的,根据BinLog_format参数的不同,可能基于sql语句、基于数据本身或者二者的混合
写入时机不同:BinLog在事务提交时写入,Redo Log的写入时机相对多元
总结
1、原子性是通过MySQL的Undo Log来实现的:当事务对数据库进行修改时,InnoDB会生成对应的undo log;如果事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。
2、MySQL会通过mvcc + Undo Log实现Innodb事务可重复读和读已提交隔离级别。
3、Innodb事务的隔离级别是由锁机制和MVVC(多版本并发控制)实现的
4、在执行SQL时会保存已执行的SQL语句到一个指定的Log文件,当执行recovery时重新执行Redo Log记录的SQL操作。
5、一致性是通过原子性、持久性和隔离性来保证的