相信你们都用过事务以及了解他的特色,如原子性(Atomicity),一致性(Consistency),隔离型(Isolation)以及持久性(Durability)等。今天想跟你们一块儿研究下事务内部究竟是怎么实现的,在讲解前我想先抛出个问题:mysql
事务想要作到什么效果?sql
按我理解,无非是要作到可靠性以及并发处理数据库
可靠性:数据库要保证当insert或update操做时抛异常或者数据库crash的时候须要保障数据的操做先后的一致,想要作到这个,我须要知道我修改以前和修改以后的状态,因此就有了undo log和redo log。缓存
并发处理:也就是说当多个并发请求过来,而且其中有一个请求是对数据修改操做的时候会有影响,为了不读到脏数据,因此须要对事务之间的读写进行隔离,至于隔离到啥程度得看业务系统的场景了,实现这个就得用MySQL 的隔离级别。并发
下面我首先讲实现事务功能的三个技术,分别是日志文件(redo log 和 undo log),锁技术以及MVCC,而后再讲事务的实现原理,包括原子性是怎么实现的,隔离型是怎么实现的等等。最后在作一个总结,但愿你们可以耐心看完ide
redo log与undo log介绍性能
mysql锁技术以及MVCC基础优化
事务的实现原理spa
总结线程
什么是redo log ?
redo log叫作重作日志,是用来实现事务的持久性。该日志文件由两部分组成:重作日志缓冲(redo log buffer)以及重作日志文件(redo log),前者是在内存中,后者在磁盘中。当事务提交以后会把全部修改信息都会存到该日志中。假设有个表叫作tb1(id,username) 如今要插入数据(3,ceshi)
start transaction;
select balance from bank where name="zhangsan";
// 生成 重作日志 balance=600
update bank set balance = balance - 400;
// 生成 重作日志 amount=400
update finance set amount = amount + 400;
commit;
redo log 有什么做用?
mysql 为了提高性能不会把每次的修改都实时同步到磁盘,而是会先存到Boffer Pool(缓冲池)里头,把这个看成缓存来用。而后使用后台线程去作缓冲池和磁盘之间的同步。
那么问题来了,若是还没来的同步的时候宕机或断电了怎么办?还没来得及执行上面图中红色的操做。这样会致使丢部分已提交事务的修改信息!
因此引入了redo log来记录已成功提交事务的修改信息,而且会把redo log持久化到磁盘,系统重启以后在读取redo log恢复最新数据。
总结:
redo log是用来恢复数据的 用于保障,已提交事务的持久化特性
什么是 undo log ?
undo log 叫作回滚日志,用于记录数据被修改前的信息。他正好跟前面所说的重作日志所记录的相反,重作日志记录数据被修改后的信息。undo log主要记录的是数据的逻辑变化,为了在发生错误时回滚以前的操做,须要将以前的操做都记录下来,而后在发生错误时才能够回滚。
还用上面那两张表
每次写入数据或者修改数据以前都会把修改前的信息记录到 undo log。
undo log 有什么做用?
undo log 记录事务修改以前版本的数据信息,所以假如因为系统错误或者rollback操做而回滚的话能够根据undo log的信息来进行回滚到没被修改前的状态。
总结:
undo log是用来回滚数据的用于保障 未提交事务的原子性
1. mysql锁技术
当有多个请求来读取表中的数据时能够不采起任何操做,可是多个请求里有读请求,又有修改请求时必须有一种措施来进行并发控制。否则颇有可能会形成不一致。
读写锁
解决上述问题很简单,只需用两种锁的组合来对读写请求进行控制便可,这两种锁被称为:
共享锁(shared lock),又叫作"读锁"
读锁是能够共享的,或者说多个读请求能够共享一把锁读数据,不会形成阻塞。
排他锁(exclusive lock),又叫作"写锁"
写锁会排斥其余全部获取锁的请求,一直阻塞,直到写入完成释放锁。
总结:
经过读写锁,能够作到读读能够并行,可是不能作到写读,写写并行
事务的隔离性就是根据读写锁来实现的!!!这个后面再说。
MVCC (MultiVersion Concurrency Control) 叫作多版本并发控制。
InnoDB的 MVCC ,是经过在每行记录的后面保存两个隐藏的列来实现的。这两个列, 一个保存了行的建立时间,一个保存了行的过时时间, 固然存储的并非实际的时间值,而是系统版本号。
以上片断摘自《高性能Mysql》这本书对MVCC的定义。他的主要实现思想是经过数据多版原本作到读写分离。从而实现不加锁读进而作到读写并行。
MVCC在mysql中的实现依赖的是undo log与read view
undo log :undo log 中记录某行数据的多个版本的数据。
read view :用来判断当前版本数据的可见性
4、事务的实现
前面讲的重作日志,回滚日志以及锁技术就是实现事务的基础。
事务的原子性是经过 undo log 来实现的
事务的持久性性是经过 redo log 来实现的
事务的隔离性是经过 (读写锁+MVCC)来实现的
而事务的终极大 boss 一致性是经过原子性,持久性,隔离性来实现的!!!
原子性,持久性,隔离性折腾半天的目的也是为了保障数据的一致性!
总之,ACID只是个概念,事务最终目的是要保障数据的可靠性,一致性。
什么是原子性:
一个事务必须被视为不可分割的最小工做单位,一个事务中的全部操做要么所有成功提交,要么所有失败回滚,对于一个事务来讲不可能只执行其中的部分操做,这就是事务的原子性。
上面这段话取自《高性能MySQL》这本书对原子性的定义,原子性能够归纳为就是要实现要么所有失败,要么所有成功。
以上概念相信你们伙儿都了解,那么数据库是怎么实现的呢?就是经过回滚操做。所谓回滚操做就是当发生错误异常或者显式的执行rollback语句时须要把数据还原到原先的模样,因此这时候就须要用到undo log来进行回滚,接下来看一下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等。
思考:为何先写日志后写数据库? ---稍后作解释
为了作到同时成功或者失败,当系统发生错误或者执行rollback操做时须要根据undo log 进行回滚
回滚操做就是要还原到原来的状态,undo log记录了数据被修改前的信息以及新增和被删除的数据信息,根据undo log生成回滚语句,好比:
(1) 若是在回滚日志里有新增数据记录,则生成删除该条的语句
(2) 若是在回滚日志里有删除数据记录,则生成生成该条的语句
(3) 若是在回滚日志里有修改数据记录,则生成修改到原先数据的语句
事务一旦提交,其所做作的修改会永久保存到数据库中,此时即便系统崩溃修改的数据也不会丢失。
先了解一下MySQL的数据存储机制,MySQL的表数据是存放在磁盘上的,所以想要存取的时候都要经历磁盘IO,然而即便是使用SSD磁盘IO也是很是消耗性能的。为此,为了提高性能InnoDB提供了缓冲池(Buffer Pool),Buffer Pool中包含了磁盘数据页的映射,能够当作缓存来使用:
读数据:会首先从缓冲池中读取,若是缓冲池中没有,则从磁盘读取在放入缓冲池;
写数据:会首先写入缓冲池,缓冲池中的数据会按期同步到磁盘中;
上面这种缓冲池的措施虽然在性能方面带来了质的飞跃,可是它也带来了新的问题,当MySQL系统宕机,断电的时候可能会丢数据!!!
由于咱们的数据已经提交了,但此时是在缓冲池里头,还没来得及在磁盘持久化,因此咱们急需一种机制须要存一下已提交事务的数据,为恢复数据使用。
因而 redo log就派上用场了。下面看下redo log是何时产生的
既然redo log也须要存储,也涉及磁盘IO为啥还用它?
(1)redo log 的存储是顺序存储,而缓存同步是随机操做。
(2)缓存同步是以数据页为单位的,每次传输的数据大小大于redo log。
隔离性是事务ACID特性里最复杂的一个。在SQL标准里定义了四种隔离级别,每一种级别都规定一个事务中的修改,哪些是事务之间可见的,哪些是不可见的。
级别越低的隔离级别能够执行越高的并发,但同时实现复杂度以及开销也越大。
Mysql 隔离级别有如下四种(级别由低到高):
READ UNCOMMITED (未提交读)
READ COMMITED (提交读)
REPEATABLE READ (可重复读)
SERIALIZABLE (可重复读)
只要完全理解了隔离级别以及他的实现原理就至关于理解了ACID里的隔离型。前面说过原子性,隔离性,持久性的目的都是为了要作到一致性,但隔离型跟其余两个有所区别,原子性和持久性是为了要实现数据的可性保障靠,好比要作到宕机后的恢复,以及错误后的回滚。
那么隔离性是要作到什么呢? 隔离性是要管理多个并发读写请求的访问顺序。 这种顺序包括串行或者是并行
说明一点,写请求不只仅是指insert操做,又包括update操做。
总之,从隔离性的实现能够看出这是一场数据的可靠性与性能之间的权衡。
可靠性性高的,并发性能低(好比 Serializable)
可靠性低的,并发性能高(好比 Read Uncommited)
READ UNCOMMITTED
在READ UNCOMMITTED隔离级别下,事务中的修改即便还没提交,对其余事务是可见的。事务能够读取未提交的数据,形成脏读。
由于读不会加任何锁,因此写操做在读的过程当中修改数据,因此会形成脏读。好处是能够提高并发处理性能,能作到读写并行。
换句话说,读的操做不能排斥写请求。
优势:读写并行,性能高
缺点:形成脏读
READ COMMITTED
一个事务的修改在他提交以前的全部修改,对其余事务都是不可见的。其余事务能读到已提交的修改变化。在不少场景下这种逻辑是能够接受的。
InnoDB在 READ COMMITTED,使用排它锁,读取数据不加锁而是使用了MVCC机制。或者换句话说他采用了读写分离机制。
可是该级别会产生不可重读以及幻读问题。
什么是不可重读?
在一个事务内屡次读取的结果不同。
为何会产生不可重复读?
这跟 READ COMMITTED 级别下的MVCC机制有关系,在该隔离级别下每次 select的时候新生成一个版本号,因此每次select的时候读的不是一个副本而是不一样的副本。
在每次select之间有其余事务更新了咱们读取的数据并提交了,那就出现了不可重复读
REPEATABLE READ(Mysql默认隔离级别)
在一个事务内的屡次读取的结果是同样的。这种级别下能够避免,脏读,不可重复读等查询问题。mysql 有两种机制能够达到这种隔离级别的效果,分别是采用读写锁以及MVCC。
采用读写锁实现:
为何能可重复度?只要没释放读锁,在次读的时候仍是能够读到第一次读的数据。
优势:实现起来简单
缺点:没法作到读写并行
采用MVCC实现:
为何能可重复度?由于屡次读取只生成一个版本,读到的天然是相同数据。
优势:读写并行
缺点:实现的复杂度高
可是在该隔离级别下仍会存在幻读的问题,关于幻读的解决我打算另开一篇来介绍。
SERIALIZABLE
该隔离级别理解起来最简单,实现也最单。在隔离级别下除了不会形成数据不一致问题,没其余优势。
--摘自《高性能Mysql》
数据库老是从一个一致性的状态转移到另外一个一致性的状态.
下面举个例子:zhangsan 从银行卡转400到理财帐户
start transaction;
select balance from bank where name="zhangsan";
// 生成 重作日志 balance=600
update bank set balance = balance - 400;
// 生成 重作日志 amount=400
update finance set amount = amount + 400;
commit;
1.假如执行完 update bank set balance = balance - 400;之发生异常了,银行卡的钱也不能平白无辜的减小,而是回滚到最初状态。
2.又或者事务提交以后,缓冲池还没同步到磁盘的时候宕机了,这也是不能接受的,应该在重启的时候恢复并持久化。
3.假若有并发事务请求的时候也应该作好事务之间的可见性问题,避免形成脏读,不可重复读,幻读等。在涉及并发的状况下每每在性能和一致性之间作平衡,作必定的取舍,因此隔离性也是对一致性的一种破坏。
本文出发点是想讲一下Mysql的事务的实现原理。
实现事务采起了哪些技术以及思想?
原子性:使用 undo log ,从而达到回滚
持久性:使用 redo log,从而达到故障后恢复
隔离性:使用锁以及MVCC,运用的优化思想有读写分离,读读并行,读写并行
一致性:经过回滚,以及恢复,和在并发环境下的隔离作到一致性。