详解MySQL事务原理
老刘是即将找工作的研究生,自学大数据开发,一路走来,感慨颇深,网上大数据的资料良莠不齐,于是想写一份详细的大数据开发指南。这份指南把大数据的【基础知识】【框架分析】【源码理解】都用自己的话描述出来,让伙伴自学从此不求人。
您的点赞是我持续更新的动力,禁止白嫖,看了就要有收获,一起加油。
今天给大家分享的是大数据开发基础部分MySQL的事务,事务在MySQL知识点中非常重要的部分,很多伙伴只是知道MySQL的四大特性,但不知道其中的原理,老刘这次给大家详细的描述MySQL四大特性的原理,MySQL事务篇的大纲如下:
什么是事务?
在MySQL中的事务是由存储引擎实现的,而且支持事务的存储引擎不多,我们主要讲解InnoDB存储引擎中的事务。
事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行。
事务用来管理 DDL、DML、DCL 操作,比如 insert,update,delete 语句,默认是自动提交的。
事务的四大特性(ACID)
Atomicity(原子性):构成事务的的所有操作必须是一个逻辑单元,要么全部成功,要么全部失败。 Consistency(一致性):数据库在事务执行前后状态都必须是稳定的或者是一致的,就是说事务开始和结束后,数据库的完整性不会被破坏。 Isolation(隔离性):事务之间不会相互影响。由锁机制和MVCC机制来实现的,其中MVCC(多版本并发控制):优化读写性能(读不加锁、读写不冲突),四种隔离级别为RU(读未提交)、RC(读已提交)、RR(可重复读)、SERIALIZABLE (串行化)。 Durability(持久性):事务执行成功后必须全部写入磁盘,事务提交后,对数据的修改是永久性的,即使系统故障也不会丢失。
事务的使用
begin或start transaction:开启一个事务;
commit:提交一个事务,并使已对数据库进行的所有修改称为永久性的;
rollback:回滚会结束用户的事务,并撤销正在进行的所有未提交的修改。
ACID实现原理
下面我们就来详细讲解一下上述示例涉及的事务的ACID特性的具体实现原理。总结来说,事务的隔离性由多版本控制机制和锁实现,而原子性、一致性和持久性通过InnoDB的redo log、undo log和ForceLog at Commit机制来实现。
重做日志Redo Log
如果要存储数据则先存储数据的日志,一旦内存崩了,则可以从日志找重做日志保证了数据的可靠性,InnoDB采用了Write Ahead Log(预写日志)策略,即当事务提交时,先写重做日志,然后再择时将脏页写入磁盘。如果发生宕机导致数据丢失,就通过重做日志进行数据恢复。
回滚日志Undo Log
数据库崩溃重启后需要从redo log中把未落盘的脏页数据恢复出来,重新写入磁盘,保证用户的数据不丢失。当然,在崩溃恢复中还需要回滚没有提交的事务。由于回滚操作需要undo日志的支持,undo日志的完整性和可靠性需要redo日志来保证,所以崩溃恢复先做redo恢复数据,然后做undo回滚。
所以,在事务执行的过程中,除了记录redo log,还会记录一定量的undo log。undo log记录了数据在每个操作前的状态,如果事务执行过程中需要回滚,就可以根据undo log进行回滚操作。
Force Log at Commit机制
它实现事务的持久性,即当事务提交时,必须先将该事务的所有日志写入到重做日志文件进行持久化,然后事务的提交操作完成才算完成。为了确保每次日志都写入到重做日志文件,在每次将重做日志缓冲写入重做日志后,必须调用一次fsync操作(操作系统),将缓冲文件从文件系统缓存中真正写入磁盘。
总结一下就是redo log用于在崩溃时恢复数据,undo log用于对事务的影响进行撤销,也可以用于多版本控制。而Force Log at Commit机制保证事务提交后redo log日志都已经持久化。
原子性
原子性是指一个事务是一个不可分割的工作单位,其中的操作要么都做,要么都不做。例如银行转账要么成功,要么失败,是不存在中间的状态!
Undo Log是实现原子性的关键,靠的就是undo log。当事务对数据库进行修改时,InnoDB会生成对应的undo log。undo log它属于逻辑日志,它记录的是sql执行相关的信息。当发生回滚时,InnoDB会根据undo log的内容做与之前相反的工作:对于每个insert,回滚时会执行delete;对于每个delete,回滚时会执行insert;对于每个update,回滚时会执行一个相反的update,把数据改回去。
以update操作为例:当事务执行 update 时,其生成的 undo log 中会包含被修改行的主键(以便知道修改了哪些行)、修改了哪些列、这些列在修改前后的值等信息,回滚时便可以使用这些信息将数据还原到 update 之前的状态。
持久性
持久性是指事务执行成功后必须全部写入磁盘,事务提交后,对数据的修改是永久性的,即使系统故障也不会丢失。
InnoDB作为MySQL的存储引擎,数据是存放在磁盘中的,但如果每次读写数据都需要磁盘IO,效率会很低。为此,InnoDB提供了缓存(Buffer Pool),Buffer Pool中包含了磁盘中部分数据页的映射,作为访问数据库的缓冲:当从数据库读取数据时,会首先从Buffer Pool中读取,如果Buffer Pool中没有,则从磁盘读取后放入Buffer Pool;当向数据库写入数据时,会首先写入Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中。
虽然Buffer Pool的使用大大提高了读写数据的效率,但是也有别的问题,当MySQL宕机,而此时Buffer Pool中修改的数据还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证。
于是,优秀的程序员们引入了redo log,当我们对数据进行修改时,除了修改Buffer Pool中的数据,还会在redo log中记录这次操作。当事务提交时,会调用fsync接口对redo log进行刷盘。如果MySQL宕机,重启时可以读取redo log中的数据,对数据库进行恢复。
还有一点必须知道就是redo log采用的是WAL策略,所有修改先写入日志,再更新到Buffer Pool,保证了数据不会因MySQL宕机而丢失,从而满足了持久性要求。
隔离性
在MySQL隔离性中,一般有两种情况:
要求同一时刻只能有一个事务对数据进行写操作,InnoDB通过锁机制来保证这一点。 在进行读操作的时候,可能出现脏读、不可重复读、幻读的问题。
首先讲第一种情况,MySQL要求同一时刻只能有一个事务对数据进行写操作,InnoDB通过锁机制来保证这一点。
锁机制的基本原理可以理解为:事务在修改数据之前,需要先获得相应的锁;获得锁之后,事务便可以修改数据;该事务操作期间,这部分数据是锁定的,其他事务如果需要修改数据,需要等待当前事务提交或回滚后释放锁。
至于锁机制中的锁,一般就是之前讲到的MySQL锁,大家可以去看看这篇MySQL锁的内容。
接着讲第二种情况,读操作可能出现脏读、不可重复读、幻读的问题。
隔离性追求的是并发情形下事务之间互不干扰,但是在事务的并发操作中可能会出现一些问题:
丢失更新:两个事务针对同一数据都发生修改操作时,会存在丢失更新问题。 脏读:对于两个事务 T1,T2,T1 读取了已经被 T2 更新但还没有被提交的字段。之后,若 T2 回滚,T1读取的内容就是临时且无效的。 不可重复读:对于两个事务T1,T2,T1 读取了一个字段,然后 T2 更新了该字段。之后,T1再次读取同一个字段,发现字段的内容不一样。要求,多次读取数据的时候,在一个事务中读出的都应该是一样的。一般是由于 update 操作引发,所以将来执行的时候要特别注意。 幻读:对于两个事务T1,T2,T1 从一个表中读取了一个字段,然后 T2 在该表中插入了一些新的行。之后。如果 T1 再次读取同一个表,就会多出几行。就是发现数据的数量不一样。要求,在一个事务中多次去读取数据的时候都应该是一样的。
虽然有上述这些问题,但MySQL数据库为我们提供的四种隔离级别(由低到高):
Read uncommitted (读未提交):最低级别,任何情况都无法保证。 Read committed (RC,读已提交):可避免脏读的发生。 Repeatable read (RR,可重复读):可避免脏读、不可重复读的发生。(InnoDB默认级别为RR,它可以解决幻读,主要原因是Next-Key(Gap)锁,只有RR才能使用Next-Key锁) Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
解决脏读、不可重复读、幻读的问题使用的是MVCC,即多版本的并发控制协议。它说的就是在同一时刻,不同的事务读取到的数据可能是不同的(即多版本)。
MVCC最大的优点是读不加锁,因此读写不冲突,并发性能好。InnoDB实现MVCC,多个版本的数据可以共存,主要是依靠数据的隐藏列( 也可以称之为标记位 )和undo log。其中数据的隐藏列包括了该行数据的版本号、删除时间、指向undo log的指针等等;当读取数据时,MySQL可以通过隐藏列判断是否需要回滚并找到回滚需要的undo log,从而实现MVCC。
MVCC如何解决脏读、不可重复读、幻读的问题
1、MVCC解决脏读
当事务T1在第三个时刻读取自己的余额时,会发现数据已被T2事务修改,并且T2的状态还没有提交。此时事务A读取最新数据后,根据数据的undo log执行回滚操作,得到事务T2修改前的数据,从而避免了脏读。
2、MVCC解决不可重复读
当事务T1在第二个时刻第一次读取数据时,会记录该数据的版本号(数据的版本号是以row为单位记录的),假设版本号为1;当事务T2对自己的余额进行修改并且提交时,该行记录的版本号增加,假设版本号为2;当事务T1在第五个时刻再一次读取数据时,发现数据的版本号2大于第一次读取时记录的版本号1,因此会根据undo log执行回滚操作,得到版本号为1时的数据,从而实现了可重复读。
3、MVCC解决幻读
InnoDB实现的RR通过next-key lock机制避免了幻读现象。
next-key lock是行锁的一种,实现相当于record lock(记录锁) + gap lock(间隙锁),它的特点是不仅会锁住记录本身(record lock的功能),还会锁定一个范围(gap lock的功能)。
当事务T1在第二个时刻第一次读取0<id<5数据时,会进行标记,标记内容包括数据的版本号等,并且标记的不只是id=1的数据,还将范围(0,5)进行了标记。我们接着在第三个时刻插入新的用户并且提交事务,最后第五个时刻再次读取0<id<5数据时,便可以发现id=2的数据比之前标记的版本号更高,此时再结合undo log执行回滚操作,避免了幻读。
稍微总结下,InnoDB通过锁机制、数据的隐藏列、undo log和类next-key lock,实现了一定程度的隔离性,可以满足大多数场景的需要。不过需要说明的是,RR虽然避免了幻读问题,但是毕竟不是Serializable,不能保证完全的隔离。
一致性
一致性是事物追求的最终目标,前面提到的原子性,隔离性,持久性都是为了保证数据库的一致性。也就是说ACID四大特性之中,C(一致性)是目的,A(原子性)、I(隔离性)、D(持久性)是手段,是为了保证一致性,数据库提供的手段。数据库必须要实现AID三大特性,才有可能实现一致性。
总结
本文作为大数据开发指南MySQL的第四篇详细介绍了MySQL事务的内容,尤其是MySQL四大特性的原理。希望大家能够跟着老刘的文章,好好捋捋思路,争取能够用自己的话把这些知识点讲述出来!
尽管当前水平可能不及各位大佬,但老刘会努力变得更加优秀,让各位小伙伴自学从此不求人!
大数据开发指南地址如下:
github:https://github.com/BigDataLaoLiu/BigDataGuide 码云:https://gitee.com/BigDataLiu/BigDataGuide 如果有相关问题,联系公众号:努力的老刘。文章都看到这了,点赞关注支持一波!