MySQL3️⃣InnoDB 引擎

1、逻辑存储结构

结构图

image-20220313170930188

1.1、表空间

存储表结构、记录、索引等数据

  • 参数:innodb_file_per_table,开启则每张表都对应一个表空间(8.0 默认开启)
  • 文件:xxx.ibd

1.2、段

管理多个区

包括以下三部分

  • 数据段(Leaf node segment):即 B+tree 的叶子结点
  • 索引段(Non-leaf node segment):即 B+tree 树的非叶子节点
  • 回滚段(Rollback segment):存放 undo log(回滚日志,后面会讲到)

1.3、区

表空间的单元结构

每个区的大小为 1M

1.4、页

InnoDB 存储引擎磁盘管理的最小单元

  • 默认大小:16KB(即默认一个区有 64 个连续的页)
  • 为了保证页的连续性,InnoDB 每次从磁盘申请 4-5 个区。

1.5、行

InnoDB 中按行存放数据

有三个隐藏字段,在 MVCC 中会具体讲解

  • DB_TRX_ID:事务 ID,值为插入或最近一次修改此记录的事务 ID。
  • DB_ROLL_PTR:回滚指针,指向记录的上一版本
  • DB_ROW_ID:隐藏主键,当表结构没有指定主键时生成。

2、架构

MySQL 5.5 之后默认使用 InnoDB 存储引擎

(事务处理,崩溃恢复特性)

InnoDB 架构图

左侧为内存结构,右侧为磁盘结构。

image-20220316200123424

2.1、内存结构

  • 内存结构 = 后台线程 + 三个缓冲区
  • 在专用服务器上,通常将多达 80% 的物理内存分配给缓冲区。

2.1.1、缓冲池

Buffer Pool

  • 功能:缓存磁盘上经常操作的真实数据,减少磁盘 I/O。
  • 参数innodb_buffer_pool_size
  • 缓存信息:索引页、数据页、undo页、插入缓存、自适应哈希索引、InnoDB 的锁信息等。

情景

  1. 缓冲池中没有数据,通过后台线程从磁盘加载并缓存
  2. 执行增删改查操作时,操作缓冲池中的数据(产生脏页),而不是直接操作磁盘。
  3. 缓冲池再以一定频率,通过后台线程将脏页刷新到磁盘

缓冲池以 Page 为单位,以链表结构组织管理

包括以下三种 Page

  • free page:未被使用
  • clean page:被使用但未被修改
  • dirty page:被使用且被修改(即数据与磁盘的数据不一致,需要刷新到磁盘)

2.1.2、更改缓冲区

Change Buffer

  • MySQL 8 引入,针对于非唯一的二级索引页
  • 功能:缓存尚未被读取到缓冲池的数据对应的 DML 操作,减少磁盘 I/O。

情景

  1. 执行 DML 语句时,数据尚未从磁盘读到缓冲池
  2. 此时在更改缓冲区中存储数据变更,而不是直接操作磁盘。
  3. 待数据被缓冲池读取后,更改缓冲区将数据合并恢复到缓冲池中
  4. 缓冲池再将合并后的数据刷新到磁盘中。

2.1.3、自适应 hash 索引

Adaptive Hash Index

  • 功能:优化 Buffer Pool 数据的查询。
  • 参数adaptive_hash_index

回顾 hash 索引

  1. hash 索引
    • 支持等值查询,不支持范围查询;不支持排序
    • 查询效率高,通常只需一次检索(前提是不存在哈希冲突)
  2. Memory 引擎支持 hash 索引
  3. InnoDB 引擎支持 B+tree 索引,不支持 hash 索引,但是提供了自适应 hash 索引。

自适应 hash 索引

  1. InnoDB 对表中的各索引页查询进行监控。
  2. 若观察到在特定的条件下,使用 hash 索引可以提升速度,则建立 hash 索引。
  3. 无需人工操作,是系统根据情况自动完成。

2.1.4、日志缓冲区

Log Buffer

  • 功能:保存要写入到磁盘中的日志数据(redo log 、undo log)
  • 日志先保存到日志缓冲区,再定期刷新到磁盘中。
  • 增加日志缓冲区的大小可以节省磁盘 I/O。

参数

  • innodb_log_buffer_size:缓冲区大小,默认 16MB
  • innodb_flush_log_at_trx_commit:日志刷新到磁盘时机
    • 1:日志在每次事务提交时写入,并刷新到磁盘(默认)。
    • 0:每秒将日志写入并刷新到磁盘一次。
    • 2:日志在每次事务提交后写入,并每秒刷新到磁盘一次。

2.2、磁盘结构

磁盘结构 = 重做日志 + 双写缓冲文件 + 5 个表空间

2.2.1、系统表空间

System Tablespace

存储内容

  • MySQL 8:作为更改缓冲区的存储区域
  • MySQL 5.x:还包含 InnoDB 数据字典、undolog 等信息

回顾:表空间参数 innodb_file_per_table

  1. 如果未开启参数,并且没有通用表空间。
  2. 则系统表空间可能也会包含表结构、索引等数据。

2.2.2、每个表的文件表空间

File-Per-Table Tablespaces

开启表空间参数 innodb_file_per_table,每个表都对应一个 xxx.ibd 文件。

2.2.3、通用表空间

General Tablespaces

通用表空间需要手动创建,建表时可以指定该表空间。

# 创建通用表空间
CREATE TABLESPACE ts_name ADD DATAFILE 'file_name' ENGINE = engine_name;
# 指定表空间
CREATE TABLE xxx ... TABLESPACE ts_name;

2.2.4、撤销表空间

Undo Tablespaces

  • MySQL 实例在初始化时,自动创建 2 个默认 undo 表空间(初始大小16M)
  • 用于存储 undo log 日志

2.2.5、临时表空间

Temporary Tablespaces

  • 功能:存储用户创建的临时表等数据。
  • 分类:会话临时表空间、全局临时表空间

2.2.6、双写缓冲区文件

Doublewrite Buffer Files

  • 缓冲池将脏页刷新到磁盘前,先将脏页保存到双写缓冲区文件中。
  • 若系统出现异常,用于恢复数据

2.2.7、重做日志

Redo Log

功能:实现事务的持久性

  1. 事务提交后,会把所有修改信息都会存到该日志中。
  2. 若缓冲池将脏页刷新到磁盘的过程中出错,使用 Redo Log 恢复数据。

2.3、后台线程

作用:将 InnoDB 缓冲池中的数据刷新到磁盘文件中

2.3.1、主线程

Master Thread:核心后台线程,负责以下功能

  1. 调度其它线程。
  2. 将缓冲池的数据刷新到磁盘中。
  3. 脏页刷新、合并插入缓存、回收 undo 页等。

2.3.2、IO 线程

IO Thread

  • InnoDB 使用 AIO 来处理 IO 请求,IO Thread 负责处理 IO 请求回调

  • 查看 InnoDB 状态show engine 1 innodb status \G;

    线程类型 默认个数 职责
    Read thread 4 读操作
    Write thread 4 写操作
    Log thread 1 将日志缓冲区刷新到磁盘
    Insert buffer thread 1 将写缓冲区内容刷新到磁盘

2.3.3、净化线程

Purge Thread

回收已提交事务的 undo log 页。

2.3.4、页面清理线程

Page Cleaner Thread

协助 Master Thread 刷新脏页到磁盘,减轻 Master Thread 压力,减少阻塞。

3、事务原理(❗)

回顾 事务

  • 事务操作:控制、提交、回滚
  • 四大特性:ACID(原子性、一致性、隔离性、持久性)
  • 并发事务:脏读、不可重复读、幻读
  • 隔离级别:RU、RC、RR、Serializable

事务原理围绕 ACID 展开。

  • 原子性、一致性、持久性(ACD):通过日志实现

    • 持久性:redo log
    • 原子性:undo log
    • 一致性:redo log + undo log
  • 隔离性(I):通过 + MVCC 实现

3.1、redo log

3.1.1、说明

重做日志

  • 组成
    • 重做日志缓冲(redo log buffer):存放在内存中的 Log Buffer。
    • 重做日志文件(redo log file):存放在磁盘中的 Redo Log。
  • 功能记录事务提交时数据页的物理修改,实现持久性
    • 事务提交后,将所有修改信息保存到该日志中。
    • 若缓冲池将脏页刷新到磁盘的过程中出错,使用 Redo Log 恢复数据

3.1.2、流程

  1. 事务执行增删改操作,首先操作缓冲池中的数据

    (若缓冲池中没有对应的数据,通过后台线程加载磁盘数据)

  2. 缓冲池中的数据被修改,修改后的数据页即为脏页

  3. 缓冲池会以一定频率,通过后台线程将脏页刷新到磁盘,从而保证数据一致性。

    image-20220410135419946

问题

  • 缓冲区中的脏页数据是异步刷新的,而不是实时刷新
  • 事务 A 操作缓冲池的数据后即“认为”成功。
  • 刷新过程出错,数据实际上没有被持久化

3.1.3、WAL

预写日志(Write-Ahead Logging,WAL)

  1. 缓冲区中的数据被修改后,将脏页的变化记录在 redo log buffer 中。

  2. 事务提交时,将 redo log buffer 中的数据刷新到 redo log 磁盘文件中。

  3. 脏页刷新的过程出错,可使用 redo log 进行恢复

  4. 当脏页成功刷新后,redo log 可删除(因此两个 redolog 文件是循环写的)

    image-20220316224006196

思考:每次提交事务,实时刷新 redo log,异步刷新脏页。

为什么不实时将脏页数据刷新到磁盘呢?

在业务操作中,一般是随机读写磁盘,而不是顺序读写磁盘。

  • 直接刷新:直接将脏页数据刷新到磁盘,是随机写,并且浪费磁盘 I/O
  • 使用 redo log:日志文件往磁盘文件中写入数据时,是顺序写效率高于随机写。

3.2、undo log

回滚日志

在事务执行时产生,记录数据被修改前的信息

  • 作用:回滚(事务的原子性)、MVCC。
  • 存储
    • 回顾逻辑存储结构:段中有一个回滚段(Rollback segment)
    • undo log 采用段的方式进行管理和记录,每个回滚段中包含 1024 个 undo log segment

undo log 是逻辑日志(记录相反操作),redo log 是物理日志(记录实际操作)

示例

  1. 执行 DELETE 时,undo log 中记录一条对应的 INSERT。
  2. 执行 UPDATE 时,undo log 中记录一条对应的 UPDATE。

当执行 rollback 时,从 undo log 中读取相应逻辑记录并回滚。

3.3、MVCC(❗)

多版本并发控制(Multi-Version Concurrency Control)

维护一个数据的多个版本,使读写操作不冲突

3.3.1、当前读 & 快照读

当前读:读取记录的最新版本

  • 对记录加锁,阻塞其它事务的修改。
  • 常见当前读
    • 共享锁select ... lock in share mode
    • 排它锁:增删改、select ... for update

快照读:读取记录的可见版本(可能是历史数据)

  • 不加锁,非阻塞
  • 不同隔离级别的快照
    • RC:每次 select 都生成一个快照。
    • RR:开启事务后第一个select 生成快照。
    • Serializable:快照读会退化为当前读。

3.3.2、MVCC

MVCC 的实现:快照读、隐藏字段、回滚日志、readView

  • 隐藏字段
    • DB_TRX_ID:事务ID,值为插入或最后一次修改此记录的事务 ID。
    • DB_ROLL_PTR:回滚指针。指向这条记录的上一个版本。
    • DB_ROW_ID:隐藏主键。当表结构没有指定主键时生成。
  • 回滚日志(undolog):在事务执行时产生,记录数据被修改前的信息。
    • :产生的 undo log 只在回滚时需要,事务提交后即删除。
    • 删改:产生的undo log 在回滚和快照读时都需要,事务提交后不会立即删除。

3.3.3、版本链

隐藏字段中的回滚指针(DB_ROLL_PTR),指向了记录的上一版本。

  • 记录的多个版本之间形成一条版本链。

  • 链头是最新的历史版本,链尾是最早的历史版本。

示例

  • 事务 2、3、4 先后执行三条 UPDATE 语句

    image-20220317152930831
  • 版本链情况

    image-20220317153737270

3.3.4、读视图

ReadView

功能

快照读时 MVCC 读取数据的依据,记录并维护系统当前活跃的事务 ID。

  • m_ids:当前活跃事务 ID 集合
  • min_trx_id:最小活跃事务 ID
  • max_trx_id:预分配事务 ID,当前最大事务 ID+1(因为事务ID是自增的)
  • creator_trx_id:ReadView 创建者的事务 ID

生成时机

  • RC:事务每次执行快照读时生成。
  • RR:仅在事务中第一次执行快照读时生成

访问规则

  • 遍历版本链,逐条判断 trx_id 是否满足条件

  • trx_id 代表当前版本对应的事务 ID

    trx_id 含义 是否可访问
    == creator_trx_id 数据是当前事务更改的
    < min_trx_id 数据已提交
    [min_trx_id, max_trx_id] 数据已提交 trx_id 不在 m_ids 中才 ✔
    > max_trx_id 当前事务在 ReadView 生成后开启
posted @ 2022-10-13 17:52  Jaywee  阅读(51)  评论(0编辑  收藏  举报

👇