深刻分析Innodb的事务

首先,咱们都知道数据库事务的四大特性:mysql

  • 原子性:事务是最小的工做单元,一个事务要么所有成功,要么所有失败
  • 一致性:事务开始和结束以后,数据库的完整性不会遭到破坏,也就保证事务是从一个正确的状态变成另外一个正确状态(正确状态:咱们预期的状态)
  • 隔离性:不一样事务之间不会相互影响,隔离级别一共有四种,读未提交,读已提交,可重复读,串行化
  • 持久性:事务提交以后,对数据的修改是永久的,即便系统出现故障也不会丢失

Innodb架构

上面是Innodb存储引擎的架构,咱们能够直观的看到存储引擎由内存池,后台线程,和磁盘文件三大部分组成。

Buffer Pool缓冲池

用于处理数据,page是Innodb存储的最基本结构,也是Innodb磁盘管理的最小单位,数据变动的时候,缓存里的数据页和磁盘的数据页不一致,该数据页被称为脏页。sql

Redo Log Buffer 重作日志缓冲

redo log保证数据的可靠性,存储数据以前首先要存储变动数据的日志,一旦系统出现故障能够从日志中找。Innodb采用了Write Ahead Log(预写日志)策略,就是当事务提交时,先写重作日志,而后再择时将脏页落盘。Force Log at Commit机制保证事务的持久性,事务提交的时候,必须先将该事务的全部日志记录落盘,在每次将重作日志缓冲写入到重作日志后,必须调用一次fsync操做,将缓冲日志从文件系统缓存真正写入磁盘数据库

重作日志的落盘机制

redo log落盘机制有三种,能够经过参数innodb_flush_log_at_trx_commit进行设置,默认值为1缓存

  • 0:事务提交的时候不进行重作日志的写入,并且每隔固定的时间写入操做系统缓存并落盘
  • 1:事务提交的时候直接写入到缓存并刷新到磁盘
  • 2:事务提交的时候先写到操做系统的缓存,而后每隔固定的时间再将操做系统缓存中的数据刷新到磁盘

数据落盘机制

上图是数据落盘和redo log日志落盘的机制,咱们说下这个数据落盘时候的双写机制和检查点bash

Double Write

Innodb经过双写机制保证数据的可靠性。Double Write由两个部分构成,一个是内存中的double write buffer,大小为2MB。第二部分是物理磁盘,共享表空间中的128个连续的页,大小也是2MB。数据结构

double write 过程

在对缓冲池中的脏页进行刷新的时候,并非直接将数据写到磁盘。首先,经过mencpy函数将脏页复制到内存中的double write buffer区,而后将double write buffer中的数据分两次,每次1MB,将数据顺序地写入到共享表空间的磁盘上,而后立刻调用fsnyc真正地落盘。完成以后,再讲double write buffer中的数据写入到各个表空间中。若是操做系统在写入磁盘的过程当中崩溃了,能够从共享表空间中找到数据副本恢复数据。架构

checkpoint 检查点

表示将脏页写入到磁盘的时机:并发

  • 目的:用于缩短数据库的恢复时间,buffer pool空间不够时,将数据刷新到磁盘,redo log不够用的时候刷新脏页
  • 分类
    • sharp checkpoint:彻底检查点,数据库正常关闭的时候,会触发把全部的脏页都写入磁盘
    • fuzzy checkpoint:模糊检查点,使用过程当中会触发。
      • master thread checkpoint:固定时间将缓冲池中的脏页按必定比例落盘,异步落盘
      • flush_lru_list checkpoint:读取lru列表,将最近最少使用的数据刷新到磁盘
      • flush checkpoint:redo log file快满了的时候回触发批量的数据落盘,这个事件触发的时候有两种状况,不可被覆盖的redo log占log file的比值为75%触发异步刷新,大于等于90%会触发同步落盘。

undo log

当数据库崩溃以后会利用redo log将尚未及时落盘的数据恢复,从新写入磁盘。在恢复的过程当中还须要去回滚尚未提交的事务,回滚事务就须要利用到undo log,而undo log的完整性和可靠性须要redo log保证,所以恢复数据库的时候,首先将redo log里面数据恢复,而后作undo log的回滚。异步

undo log的存储

数据和回滚日志的每条记录都会有三个额外的字段:函数

  • rowid:行id
  • Trx id:事务id
  • Roll Pointer:回滚指针,指向上一个历史版本

undo log并无使用额外的文件存储,而是存放在共享表空间的回滚段中。undo log的产生也能够当作是数据库的数据,所以,undo log 也会写入到redo log中,也就是undo log 的产生会伴随着redo log的产生,undo log的完整性和可靠性也是由redo log来保证

原子性,一致性,持久性原理分析

原子性,一致性和持久性主要是经过redo log,undo log和Force Log at commit机制来完成的。redo log用在数据库崩溃的时候,从redo log恢复数据,undo log用于对事务的影响进行撤销,也就是回滚,还用于多咱们后面会讲到的多版本控制。Force log at commit保证事务提交以后可以持久化到redo log。

隔离性

事务的并发问题

  • 丢失更新:两个事务针对同一个数据进行更新的时候,会存在丢失更新问题
  • 脏读:一个事务读取到另外一个事务未提交的数据
  • 不可重复度:一个事务对同一条记录读取两个的结果不同
  • 幻读:一个事务对同一张表读取两次结果不一致

针对上面的四个问题,事务之间有不一样的隔离级去解决上面的问题

四种隔离级别

  • RU(读未提交):最低的隔离级别,没法解决上面的任何问题
  • RC(读已提交):能够避免脏读的发送,只有提交的数据才能被读取
  • RR(可重复读):能够避免脏读和不可重复读(Innodb中,RR还能够用于解决幻读,主要是经过Next——Key锁实现,关于锁的相关问题,能够阅读上一篇文章:juejin.im/post/5e1d6d…
  • Serializable(串行化):能够避免脏读,不可重复读,幻读的发生,但效率是最低的

Innodb的MVCC实现

在Innodb中,使用的是MVCC来保证事务之间的隔离,MVCC使得广泛的select语句不会加锁,提升数据库的并发处理能力

什么是MVCC

MVCC:多版本并发控制,是一种并发控制的方法,用来实现数据库的事务性。

当前读和快照读

在MVCC中,读操做能够分红两种

  • 当前读:读取的是记录的最新版本,而且读到的记录,都会加锁,保证其余事务不会再修改这条记录
  • 快照读:读取的是记录的可见版本,有可能读的是历史版本,不须要额外加锁,就是select语句

在Innodb中简单的select语句属于快照读,不加锁,读的是历史版本。而其余的增删改语句属于当前读,须要加锁,读的是当前版本。

一致性非锁定读

一致性非锁定读:Innodb引擎经过MVCC读取当前数据库中行数据的方式。若是读取的是正在执行删除或者更新操做的记录,那么本次读操做不会所以阻塞去等待锁的释放。而会去读取该行的一个最新的可见快照。

MVCC的实现原理

MVCC的实现主要依赖的是undo log和read view(事务链表)

undo log

咱们知道undo log的行记录中有三个隐藏字段:分别对应该行的rowid、事务号db_trx_id和回滚指针db_roll_ptr,其中db_trx_id表示最近修改的事务的id,db_roll_ptr指向回滚段中的undo log。 根据行为的不一样,undo log分为两种

  • insert undo log:insert中产生的undo log,insert操做只对当前事务可见,rollback在该事务中是直接删除的,所以不须要进行purge操做(清除操做,经过purge Thread实现)
  • update undo log:更新或者删除操做产生的undo log,这两类操做会对已经存在的记录产生影响,为了被MVCC读取,不能在事务提交的时候进行删除,而是在事务提交以后放到历史列表中,等待purge线程进行最后的删除。

read view

事务链表,mysql中的事务在开始到提交的阶段都会被保持在一个叫trx_sys的事务链表中啊,事务链表中保持的都是还未提交的事务,事务一旦被提交,就会从链表中删除该事务。

  • RR隔离级别下,每一个事务开始的时候会将当期系统中全部活跃的事务拷贝到链表中
  • RC隔离级别下,每一个语句开始的时候,会将当前系统中的全部活跃的事务拷贝到一个链表中。

说道这里,你们就应该清楚了为何RR能够避免不能够重复读而RC不行,由于二者获取事务链表的方式不一样,RC每一个语句都会从新获取,所以能够读取到其余事务最新提交的数据。 咱们能够经过show engine innodb status语句查看事务链表

如何读历史版本

咱们知道事务开启的时候会获取事务链表,这个类中存储了当前链表中最大的事务ID和最小的事务id。 假设当前活跃的事务链表以下:

ct-trx-->trx11 --> trx10 --> trx9 --> trx5 --> trx2;
复制代码

ct-trx表示当前事务id,对应的read_view数据结构以下

read_view->creator_trx_id = ct-trx; 
read_view->up_limit_id = trx2; 低水位 
read_view->low_limit_id = trx11; 高水位 
read_view->trx_ids = [trx11, trx10, trx9, trx5, trx2];
复制代码
  • low_limit_id:高水位,及当前活跃事务的最大id,若是读到的row的事务id>=low_limit_id,说明这些id在此以前的数据都没有提交,数据都不能够见
  • up_limit_id:低水位,及最小的事务id,同理,若是读到的row的事务id<=up_limit_id,说明这些数据在事务建立的时候id都已经提交了,数据都可见。
  • 当事务id在二者之间的时候,则查找该记录的当前记录的事务id是否在链表中,若是在说明,当前事务还未提交在当前版本中还未提交,当前版本不可见,若是不在,则数据可见。
相关文章
相关标签/搜索