Mysql - InnoDB引擎

逻辑存储结构

Mysql - InnoDB引擎_第1张图片

表空间

表空间是 InnoDB 存储引擎逻辑结构的最高层, 如果用户启用了参数 innodb_file_per_table(
8.0 版本中默认开启 ) ,则每张表都会有一个表空间( xxx.ibd ),一个 mysql 实例可以对应多个表空
间,用于存储记录、索引等数据。

段,分为数据段( Leaf node segment )、索引段( Non-leaf node segment )、回滚段 (Rollback segment ), InnoDB 是索引组织表,数据段就是 B+ 树的叶子节点, 索引段即为 B+ 树的非叶子节点。段用来管理多个Extent (区)。

区,表空间的单元结构,每个区的大小为 1M 。 默认情况下, InnoDB 存储引擎页大小为 16K , 即一 个区中一共有64 个连续的页。

页,是 InnoDB 存储引擎磁盘管理的最小单元,每个页的大小默认为 16KB 。为了保证页的连续性, InnoDB 存储引擎每次从磁盘申请 4-5 个区。

行,InnoDB 存储引擎数据是按行进行存放的。

在行中,默认有两个隐藏字段:

Trx_id:每次对某条记录进行改动时,都会把对应的事务id赋值给trx_id隐藏列。

Roll_pointer :每次对某条引记录进行改动时,都会把旧的版本写入到 undo 日志中,然后这个
隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。

结构

Mysql - InnoDB引擎_第2张图片

 内存结构

Mysql - InnoDB引擎_第3张图片

Mysql - InnoDB引擎_第4张图片

Mysql - InnoDB引擎_第5张图片

 Mysql - InnoDB引擎_第6张图片

 磁盘结构

Mysql - InnoDB引擎_第7张图片

Mysql - InnoDB引擎_第8张图片 Mysql - InnoDB引擎_第9张图片

 Mysql - InnoDB引擎_第10张图片

 事务原理

Mysql - InnoDB引擎_第11张图片

Mysql - InnoDB引擎_第12张图片

  redo log

 重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log file),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都存到该日志文件中, 用于在刷新脏页到磁盘,发生错误时, 进行数据恢复使用。

如果没有 redolog ,可能会存在什么问题的? 我们一起来分析一下。
我们知道,在 InnoDB 引擎中的内存结构中,主要的内存区域就是缓冲池,在缓冲池中缓存了很多的数据页。 当我们在一个事务中,执行多个增删改的操作时,InnoDB 引擎会先操作缓冲池中的数据,如果缓冲区没有对应的数据,会通过后台线程将磁盘中的数据加载出来,存放在缓冲区中,然后将缓冲池中的数据修改,修改后的数据页我们称为脏页。 而脏页则会在一定的时机,通过后台线程刷新到磁盘中,从而保证缓冲区与磁盘的数据一致。 而缓冲区的脏页数据并不是实时刷新的,而是一段时间之后将缓冲区的数据刷新到磁盘中,假如刷新到磁盘的过程出错了,而提示给用户事务提交成功,而数据却没有持久化下来,这就出现问题了,没有保证事务的持久性。
Mysql - InnoDB引擎_第13张图片
那么,如何解决上述的问题呢? 在 InnoDB 中提供了一份日志 redo log ,接下来我们再来分析一
下,通过redolog如何解决这个问题。 Mysql - InnoDB引擎_第14张图片

 

有了 redolog 之后,当对缓冲区的数据进行增删改之后,会首先将操作的数据页的变化,记录在 redo log buffer中。在事务提交时,会将 redo log buffer 中的数据刷新到 redo log 磁盘文件中。
过一段时间之后,如果刷新缓冲区的脏页到磁盘时,发生错误,此时就可以借助于 redo log 进行数据 恢复,这样就保证了事务的持久性。 而如果脏页成功刷新到磁盘 或 或者涉及到的数据已经落盘,此 时redolog 就没有作用了,就可以删除了,所以存在的两个 redolog 文件是循环写的。
那为什么每一次提交事务,要刷新 redo log 到磁盘中呢,而不是直接将 buffer pool 中的脏页刷新
到磁盘呢 ?
因为在业务操作中,我们操作数据一般都是随机读写磁盘的,而不是顺序读写磁盘。 而 redo log
往磁盘文件中写入数据,由于是日志文件,所以都是顺序写的。顺序写的效率,要远大于随机写。 这种先写日志的方式,称之为 WAL Write-Ahead Logging

undo log

Mysql - InnoDB引擎_第15张图片

回滚日志,用于记录数据被修改前的信息 , 作用包含两个 : 提供回滚 ( 保证事务的原子性 )
MVCC( 多版本并发控制 )
undo log redo log 记录物理日志不一样,它是逻辑日志。可以认为当 delete 一条记录时, undo
log 中会记录一条对应的 insert 记录,反之亦然,当 update 一条记录时,它记录一条对应相反的
update 记录。当执行 rollback 时,就可以从 undo log 中的逻辑记录读取到相应的内容并进行回滚。
Undo log 销毁: undo log 在事务执行时产生,事务提交时,并不会立即删除 undo log ,因为这些
日志可能还用于 MVCC

 Undo log存储:undo log采用段的方式进行管理和记录,存放在前面介绍的 rollback segment 回滚段中,内部包含1024undo log segment

MVCC

概念

当前读

读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。对于我们日常的操作,如:select ... lock in share mode( 共享锁 ) select ...
for update update insert delete( 排他锁 ) 都是一种当前读。
Mysql - InnoDB引擎_第16张图片
在测试中我们可以看到,即使是在默认的 RR 隔离级别下,事务 A 中依然可以读取到事务 B 最新提交的内容,因为在查询语句后面加上了 lock in share mode 共享锁,此时是当前读操作。当然,当我们加排他锁的时候,也是当前读操作。

快照读

简单的 select (不加锁)就是快照读,快照读,读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。
  • Read Committed:每次select,都生成一个快照读。
  •  Repeatable Read:开启事务后第一个select语句才是快照读的地方。
  • Serializable:快照读会退化为当前读。

Mysql - InnoDB引擎_第17张图片

在测试中 , 我们看到即使事务 B 提交了数据 , 事务 A 中也查询不到。 原因就是因为普通的 select 是快照
读,而在当前默认的 RR 隔离级别下,开启事务后第一个 select 语句才是快照读的地方,后面执行相
同的select 语句都是从快照中获取数据,可能不是当前的最新数据,这样也就保证了可重复读。

MVCC

全称 Multi-Version Concurrency Control ,多版本并发控制。指维护一个数据的多个版本,
使得读写操作没有冲突,快照读为 MySQL 实现 MVCC 提供了一个非阻塞读功能。 MVCC 的具体实现,还需要依赖于数据库记录中的三个隐式字段、undo log 日志、 readView

 

原理

我们再来介绍一下 InnoDB 引擎的表中涉及到的隐藏字段 、 undolog 以及 readview ,从而来介绍一下MVCC 的原理。

隐藏字段

Mysql - InnoDB引擎_第18张图片

当我们创建了上面的这张表,我们在查看表结构的时候,就可以显式的看到这三个字段。 实际上除了这三个字段以外,InnoDB还会自动的给我们添加三个隐藏字段及其含义分别是:

隐藏字段
含义
DB_TRX_ID
最近修改事务 ID ,记录插入这条记录或最后一次修改该记录的事务 ID
DB_ROLL_PTR
回滚指针,指向这条记录的上一个版本,用于配合 undo log ,指向上一个版本。
DB_ROW_ID
隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段。
而上述的前两个字段是肯定会添加的, 是否添加最后一个字段 DB_ROW_ID ,得看当前表有没有主键, 如果有主键,则不会添加该隐藏字段。
查看隐藏字段:
进入服务器中的 /var/lib/mysql/itcast/ , 查看 stu 的表结构信息 , 通过如下指令 :
ibd2sdi stu.ibd
查看到的表结构信息中,有一栏 columns ,在其中我们会看到处理我们建表时指定的字段以外,还有 额外的两个字段 分别是:DB_TRX_ID DB_ROLL_PTR ,因为该表有主键,所以没有 DB_ROW_ID 隐藏字段。
Mysql - InnoDB引擎_第19张图片

 Mysql - InnoDB引擎_第20张图片

 undolog

回滚日志,在 insert update delete 的时候产生的便于数据回滚的日志。
insert 的时候,产生的 undo log 日志只在回滚时需要,在事务提交后,可被立即删除。
update delete 的时候,产生的 undo log 日志不仅在回滚时需要,在快照读时也需要,不会立即
被删除。

版本链

Mysql - InnoDB引擎_第21张图片

 DB_TRX_ID : 代表最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID,是自增的。

DB_ROLL_PTR : 由于这条数据是才插入的,没有被更新过,所以该字段值为null

 Mysql - InnoDB引擎_第22张图片

Mysql - InnoDB引擎_第23张图片Mysql - InnoDB引擎_第24张图片

Mysql - InnoDB引擎_第25张图片 Mysql - InnoDB引擎_第26张图片

Mysql - InnoDB引擎_第27张图片

 最终我们发现,不同事务或相同事务对同一条记录进行修改,会导致该记录的undolog生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。

readview

ReadView (读视图)是 快照读 SQL 执行时 MVCC 提取数据的依据,记录并维护系统当前活跃的事务(未提交的)id
ReadView 中包含了四个核心字段:
字段
含义
m_ids
当前活跃的事务 ID 集合
min_trx_id
最小活跃事务 ID
max_trx_id
预分配事务 ID ,当前最大事务 ID+1 (因为事务 ID 是自增的)
creator_trx_id
ReadView 创建者的事务 ID
而在 readview 中就规定了版本链数据的访问规则:
trx_id 代表当前 undolog 版本链对应事务 ID
条件
是否可以访问
说明
trx_id == creator_trx_id
可以访问该版本
成立,说明数据是当前这个事务更改的。
trx_id < min_trx_id
可以访问该版本
成立,说明数据已经提交了。
trx_id > max_trx_id
不可以访问该版本
成立,说明该事务是在
ReadView 生成后才开启。
min_trx_id <= trx_id <= max_trx_id
如果 trx_id 不在 m_ids 中,
是可以访问该版本的
成立,说明数据已经提交。
不同的隔离级别,生成 ReadView 的时机不同:
READ COMMITTED :在事务中每一次执行快照读时生成 ReadView
REPEATABLE READ :仅在事务中第一次执行快照读时生成 ReadView ,后续复用该 ReadView

原理分析

RC(Read commit)隔离级别

RC 隔离级别下,在事务中每一次执行快照读时生成 ReadView。
我们就来分析事务 5 中,两次快照读读取数据,是如何获取数据的 ?
在事务 5 中,查询了两次 id 30 的记录,由于隔离级别为 Read Committed ,所以每一次进行快照读
都会生成一个 ReadView ,那么两次生成的 ReadView 如下。
Mysql - InnoDB引擎_第28张图片
那么这两次快照读在获取数据时,就需要根据所生成的 ReadView 以及 ReadView 的版本链访问规则, 到undolog 版本链中匹配数据,最终决定此次快照读返回的数据。

 Mysql - InnoDB引擎_第29张图片

在进行匹配时,会从undo log的版本链,从上到下进行挨个匹配 

先匹配这条记录 Mysql - InnoDB引擎_第30张图片

 

这条记录对应的 trx_id为 4 ,也就是将 4 带入右侧的匹配规则中。 ①不满足 ②不满足 ③不满足 ④也不满足 , 都不满足,则继续匹配undo log 版本链的下一条。
再匹配第二条

 记录对应的trx_id3,也就是将3带入右侧的匹配规则中。①不满足 ②不满足 ③不满足 ④也 不满足 ,都不满足,则继续匹配undo log版本链的下一条。

再匹配第三条

,这条记录对应的trx_id 2 ,也就是将 2 带入右侧的匹配规则中。①不满足 ②满足 终止匹配,此次快照读,返回的数据就是版本链中记录的这条数据。
Mysql - InnoDB引擎_第31张图片
在进行匹配时,会从 undo log 的版本链,从上到下进行挨个匹配:
先匹配

 

这条记录,这条记录对应的 trx_id为 4 ,也就是将 4 带入右侧的匹配规则中。 ①不满足 ②不满足 ③不满足 ④也不满足 ,都不满足,则继续匹配undo log 版本链的下一条
再匹配第二条

 

这条 记录对应的trx_id 3 ,也就是将 3 带入右侧的匹配规则中。①不满足 ②满足 。终止匹配,此次快照读,返回的数据就是版本链中记录的这条数据。

 RR隔离级别

 RR隔离级别下,仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。 而RR 是可重复读,在一个事务中,执行两次相同的select语句,查询到的结果是一样的。

那MySQL是如何做到可重复读的呢? 我们简单分析一下就知道了

Mysql - InnoDB引擎_第32张图片

 MVCC总结

所以呢, MVCC 的实现原理就是通过 InnoDB 表的隐藏字段、 UndoLog 版本链、 ReadView 来实现的。 而MVCC + 锁,则实现了事务的隔离性。 而一致性则是由 redolog undolog 保证。
Mysql - InnoDB引擎_第33张图片

 总结

Mysql - InnoDB引擎_第34张图片

 

你可能感兴趣的:(mysql,mysql,java,数据库)