图解mysql事务实现原理

什么是事务?
事务是一组原子性的sql语句,或者说是一个独立的工作单元。事务有四个特性,原子性(Atomicity)、一致性(Consistency)、隔离型(Isolation)以及持久性(Durability)。今天想跟大家一起研究下事务内部到底是怎么实现的。

事务无非是要做到可靠性以及并发处理
可靠性:数据库要保证当insert或update操作时抛异常或者数据库crash的时候需要保障数据的操作前后的一致,想要做到这个,我需要知道我修改之前和修改之后的状态,所以就有了undo log和redo log。
并发处理:也就是说当多个并发请求过来,并且其中有一个请求是对数据修改操作的时候会有影响,为了避免读到脏数据,所以需要对事务之间的读写进行隔离,至于隔离到啥程度得看业务系统的场景了,实现这个就得用MySQL 的隔离级别。

下面我首先讲实现事务功能的三个技术,分别是日志文件(redo log 和 undo log),锁技术以及MVCC,然后再讲事务的实现原理,包括原子性是怎么实现的,隔离型是怎么实现的等等。最后在做一个总结,希望大家能够耐心看完

一:redo log 与 undo log介绍

1、redo log

什么是redo log ?
redo log叫做重做日志,用来实现事务的持久性。该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都会存到该日志中。

redo log 有什么作用?
mysql 为了提升性能不会把每次的修改都实时同步到磁盘,而是会先存到Boffer Pool(缓冲池)里头,把这个当作缓存来用。然后使用后台线程去做缓冲池和磁盘之间的同步。
那么问题来了,如果还没来的同步的时候宕机或断电了怎么办?这样会导致丢部分已提交事务的修改信息!
所以引入了redo log来记录已成功提交事务的修改信息,并且会把redo log持久化到磁盘,系统重启之后在读取redo log恢复最新数据。

总结:
redo log是用来恢复数据的用于保障,已提交事务的持久化特性。

2、undo log

什么是 undo log ?
undo log 叫做回滚日志,用于记录数据被修改前的信息。它正好跟前面所说的重做日志所记录的相反,重做日志记录数据被修改后的信息。undo log主要记录的是数据的逻辑变化,为了在发生错误时回滚之前的操作,需要将之前的操作都记录下来,然后在发生错误时才可以回滚。

undo log 有什么作用?
undo log 记录事务修改之前版本的数据信息,因此假如由于系统错误或者rollback操作而回滚的话可以根据undo log的信息来进行回滚到没被修改前的状态。

总结:
undo log是用来回滚数据的用于保障未提交事务的原子性。

二:mysql锁技术以及MVCC基础

1、mysql锁技术

当有多个请求来读取表中的数据时可以不采取任何操作。但是多个请求里有读请求,又有修改请求时必须有一种措施来进行并发控制。不然很有可能会造成不一致。

读写锁
解决上述问题很简单,只需用两种锁的组合来对读写请求进行控制即可,这两种锁被称为:
①共享锁(shared lock),又叫做"读锁"
读锁是可以共享的,或者说多个读请求可以共享一把锁读数据,不会造成阻塞。
②排他锁(exclusive lock),又叫做"写锁"
写锁会排斥其他所有获取锁的请求,一直阻塞,直到写入完成释放锁。
在这里插入图片描述
总结:
通过读写锁,可以做到读读并行,但不能做到写读,写写并行。事务的隔离性就是根据读写锁来实现的!!

2、MVCC机制

MVCC (MultiVersion Concurrency Control) 叫做多版本并发控制机制。(它的主要实现思想是通过数据多版本来做到读写分离。从而实现不加锁读进而做到读写并行。

Mysql在可重复读隔离级别下如何保证事务较高的隔离性?同样的sql查询语句在一个事务里多次执行查询结果相同,就算其它事务对数据有修改也不会影响当前事务sql语句的查询结果。
这个隔离性就是靠MVCC机制来保证的,对一行数据的读和写两个操作默认是不会通过加锁互斥来保证隔离性,避免了频繁加锁互斥。而在串行化隔离级别为了保证较高的隔离性是通过将所有操作加锁互斥来实现的。
Mysql在读已提交和可重复读隔离级别下都实现了MVCC机制。

undo日志版本链与read view机制详解
undo日志版本链是指一行数据被多个事务依次修改过后,在每个事务修改完后,Mysql会保留修改前的数据undo回滚日志,并且用两个隐藏字段trx_id和roll_pointer把这些undo日志串联起来形成一个历史记录版本链
在这里插入图片描述
MVCC机制的实现就是通过read-view机制与undo版本链比对机制,使得不同的事务会根据数据版本链对比规则读取同一条数据在版本链上的不同版本数据。

MVCC在mysql中的实现依赖的是undo log与read view
undo log:undo log 中记录某行数据的多个版本的数据。
read view:用来判断当前版本数据的可见性。

三:事务的实现

前面讲的重做日志(redo log),回滚日志(undo log)、锁技术以及MVCC机制就是实现事务的基础。

事务的原子性是通过undo log来实现的。
事务的持久性是通过redo log来实现的。
事务的隔离性是通过 (读写锁 + MVCC)来实现的。
事务的一致性是通过原子性,持久性,隔离性来实现的!!!

1、原子性的实现

什么是原子性:
一个事务必须被视为不可分割的最小工作单位,一个事务中的所有操作要么全部成功提交,要么全部失败回滚,对于一个事务来说不可能只执行其中的部分操作,这就是事务的原子性。
上面这段话取自《高性能MySQL》这本书对原子性的定义,原子性可以概括为就是要实现要么全部失败,要么全部成功。
以上概念相信大家伙儿都了解,那么数据库是怎么实现的呢?
就是通过回滚操作。 所谓回滚操作就是当发生错误异常或者显式的执行rollback语句时需要把数据还原到原先的模样,所以这时候就需要用到undo log来进行回滚,接下来看一下undo log在实现事务原子性时是怎么发挥作用的

1.1 undo log 的生成

假设有两个表 bank和finance,表中原始数据如图所示,当进行插入、删除以及更新操作时生成的undo log如下面图所示:
在这里插入图片描述
在这里插入图片描述
从上图可以了解到数据的变更都伴随着回滚日志的产生:
(1) 产生了被修改前数据(zhangsan,1000) 的回滚日志
(2) 产生了被修改前数据(zhangsan,0) 的回滚日志

根据上面流程可以得出如下结论:
1、每条数据变更(insert/update/delete)操作都伴随一条undo log的生成,并且回滚日志必须先于数据持久化到磁盘上。
2、所谓的回滚就是根据回滚日志做逆向操作,比如delete的逆向操作为insert,insert的逆向操作为delete,update的逆向为update等。

1.2 根据undo log 进行回滚

为了做到同时成功或者失败,当系统发生错误或者执行rollback操作时需要根据undo log 进行回滚
在这里插入图片描述
回滚操作就是要还原到原来的状态,undo log记录了数据被修改前的信息以及新增和被删除的数据信息,根据undo log生成回滚语句,比如:
(1) 如果在回滚日志里有新增数据记录,则生成删除该条的语句
(2) 如果在回滚日志里有删除数据记录,则生成生成该条的语句
(3) 如果在回滚日志里有修改数据记录,则生成修改到原先数据的语句

2、持久性的实现

事务一旦提交,其所作做的修改会永久保存到数据库中,此时即使系统崩溃修改的数据也不会丢失。

先了解一下MySQL的数据存储机制,MySQL的表数据是存放在磁盘上的,因此想要存取的时候都要经历磁盘IO,然而即使是使用SSD磁盘IO也是非常消耗性能的。 为此,为了提升性能InnoDB提供了缓冲池(Buffer Pool),Buffer Pool中包含了磁盘数据页的映射,可以当做缓存来使用:
读数据:会首先从缓冲池中读取,如果缓冲池中没有,则从磁盘读取在放入缓冲池;
写数据:会首先写入缓冲池,缓冲池中的数据会定期同步到磁盘中;
上面这种缓冲池的措施虽然在性能方面带来了质的飞跃,但是它也带来了新的问题,当MySQL系统宕机,断电的时候可能会丢数据!!!
因为我们的数据已经提交了,但此时是在缓冲池里头,还没来得及在磁盘持久化,所以我们急需一种机制需要存一下已提交事务的数据,为恢复数据使用。
于是 redo log就派上用场了。下面看下redo log是什么时候产生的
既然redo log也需要存储,也涉及磁盘IO为啥还用它?
在这里插入图片描述
(1)redo log 的存储是顺序存储,而缓存同步是随机操作。
(2)缓存同步是以数据页为单位的,每次传输的数据大小大于redo log。

3、隔离性实现

常见问题

脏读:事务读取到其它事务还未提交的数据。
不可重复读:在同一次事务中前后查询的数据不一致。
幻读:一次事务中前后查询的数据量发生了变化。

快照读和当前读

快照读:在MySQL中,查询语句分为两种,一种是简单的select操作,属于快照读,不加锁。它读的是记录的快照版本

select * from table where ?;

当前读:另一种是要加锁的特殊的读操作,它读的是记录的最新版本。

-- 共享读锁
select * from table where ? lock in share mode;
-- 共享写锁
select * from table where ? for update;
-- 增删改也属于当前读,因为要先看这条记录在不在
insert into table values ();
update table set ? where ?;
delete from table where ?;

隔离性是事务ACID特性里最复杂的一个。在SQL标准里定义了四种隔离级别,每一种级别都规定一个事务中的修改,哪些是事务之间可见的,哪些是不可见的。级别越低的隔离级别可以执行越高的并发,但同时实现复杂度以及开销也越大。
Mysql 隔离级别有以下四种(级别由低到高):
READ UNCOMMITED (读未提交)
READ COMMITED (读已提交)
REPEATABLE READ (可重复读)
SERIALIZABLE (序列化)

只要彻底理解了隔离级别以及他的实现原理就相当于理解了ACID里的隔离型。前面说过原子性,隔离性,持久性的目的都是为了要做到一致性,但隔离型跟其他两个有所区别,原子性和持久性是为了要实现数据的可性保障靠,比如要做到宕机后的恢复,以及错误后的回滚。
那么隔离性是要做到什么呢? ** 隔离性是要管理多个并发读写请求的访问顺序。** 这种顺序包括串行或者是并行。
说明一点,写请求不仅仅是指insert操作,又包括update操作。
在这里插入图片描述
总之,从隔离性的实现可以看出这是一场数据的可靠性与性能之间的权衡。
可靠性高的,并发性能低(比如 Serializable)
可靠性低的,并发性能高(比如 Read Uncommited)

READ UNCOMMITTED(读未提交)

在READ UNCOMMITTED隔离级别下,事务中的修改即使还没提交,对其他事务是可见的。事务可以读取未提交的数据,造成脏读。因为读不会加任何锁,所以写操作在读的过程中修改数据,所以会造成脏读。好处是可以提升并发处理性能,能做到读写并行。
换句话说,读的操作不能排斥写请求。
在这里插入图片描述
优点:读写并行,性能高
缺点:造成脏读

READ COMMITTED(读已提交)

一个事务的修改在他提交之前的所有修改,对其他事务都是不可见的。其他事务能读到已提交的修改变化。在很多场景下这种逻辑是可以接受的。

RC在RU的基础上采用MVCC机制来解决脏读的问题

但是该级别会产生不可重读以及幻读问题。
在该隔离级别下每次 select时都会新生成一个read-view,所以每次select读取的是不同的副本。
在这里插入图片描述

REPEATABLE READ(可重复读)

Mysql默认隔离级别

在一个事务内的多次读取的结果是一样的。这种级别下可以避免,脏读,不可重复读等查询问题。

RR也采用的是MVCC机制,那么相比较RC来说是如何解决不可重复读问题的呢?
RR仅在第一次执行快照读时生成read-view,后续快照读复用
在这里插入图片描述
为什么能可重复度?因为多次读取只生成一个版本,读到的自然是相同数据。
优点:读写并行
缺点:实现的复杂度高

InnoDB在RR隔离级别下解决了幻读问题,但是没有完全的解决
在快照读下因为采用的是MVCC机制没有加锁,所以没有解决幻读问题。(所以如果后续要对数据进行写操作,还是通过for update语句上锁比较稳妥)
在当前读下通过 间隙锁 + 行锁 解决了幻读问题。

SERIALIZABLE(序列化)

全部加锁,可以解决脏读、不可重复读、幻读。
在这里插入图片描述

总结
实现事务采取了哪些技术以及思想?
原子性:使用undo log,从而达到回滚
持久性:使用redo log,从而达到故障后恢复
隔离性:使用锁以及MVCC,运用的优化思想有读写分离,读读并行,读写并行
一致性:通过回滚,以及恢复,和在并发环境下的隔离做到一致性。

作者:stevenyeahnet
链接:https://www.jianshu.com/p/93dc93d2aebc

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小本科生debug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值