「黑马 MySQL」二、InnoDB 引擎

逻辑存储结构

在 InnoDB 存储引擎中，每行数据包含了表中所定义的所有字段的值之外，还包括以下几个重要的东西：

6 字节的事务 ID trx id
在 InnoDB 中，每个事务都会被赋予一个唯一的事务 ID，并且每行数据都包含了这个事务 ID 的值，用于实现事务级别的多版本并发控制 MVCC

7 字节的回滚指针 roll pointer
为了支持事务的回滚操作，在每个行数据中都会包含一个指向回滚日志里对应记录的回滚指针，当事务需要回滚时，通过回滚指针可以快速定位到对应的回滚记录并恢复数据。

非空列位图
非空列位图是 InnoDB 存储引擎中用于提高存储效率的一种机制，由于在一个表中往往会有很多列都是 NULL 值，因此可以将这些列的信息使用位图的方式来进行表示，从而在存储数据时尽可能地压缩存储空间

记录头
每个行数据都包含了一个记录头，其中包括了一些元数据信息，如数据是否被删除、是否被锁定等

版本号
在 InnoDB 的 MVCC 实现中，每个行数据还会包含一个版本号，用来表示当前行的版本信息，当一个事务更新一行数据时，实际上是在原有行数据的基础上新增了一条新记录，并通过版本号来区分这两条记录

综上所述，InnoDB 行 ROW 中除了表中所定义的字段的值，还包含了很多其他的元数据信息，在 InnoDB 存储引擎实现事务支持、MVCC、存储效率和数据恢复等方面都发挥着重要的作用

架构

下面是 InnoDB 架构图，左侧为内存结构，右侧是磁盘结构

内存结构 - 缓冲池 Buffer Pool

操作缓存池的数据，再以一定频率刷新到磁盘，减少磁盘 IO

缓冲池以页 Page 为单位，底层采用链表管理，分为三种类型：

free page：空闲 page，未被使用

clean page： 被使用过的 page，但没有被修改过数据

dirty page：脏页，被使用过的 page，修改过数据，此时数据与磁盘的数据产生了不一致，需要刷新进磁盘

内存结构 - 更改缓冲区 Change Buffer

将数据的变更缓存在更改缓冲区，当未来有数据被读取时，将数据合并并恢复到缓冲池 Buffer Pool 中，然后合并的数据会被刷入磁盘

更改缓冲区 Change Buffer 存在意义：
二级索引通常非唯一的并且插入的比较随机，删除更改操作的设涉及范围比较大，造成大量磁盘 IO，先在缓冲池 Buffer Pool 中合并数据可以减少磁盘 IO

内存结构 - 自适应哈希索引 Adaptive Hash Index

优化对 Buffer Pool 的数据查询

内存结构 - 日志缓冲区 Log Buffer

日志缓冲区保存 redo log、undo log 这类日志数据，默认 16MB，定期刷到磁盘，减少大量操作频繁磁盘 IO

磁盘结构 - 系统表空间 System Tablespace

磁盘结构 - 文件表空间 File-Per-Table Tablespaces：

磁盘结构 - 通用表空间 General Tablespaces

磁盘结构 - 撤销表空间 Undo Tablespaces 临时表空间 Temporary Tablespaces

磁盘结构 - 双写缓冲区 Doublewrite Buffer Files 重做日志 Redo Log

后台线程

后台线程作用：内存刷到磁盘

事务原理

事务是一组操作的集合，它是一个不可分割的工作单位，事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求，即这些操作要么同时成功，要么同时失败

ACID 特性

原子性 Atomicity：事务是不可分割的最小操作单元，要么全部成功，要么全部失败

一致性 Consistency：事务完成时，必须使所有的数据都保持一致状态

隔离性 Isolation：数据库系统提供的隔离机制，保证事务在不受外部并发操作影响的独立环境下运行

持久性 Durability：事务一旦提交或回滚，它对数据库中的数据的改变就是永久的

原子性 - undo log
持久性 - redo log
一致性 - undo log + redo log
隔离性 - 锁 + MVCC

redo log - 持久性

当脏页刷入磁盘发生错误时，根据 redo log 进行数据的恢复

WAL write-ahead logging 先写日志，日志时 append 写入，顺序磁盘 IO，而非刷入脏页的随机磁盘 IO

undo log - 原子性

回滚日志 undo log 用于记录数据被修改前的信息，提供回滚 rollback 和 MVCC

undo log 主要适用于回滚，所以记录是逻辑日志，会记录相反操作语句，这样 rollback 就可以读到相应内容并回滚

MVCC

RU：直接读最近数据，没 MVCC，没锁，什么都没
RC：每次读都生成一个快照读，可以读到最新的已提交数据
RR：事务开启后的第一个 select 语句生成快照读，此后延续使用这个 readview，保证可重复读
SE：每次都是当前读，会被阻塞，也会上锁

当前读：
读取的是记录的最新版本，读取时还要保证其他并发事务不能修改当前记录，会对读取的记录进行加锁，如：
select..lock in share mode 共享锁，select..for update、update、insert、delete 排他锁都是一种当前读

快照读：
简单的 select（不加锁）就是快照读，快照读，读取的是记录数据的可见版本，有可能是历史数据，不加锁，是非阻塞读

Read Committed：每次 select，都生成一个快照读

Repeatable Read：开启事务后第一个 select 语句才是快照读的地方

Serializable：快照读会退化为当前读

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

实现原理 - 三个隐式字段

DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID

实现原理 - undo log 日志

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

不同事务或相同事务对同一条记录进行修改，会导致该记录的 undo log 生成一条记录版本链表，链表的头部是最新的旧日记录，链表尾部是最早的旧记录

实现原理 - readView

每个事物在进行读取数据时生成的 readView 都不同

readView 版本链访问规则

先看当前事务 trx_id 是否等于 creator_trx_id，是则访问，不是则 2

检查当前事务 trx_id 是否 between min_trx_id and max_trx_id（最大 trx_id + 1）并且已经不再活跃（已提交），是则访问，不是则 3

找到最大的小于 min_trx_id 的版本（最早已提交版本）

Read Commited

事务 5 第一次快照读，trx_id = 2 的版本符合条件 2 的要求，trx_id < min_trx_id = 3，实现读事务 2 已提交

事务 5 第二次快照读，trx_id = 3 的版本符合条件 2 的要求，trx_id < min_trx_id = 4，实现读事务 3 已提交

Repeatable Read

RR 事务隔离级别仅在第一次执行快照读时生成 readView，后续复用该 readView

此文由 Mix Space 同步更新至 xLog
原始链接为 https://blog.0xling.cyou/posts/mysql/mysql-2