[MySQL数据库] InnoDB存储引擎(四): InnoDB磁盘文件

🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343

🏵️热门专栏:

🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm=1001.2014.3001.5482

🍕 Collection与数据结构 (93平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482

🧀线程与网络(97平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482

🍭MySql数据库(95平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482

🍬算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482

🍃 Spring(97平均质量分)https://blog.csdn.net/2301_80050796/category_12724152.html?spm=1001.2014.3001.5482

🎃Redis(97平均质量分)https://blog.csdn.net/2301_80050796/category_12777129.html?spm=1001.2014.3001.5482

🐰RabbitMQ(97平均质量分) https://blog.csdn.net/2301_80050796/category_12792900.html?spm=1001.2014.3001.5482

感谢点赞与关注~~~

目录

  • [1. InnoDB存储引擎包含哪些磁盘文件?](#1. InnoDB存储引擎包含哪些磁盘文件?)
    • [1.1 衍生问题1: 什么是表空间?](#1.1 衍生问题1: 什么是表空间?)
  • [2. 系统表空间](#2. 系统表空间)
    • [2.1 系统表空间的作用?](#2.1 系统表空间的作用?)
    • [2.2 系统表空间保存在哪里?](#2.2 系统表空间保存在哪里?)
  • [3. 独立表空间](#3. 独立表空间)
    • [3.1 独立表空间有哪些作用?](#3.1 独立表空间有哪些作用?)
      • [3.1.1 衍生问题1: 独立表空间文件存在哪里?](#3.1.1 衍生问题1: 独立表空间文件存在哪里?)
      • [3.1.2 衍生问题2: 每个表都对应一个独立表空间吗?](#3.1.2 衍生问题2: 每个表都对应一个独立表空间吗?)
  • [4. 撤销表空间](#4. 撤销表空间)
    • [4.1 撤销表空间的作用?](#4.1 撤销表空间的作用?)
      • [4.1.1 衍生问题1: 什么是撤销日志?](#4.1.1 衍生问题1: 什么是撤销日志?)
    • [4.2 在使用MySQL时没有手动创建撤销表空间,他是什么时候被创建的?](#4.2 在使用MySQL时没有手动创建撤销表空间,他是什么时候被创建的?)
      • [4.2.1 衍生问题1: 默认的撤销表空间名称和路径是什么?](#4.2.1 衍生问题1: 默认的撤销表空间名称和路径是什么?)
      • [4.2.2 衍生问题2: 可以手动撤销表空间吗?](#4.2.2 衍生问题2: 可以手动撤销表空间吗?)
      • [4.3 如何删除撤销表空间](#4.3 如何删除撤销表空间)
      • [4.3.1 衍生问题1: 撤销表空间为不活动并且已经被截断为初始大小,这时候不想删除是否可以重新启用?](#4.3.1 衍生问题1: 撤销表空间为不活动并且已经被截断为初始大小,这时候不想删除是否可以重新启用?)
    • [4.4 如何查看撤销表空间的状态?](#4.4 如何查看撤销表空间的状态?)
  • [5. 撤销日志 - undo log](#5. 撤销日志 - undo log)
    • [5.1 什么是撤销日志](#5.1 什么是撤销日志)
      • [5.1.1 衍生问题1: 撤销日志的写入时机](#5.1.1 衍生问题1: 撤销日志的写入时机)
    • [5.2 撤销日志在撤销表空间中的组织形式是怎么样的?](#5.2 撤销日志在撤销表空间中的组织形式是怎么样的?)
    • [5.3 撤销日志的格式是怎样的?](#5.3 撤销日志的格式是怎样的?)
      • [5.3.1 衍生问题1: 在事务中不同的DML操作对应的撤销日志是否不同?](#5.3.1 衍生问题1: 在事务中不同的DML操作对应的撤销日志是否不同?)
      • [5.3.2 衍生问题2: 不同操作对应的撤销日志如何区分?](#5.3.2 衍生问题2: 不同操作对应的撤销日志如何区分?)
    • [5.4 撤销日志是如何组织在一起的?](#5.4 撤销日志是如何组织在一起的?)
      • [5.4.1 衍生问题1:事务提交后undolog是否就可以删除了?](#5.4.1 衍生问题1:事务提交后undolog是否就可以删除了?)
    • [5.5 撤销日志如何分类?](#5.5 撤销日志如何分类?)
    • [5.6 如何理解undo链](#5.6 如何理解undo链)
    • [5.7 撤销日志为什么需要落盘?](#5.7 撤销日志为什么需要落盘?)
      • [5.7.1 衍生问题1: 撤销日志在内存中如何记录](#5.7.1 衍生问题1: 撤销日志在内存中如何记录)
      • [5.7.2 衍生问题2: 撤销日志的回滚过程是怎样的?](#5.7.2 衍生问题2: 撤销日志的回滚过程是怎样的?)
      • [5.7.3 衍生问题3: 撤销日志的清理过程是怎样的?](#5.7.3 衍生问题3: 撤销日志的清理过程是怎样的?)
  • [6. 双写缓冲区 - Doublewrite Buffer![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/fcf7da6aea3f4584bd538524229be4ca.png)](#6. 双写缓冲区 - Doublewrite Buffer)
    • [6.1 双写缓冲区的作用](#6.1 双写缓冲区的作用)
      • [6.1.1衍生问题1: 双写缓冲区中的数据保存在哪里?](#6.1.1衍生问题1: 双写缓冲区中的数据保存在哪里?)
  • [7. 重做日志 - Redo Log](#7. 重做日志 - Redo Log)
    • [7.1 重做日志的作用](#7.1 重做日志的作用)
      • [7.1.1 衍生问题1: 为什么要用Redo Log,而不是直接写磁盘?](#7.1.1 衍生问题1: 为什么要用Redo Log,而不是直接写磁盘?)
      • [7.1.2 衍生问题2: Redo Log的写入时机(常考面试题)](#7.1.2 衍生问题2: Redo Log的写入时机(常考面试题))
    • [7.2 Redo Log的格式是怎样的?](#7.2 Redo Log的格式是怎样的?)
      • [7.2.1 衍生问题1: data部分的具体内容是什么?](#7.2.1 衍生问题1: data部分的具体内容是什么?)
    • [7.3 RedoLog的类型分为哪些?](#7.3 RedoLog的类型分为哪些?)
    • [7.4 不同日志类型对应了哪些操作](#7.4 不同日志类型对应了哪些操作)
    • [7.5 什么是Mini-Transaction](#7.5 什么是Mini-Transaction)
      • [7.5.1 一个DML操作会对数据页产生什么样的影响?](#7.5.1 一个DML操作会对数据页产生什么样的影响?)
      • [7.5.2 在记录RedoLog时服务器崩溃了导致日志不完整怎么办?](#7.5.2 在记录RedoLog时服务器崩溃了导致日志不完整怎么办?)
      • [7.5.3 Mini-Transaction的定义](#7.5.3 Mini-Transaction的定义)
      • [7.5.4 衍生问题1; 如何标识一组RedoLog属于同一个MTR?](#7.5.4 衍生问题1; 如何标识一组RedoLog属于同一个MTR?)
      • [7.5.5 衍生问题2: 事务与Mini-Transaction是什么关系](#7.5.5 衍生问题2: 事务与Mini-Transaction是什么关系)
    • [7.6 RedoLog的是如何写入缓冲区的?](#7.6 RedoLog的是如何写入缓冲区的?)
      • [7.6.1 用来组织RedoLog的数据结构是什么?](#7.6.1 用来组织RedoLog的数据结构是什么?)
      • [7.6.2 RedoLog Block在Log Buffer中是如何组织的?](#7.6.2 RedoLog Block在Log Buffer中是如何组织的?)
      • [7.6.3 从日志缓冲区写RedoLog时从内存中的哪个地址开始写](#7.6.3 从日志缓冲区写RedoLog时从内存中的哪个地址开始写)
      • [7.6.4 不同的事务在并发执行时如何执行RedoLog](#7.6.4 不同的事务在并发执行时如何执行RedoLog)
    • [7.7 RedoLog的刷盘时机](#7.7 RedoLog的刷盘时机)
      • [7.7.1 衍生问题1: 刷盘策略可以进行配置吗](#7.7.1 衍生问题1: 刷盘策略可以进行配置吗)
      • [7.7.2 不同的刷盘策略有什么影响?](#7.7.2 不同的刷盘策略有什么影响?)
    • [7.8 RedoLog日志文件的格式?](#7.8 RedoLog日志文件的格式?)
      • [7.8.1 LogBuffer中的RedoLogBlock与磁盘中的RedoLogBlock有哪些不同?](#7.8.1 LogBuffer中的RedoLogBlock与磁盘中的RedoLogBlock有哪些不同?)
      • [7.8.2 衍生问题2: 重做日志文件管理区包含了哪些信息](#7.8.2 衍生问题2: 重做日志文件管理区包含了哪些信息)
    • [7.9 什么是CheckPoint检查点?](#7.9 什么是CheckPoint检查点?)
      • [7.9.1 哪些RedoLog可以被覆盖](#7.9.1 哪些RedoLog可以被覆盖)
      • [7.9.2 如何记录可以覆盖的日志文件位置?](#7.9.2 如何记录可以覆盖的日志文件位置?)
    • [7.10 如何根据RedoLog进行崩溃恢复?](#7.10 如何根据RedoLog进行崩溃恢复?)
      • [7.10.1 如何确定哪些日志需要恢复](#7.10.1 如何确定哪些日志需要恢复)
      • [7.10.2 如何获得最新的checkpoint_lsn和恢复的起点?](#7.10.2 如何获得最新的checkpoint_lsn和恢复的起点?)
      • [7.10.3 如何进行恢复?](#7.10.3 如何进行恢复?)
      • [7.10.4 如何确定哪些日志在崩溃前已经落盘?](#7.10.4 如何确定哪些日志在崩溃前已经落盘?)

1. InnoDB存储引擎包含哪些磁盘文件?

包含系统表空间文件,双写缓冲区,撤销表空间文件(Undo),重做日志文件(Redo),独立表空间文件(File-Pre),

临时表空间文件,通用表空间文件.

1.1 衍生问题1: 什么是表空间?

  • 表空间可以理解为MySQL为了管理数据而设计的一种数据结构,主要描述的对结构的定义,表空间文件是对定义的具体实现,以文件的形式存在于磁盘上,以后我们说表空间的时候就说的是表空间文件.
  • InnoDB存储引擎的表空间包括: 系统表空间,独立表空间,通用表空间,临时表空间,撤销表空间.

2. 系统表空间

2.1 系统表空间的作用?

  • 系统表空间存储了MySQL中所有的系统表的数据,也包括数据字典.
  • 系统表空间也是变更缓冲区的存储区域,当数据库服务器关闭时,没有合并到缓冲池的二级索引修改操作被保存到系统表空间.
  • 在以前的版本中,系统表空间也包含双写缓冲区,从MySQL8.0.20开始,双写缓冲区从系统表空间中移动到单独的文件中.

2.2 系统表空间保存在哪里?

  • 系统表空间可以对应一个或者是多个数据文件,默认情况下,MySQL在data目录中创建了一个系统表空间数据文件ibdata1.系统表空间数据文件的大小和数量由innodb_data_file_path启动选项定义.

    mysql> SHOW VARIABLES LIKE 'innodb_data_file_path';
    +-----------------------+------------------------+
    | Variable_name | Value |
    +-----------------------+------------------------+
    | innodb_data_file_path | ibdata1:12M:autoextend | # 默认值
    +-----------------------+------------------------+
    1 row in set, 1 warning (0.04 sec)

  • 总结: 根据实际的应用场景可以通过配置对应的系统变量来指定数据文件的大小,名称,数量和其他属性.

3. 独立表空间

3.1 独立表空间有哪些作用?

  • 独立表空间包含单个InnoDB表的数据和索引,默认情况下每张表都对应一个表空间数据文件,便于维护,所以叫独立表空间.

3.1.1 衍生问题1: 独立表空间文件存在哪里?

  • 独立表空间在data/database_name/目录之下的table_name.ibd数据文件中创建..ibd文件与表同名.例如,表test_db.t1数据文件,如下所示:
sql 复制代码
# 选择数据库
USE test_db;
# 建表
CREATE TABLE t1 (
 id INT PRIMARY KEY AUTO_INCREMENT,
 name VARCHAR(100)
 ) ENGINE = InnoDB;
  • 查看数据文件

    root@guangchen-vm:/var/lib/mysql/test_db# ll
    total 752
    drwxr-x--- 2 mysql mysql 4096 10⽉ 26 18:29 ./
    drwxr-x--- 8 mysql mysql 4096 10⽉ 26 15:32 ../
    -rw-r----- 1 mysql mysql 114688 9⽉ 6 15:30 classes.ibd
    -rw-r----- 1 mysql mysql 114688 9⽉ 6 15:30 course.ibd
    -rw-r----- 1 mysql mysql 114688 9⽉ 6 15:30 score.ibd
    -rw-r----- 1 mysql mysql 114688 9⽉ 6 15:30 student.ibd
    -rw-r----- 1 mysql mysql 114688 10⽉ 26 18:29 t1.ibd # t1表对应的表空间数据⽂件

3.1.2 衍生问题2: 每个表都对应一个独立表空间吗?

  • 不一定
  • 默认每张表都对应一个表空间数据文件,但是也可以通过系统变量innodb_file_per_table控制开启或者禁用是否每张表都会生成一个独立的表空间文件,如果禁用会在系统表空间中创建表.

4. 撤销表空间

4.1 撤销表空间的作用?

  • 撤销表空间中包含撤销日志(Undo Log),撤销日志记录了如何撤销事务对聚集索引记录的最新更改(事务的回滚),通过对事务的回滚,从而保证了事务ACID的特性中的原子性.

4.1.1 衍生问题1: 什么是撤销日志?

介绍完撤销表空阿金之后详细介绍撤销日志

4.2 在使用MySQL时没有手动创建撤销表空间,他是什么时候被创建的?

MySQL初始化时会在数据目录下创建两个默认的撤销表空间,数据文件名分别为undo_001和undo_002 .数据字典中对应的undo表空间名称为innodb_undo_001innodb_undo_002.

复制代码
root@guangchen-vm:/var/lib/mysql# ll
# ...省略
-rw-r----- 1 mysql mysql 16777216 11⽉ 1 09:59 undo_001
-rw-r----- 1 mysql mysql 16777216 11⽉ 1 09:59 undo_002

什么叫数据字典?

数据字典(Data Dictionary)是MySQL数据库中的一个核心系统组件,它存储了关于数据库结构的元数据(metadata),相当于数据库的"目录"或"说明书".记录所有数据库对象(如表、索引、列、约束等)的定义信息.

4.2.1 衍生问题1: 默认的撤销表空间名称和路径是什么?

  • 要查看撤销表空间和路径,请查询INFORMATION_SCHEMA.FILES1.

    mysql> SELECT TABLESPACE_NAME, FILE_NAME FROM INFORMATION_SCHEMA.FILES
    WHERE FILE_TYPE LIKE 'UNDO LOG';
    +-----------------+------------+
    | TABLESPACE_NAME | FILE_NAME |
    +-----------------+------------+
    | innodb_undo_001 | ./undo_001 | # 数据字典中的名称为innodb_undo_001,对应的数据⽂为./undo_001
    | innodb_undo_002 | ./undo_002 | # 数据字典中的名称为innodb_undo_002,对应的数据⽂为./undo_002
    +-----------------+------------+
    2 rows in set (0.00 sec)

4.2.2 衍生问题2: 可以手动撤销表空间吗?

可以,通过使用CREATE UNDO TABLESPACE语句可以创建撤销表空间.基本语法如下

sql 复制代码
CREATE UNDO TABLESPACE tablespace_name ADD DATAFILE 'file_name.ibu';

那么什么时候需要手动创建撤销表空间呢?
对于长时间运行的大事务,撤销日志变得很大,通过创建额外的撤销表空间来防止单个撤销表空间变得很大,从MySQL8.0.14开始,可以在运行时使用CREATE UNDO TABLESPACE语法创建额外的撤销表空间.

在自己创建撤销表空间需要注意什么呢?

  • 通过系统变量innodb_undo_directory 指定撤销表空间的默认存放路径,如果不指定默认位置为数据目录.
  • 撤销表空间文件必须以.ibu为扩展名,建议使用唯一的撤销表空间名,避免在以后移动和复制的时候文件名发生冲突
  • 最多支持127个undo表空间,包括实例初始化时创建的两个默认表空间.

4.3 如何删除撤销表空间

  • 从MySQL8.0.14开始,使用CREATE UNDO TABLESPACE语法创建的撤销表空间可以使用DROP UNDO TABALESAPCE语法删除.
  • 撤销表空间子啊被删除之前必须是空的,要清空撤销表空间,首先必须使用ALTER UNDO TABLESAPCE语法将撤销表空间标记为不活动,以便该表空间不再用于其他新的事物.语法如下:
sql 复制代码
# 把表空间标记为不活跃
ALTER UNDO TABLESPACE tablespace_name SET INACTIVE;
# 删除表空间
DROP UNDO TABLESPACE tablespace_name;
  • 总结: 从MySQL8.0.14开始使用CREATE UNDO TABLESPACE语法创建的撤销表空间可以使用DROP UNDO TABALESPAC语法删除,但要确保撤销表空间在被删除之前必须是空的,具体操作如下:
    • 将撤销表空间标记为不活动
    • 等待当前undo表空间的事务完成后表空间被截断到初始大小
    • 执行删除操作

4.3.1 衍生问题1: 撤销表空间为不活动并且已经被截断为初始大小,这时候不想删除是否可以重新启用?

可以通过以下方法重新激活:

sql 复制代码
ALTER undo tablespace tablespace_name SET ACTIVE;

4.4 如何查看撤销表空间的状态?

  • 通过show status like 'Innodb_undo_tablespaces%'语句可以查询撤销表空间的基本信息

    mysql> SHOW STATUS LIKE 'Innodb_undo_tablespaces%';
    +----------------------------------+-------+
    | Variable_name | Value |
    +----------------------------------+-------+
    | Innodb_undo_tablespaces_total | 2 | # 撤销表空间的总数
    | Innodb_undo_tablespaces_implicit | 2 | # 隐式(InnoDB)创建撤销表空间数量
    | Innodb_undo_tablespaces_explicit | 0 | # 显式(用户)创建撤销表空间数量
    | Innodb_undo_tablespaces_active | 2 | # 活动中的撤销表空间数量
    +----------------------------------+-------+

5. 撤销日志 - undo log

5.1 什么是撤销日志

  • 当事务对数据进行修改的时候,每个修改操作都会在磁盘上创建一个与之对应的Undolog,当事物需要回滚时,会根据Undolog逐一进行撤销操作,从而保证事务的原子性.也就是说撤销日志是为事务回滚操作而诞生的机制,它是一个撤销操作记录的集合.
  • undo日志保存在undo日志段中,undo日志段位于回滚段中,回滚段位于undo表空间和全局临时表空间中.

5.1.1 衍生问题1: 撤销日志的写入时机

  • 在事务执行每个DML之前,会根据DML构建对应的撤销日志,并申请一个undolog segment(撤销日志段),把日志记录在申请到的撤销段中,再执行真正的DML操作,执行过程如下所示:

5.2 撤销日志在撤销表空间中的组织形式是怎么样的?

  • undo log segments(撤销日志段)也称为撤销段,一个撤销日志段可以保存多个事务的回滚日志,但是同一时间整你被一个活跃事务使用,对应的空间在事务提交或者回滚后才可以被重用.
  • rollback segments(回滚段)中包含撤销日志段,通常位于undo表空间和全局临时表空间中,使用系统变量Innodb_rollback_segments可以定义分配给每个undo表空间和全局临时表空间的回滚数量,默认值为128,取值范围是[1,128].
  • 一个回滚段支持的事务数取决于回滚段中的undo slots(撤销日志槽)和每个事务所需的undo日志数,一个回滚段中的undo槽数可以根据InnoDB页面大小进行计算.
  • 回滚段中还记录了HistoryList的头结点History List Base Node,以及回滚段的大小.
  • 总结: 撤销表空间包含rollback segment(回滚段) ,每个回滚段中包含若干undo log(槽数),每个槽对应一个undo log segment(撤销日志段),撤销日志段中包含着具体的撤销日志.

5.3 撤销日志的格式是怎样的?

  • 撤销日志格式示意图如下:

  • 一条记录在undolog页中的Undolog日志大体包含两部分: 分别是记录了undo类型,表id,上一条,下一条日志的偏移地址等在内的"基本信息 ",以及记录了不同操作和数据的"操作信息",如上图所示

5.3.1 衍生问题1: 在事务中不同的DML操作对应的撤销日志是否不同?

在执行DML语句操作数据库时,不同的SQL语句对应的撤销操作不同,不同的撤销操作对应的undolog存储格式也不同 ,按照增删改等不同的DML操作,生成对应的撤销日志.

5.3.2 衍生问题2: 不同操作对应的撤销日志如何区分?

  • undo类型有很多种,最常见的就是增删改 ,分别用TRX_UNDO_INSERT_REC,TRX_UNDO_DEL_MARK_REC TRX_UNDO_UPD_EXIST_REC表示,如图所示:
  • 新增时的undolog操作信息相对简单,只记录了主键值,主键长度等主键信息.
  • 删除时除了记录主键信息之外,还记录了旧事物id,旧的roll_pointer信息,用来构建有序的undo版本链,还会记录一些被索引字段的信息.
  • 更新时较为复杂,如果不更新主键,则和删除类似,会记录主键信息,旧的事物id,旧的roll_pointer信息,被索引字段的信息和被更新的信息,如果更新了主键,则会记录两台undolog,一条删除和一条新增.

5.4 撤销日志是如何组织在一起的?

  • 一条条undo log会被逐一放在undolog页中 ,undolog页和其他类型的页一样都会包含头尾信息,除此之外还有undolog特有的信息,包括:
    • undo page header: 记录了undo log页类型,日志偏移位置,下一页链表引用等信息
    • undo log segment header: 记录了回滚日志段信息
    • undo log header: 记录了产生这条日志的事务id: TRX ID,事务提交的顺序,TRX No和其它事务相关的信息.
  • 在这三个特有的头信息之外,其他的空间都会用来记录undolog日志,如果某个事务很大,一个undo log页没有办法完整记录,就需要申请新的undolog页 ,然后通过undo page header中链表引用信息链接到前一个页,后面的这些也只需要Undolog并不需要记录undo头信息.
  • 这个有undolog构成的链表称作undo链,在事务中起着重要的作用.

5.4.1 衍生问题1:事务提交后undolog是否就可以删除了?

  • 对于新增操作所记录的undolog日志,在事务提交之后就可以直接删除了,而删除和更新的undolog体质还需要服务与事务的的MVCC,所以并不可以直接删除,而是加入到History List中.

5.5 撤销日志如何分类?

  • undo log分为两个大类,一类只记录新增操作,事务提交之后可以直接删除,另一类记录删除个更新操作,所以响应的回滚链也被分为两个,Insert undo链和update undo链(delete+update)
  • 另外普通表和临时表分别对应这两类undo链,如是一个事务既有新增又有修改并且用到了临时表,那么这个事务最多可以分配四个撤销日志,也就是四个undo链,分别是:
    • 对用户定义的普通表进行Insert操作
    • 对用户定义的普通表进行update和delete操作
    • 对用户定义的临时表进行Insert操作
    • 对用户定义的临时表进行update和delete操作

5.6 如何理解undo链

  • Insert undo链和update undo链采用了不同的组织方式
  • 对于新增操作,Insert undo链中的每个undolog都会对应一条新的数据行,这个数据行中用roll_pointer信息来关联undo log,在回滚时就可以通过它找打需要回滚的undo log了,如图所示:
  • 对于删除和更新,update undo链中的每个undo log也都对应一个数据行,每次更新都会通过undo log中的roll_pointer进行关联,从而每个数据行都会构成一个undolog版本链,回滚的时候就可以依序撤销,这个扮嫩来拿在事务的MVCC中起到了非常重要的作用,可以保证事务的"隔离性".
  • 以下是一个关于更新操作的undo链

5.7 撤销日志为什么需要落盘?

  • 在对数据进行修改时,都是在内存中操作的,也就是在BufferPool中修改对饮的数据页.在修改数据页之前先把对应的撤销日志记录在内存中,如果此时事务回滚直接根据内存中的撤销日志做回滚操作即可.

  • 在修改完成提交事务之后,脏页进行落盘的操作,此时事务不提交,不能回滚,所以撤销日志也就失效了.

  • 当服务器崩溃的时候,如果事务没有提交,所有的修改都在内存中,还没有落盘,对于修改直接丢弃,如果事务已经提交,则根据重做日志和双写缓冲区中的备份进行恢复.

  • 通过分析看上去撤销日志并不需要落盘,其实以上的分析场景并没有考虑到全部的场景,比如大事务的运行,MVCC中版本链什么时候可以销毁,事务的不同隔离级别等因素.

  • 总结: 在运行大事务时,InnoDB为了避免大事务提交时统一落盘操作带来的性能问题,允许在事务进行的过程中就进行落盘操作并记录对应的Undolog,当发生崩溃恢复时,通过Undolog把未提交的事务进行回滚.

  • 如果一个事务已经提交,还有其他事务需要访问版本链中对应的Undolog,那么也需要相应的撤销日志保存在History List中.

  • 不同的隔离级别下,没有提交的事务也可能会落盘,回滚时依然需要完成撤销操作.

5.7.1 衍生问题1: 撤销日志在内存中如何记录

  • 与数据页在内存中的方式保存方式相同,撤销日志在内存中页保存在Buffer Pool中,与磁盘中的undolog页结构一直,内存中每个Undolog页通过控制块管理.
  • 在内存中使用四个链表来管理正在使用的Undolog页和空闲Undolog页,根据不同的日志类型分为:
    • Insert List: 正在被使用的用来管理Insert类型的Undolog页
    • Insert Cache List: 空闲的或者被清理后可以被后续事务重用的Insert类型Undolog页.
    • update List: 正在被使用的用来管理update类型的Undolog页.
    • update Cache List: 空闲的或被清理后可以被后续的事务重用的update类型Undolog页.

5.7.2 衍生问题2: 撤销日志的回滚过程是怎样的?

  • 回滚操作可以是用户通过rollback主动触发,也可以发生在崩溃恢复时,不论是那种触发条件,回滚操作都是相同的,基本过程就是读取该事务的Undolog,从后向前依次进行逆向操作,从而恢复索引记录.
  • 对于Insert类型的回滚操作就是delete,在删除的过程中会重新调整主键索引和二级索引
  • 对于update和delete类型的回滚操作,主要是回退这次操作在所有主键索引和二级索引的影响,可能包括重新插入被删除的二级索引记录,去除管理信息中的delete mark标记,将主索引记录修改会之前的值等.
  • 完成回滚的undolog会进行回收,将不再使用的Undolog页的磁盘空间还给Undo log segment,这个过程是写入过程的逆操作.

5.7.3 衍生问题3: 撤销日志的清理过程是怎样的?

  • InnoDB通过保存多份Undolog的历史版本来实现MVCC,当某个历史版本已经确认并不会任何现有的和未来的事务访问时,就应该被清理掉.
  • 当开启一个事务时,都会被分配一个事务编号trx_id,而事务进行度操作会创建一个ReadView,并记录当前所有事务中的最小活跃事务编号m_low_limit_id ,如果版本链中日志的trx_id< m_low_limit_id ,则表示当前读操作发生时,日志对应的事务已经提交,其修改的新版本是可见的,此时不需要再通过undo版本链构建之前的版本,这个事务的Undolog也就可以被清理了.
  • undo的清理工作是由专门的后台线程进行扫描和分发,并由多个线程进行清理,这里不再过多讨论.

6. 双写缓冲区 - Doublewrite Buffer

6.1 双写缓冲区的作用

双写缓冲区是磁盘的一个存储区域,当InnoDB将缓冲池中的数据页写入到磁盘上的表空间数据文件之前 ,先将对应的页写到双写缓冲区,如果在数据真正落盘的过程中出现了以外退出,比如操作系统,存储子系统崩溃或者异常断电的情况,InnoDB在崩溃恢复的时候可以从双写缓冲区中找到一份完好的页副本,执行过程如下图所示:

6.1.1衍生问题1: 双写缓冲区中的数据保存在哪里?

  • 在MySQL8.0.20之前,doublewrite缓冲区位于InnoDB系统表空间中,从MySQL8.0.20开始,doublewrite缓冲区默认存储区域位于数据目录下的doublewrite文件中.

    root@guangchen-vm:/var/lib/mysql# ll
    total 92948
    drwxr-x--- 8 mysql mysql 4096 11⽉ 5 11:15 ./
    drwxr-xr-x 73 root root 4096 10⽉ 30 12:07 ../

    ...省略

    默认创建两个双写缓冲区⽂件

    -rw-r----- 1 mysql mysql 196608 11⽉ 5 11:15 '#ib_16384_0.dblwr' # ⽂件1
    -rw-r----- 1 mysql mysql 8585216 11⽉ 1 10:34 '#ib_16384_1.dblwr' # ⽂件2

    ...省略

7. 重做日志 - Redo Log

7.1 重做日志的作用

  • 重做日志在保证事务的持久性和一致性方面起到了关键的作用.
  • 重做日志用于在数据崩溃之后恢复已提交的事务还没有来得及落盘的数据.重做日志以文件的形式保存在磁盘上,在正常的操作过程中,MySQL根据受影响的记录进行编码并写入重做日志文件,这些数据称为Redo,在重新启动时自动读取重做日志进行数据恢复.

7.1.1 衍生问题1: 为什么要用Redo Log,而不是直接写磁盘?

  • 我们来分析一下,首先明确一点,我们对数据进行的DML操作都会包含在事务当中,当完成并提交事务之后,在内存中被修改的数据页就要刷新到磁盘完成持久化.
  • 如果这次DML操作对应的修改开始刷盘的话,当服务器崩溃,没有被刷到磁盘的数据页就从内存中丢失,此时这个事务是不完整的,也就是没有保证事务的一致性.
  • 为了解决这个问题,InnoDB在执行每个DML操作的时候,当内存中的数据页修改完成之后,把修改的内容以日志的形式保存在磁盘上,然后对数据页进行真正的落盘操作,这样做就相当于对修改进行了一次备份,即使当服务器崩溃也不会受到影响,当服务器重启之后,可以从磁盘上的日志文件中找到上次崩溃之前没有来得及落盘的数据继续执行落盘操作.

7.1.2 衍生问题2: Redo Log的写入时机(常考面试题)

当发生数据修改操作时追加重做日志==,已落盘数据对应的日志被记录为一个检查点,检查点之前的数据被置为无效,所以重做日志文件可以循环使用==,以一个更新操作为例,重做日志的写入过程与时机,如下图所示:

  • 前面已经介绍过为什么要用Log Buffer,因为每次进行DML操作都会进行一次磁盘IO,这样会严重影响效率,所以把日志统一写入内存的Log Buffer,根据刷盘策略统一进行落盘操作,可以实现一次磁盘IO写入多条日志,从而提升效率.

双写缓冲区和RedoLog都用于数据的恢复,那么他们有什么区别吗?

  • 双写缓冲区主要防止数据页部分写入,即在写入的过程中发生了数据页损坏,在恢复的时候看看表空间文件中的数据页有没有损坏的,如果有损坏的,就从双写缓冲区中恢复,这个过程发生在重做日志恢复之前,其中记录的是数据页而不是操作日志.
  • RedoLog主要用于记录所有对数据页的物理更改,用户前滚操作,它在双写缓冲区恢复之后进行,在双写缓冲区对于损坏的数据页进行恢复之后,将Redolog中未刷新到表空间文件中的数据重新应用,其中存储的不是数据页,而是操作.
  • 两者是这样共同保证数据的持久性和一致性的:
  1. 启动恢复时,首先检查双写缓冲区中的页是否完整
  2. 用双写缓冲区修复任何损坏的页(防止后续重做日志应用到损坏页上)
  3. 然后应用重做日志,重放所有已提交但未持久化的更改
  4. 最后回滚未提交的事务(使用undo log

7.2 Redo Log的格式是怎样的?

在介绍RedoLog的格式之前,我们先来分析一下RedoLog中需要记录哪些内容

  • 当进行DML操作时,首先需要修改内存中的数据页,但是修改的数据有可能只是数据页中很少的一部分内容,甚至有可能值修改几个字节,那么在Redo Log中要记录整个数据页吗?当然不是,如果每次保存整个数据页的话就有太多无用的数据写入日志,严重影响效率而且浪费空间.
  • 为了节省空间提高效率,RedoLog只记录被修改的内容,比如当前的DML修改了那个表空间,表空间中的那个数据页,数据页中多少偏移量处的值修改成了什么.这样就可以用很小的日志记录当前对数据页所做的修改,大大节省了空间.
  • 每条RedoLog日志的结构如下图所示:

7.2.1 衍生问题1: data部分的具体内容是什么?

  • data部分又可以分为: 数据页中的偏移量,修改内容的长度和具体的修改内容如下图所示:

7.3 RedoLog的类型分为哪些?

RedoLog的类型根据数据操作的不同场景和对日志的优化方式有几十种之多,总体可以分为:

  • 用于数据页的日志类型,比如对数据页的修改
  • 用于表空间文件的日志类型,比如对表空间的修改
  • 提供额外信息的日志类型.

7.4 不同日志类型对应了哪些操作

不同的日志类型对一个的日志内容和作用各不相同

  • 用于数据页的日志类型
    主要用于记录数据页的修改,比如创建和删除数据页,以及对数据的增删改查操作.
  • 用于表空间文件的日志类型
    主要记录对表空间文件的修改
  • 提供额外信息的日志类型
    主要标记一个Mini-Transaction的结尾

7.5 什么是Mini-Transaction

7.5.1 一个DML操作会对数据页产生什么样的影响?

  • 一个Insert操作为例,对数据页的影响一般分为两种情况:如果写入记录所在的数据页空间充足,足够存储一条要写入的记录,那么就可以直接写入,如下如所示:
  • 如果写入的数据页空间不足,无法放下这条记录,由于在数据页中真实数据时按主键顺序排列的,那么就要新建一个数据页,对原来的数据进行调整,把一部分数据复制到新的数据页中,以便在目标页上留出足够的空间来保存即将写入的记录,此时对应的示意图如下所示;
  • 通过以上两种情况下插入一条记录的分析可以看出,当数据页空间充足的情况下可以直接写入数据库,并记录一条RedoLog即可.
  • 当数据页空间不足无法放下这条记录的情况下,会创建一个新数据页,同时还有数据的复制和写入,索引树非叶子结点上修改,在实际的执行过程中有对表空间中段,区中统计信息的修改等,这意味一个简单的Insert操作会产生很多的RedoLog.包括对数据页的修改日志和对表空间文件的修改日志.

7.5.2 在记录RedoLog时服务器崩溃了导致日志不完整怎么办?

那么这时候有一个问题需要考虑,试想一下如果执行这一系统操作的时候,RedoLog只记录了一半服务器就崩溃了,那么当服务器重启的时候如果按照RedoLog进行恢复,得到的结果坑定是错误的,所以在记录RedoLog的时候就要保证一个DML所对应的一系列日志必须是完整的才可以执行恢复操作,否则就不执行恢复.

7.5.3 Mini-Transaction的定义

  • Mini-Transaction就是针对以上的操作定义的概念,也就是说把记录一个DML操作的过程称为一个Mini-Transaction,简称MTR,一个所谓的MTR包含一个DML操作产生的一组完整的日志,在进行崩溃恢复这一组RedoLog作为一个不可分割的整体.
  • 这里说的不可分割的组是MySQL中定义的,常见的有:
    • 向聚簇索引对应的b+树的页面中插入一条记录时产生的RedoLog不可分割.
    • 向某个二级索引对应的b+树的页面中插入一条数据的记录产生的RedoLog不可分割
    • 还有其他的一些对页面的访问操作时产生的RedoLog不可分割.
  • 每条语句根据具体的执行情况可能会产生多个MTR
  • 总结: Mini-Transaction是MySQL内部对底层数据页的一个原子操作,包含一个DML操作产生的一组完整的日志,保证数据库异常恢复时数据页中数据的一致性.

7.5.4 衍生问题1; 如何标识一组RedoLog属于同一个MTR?

在执行DML操作的过程中,每一个对数据页的修改都会记录一条RedoLog,这些日志会被顺序记录下来,并在这组日志的最后加一条特殊的日志标识作为一个MRT的结尾.

7.5.5 衍生问题2: 事务与Mini-Transaction是什么关系

Mini-Transaction是包含的是一个DML操作对应的一组RedoLog,而一个事务中可能会包含多个DML操作,所以一个事务中包含一个或者是多个SQL语句,一个SQL语句包含多个MRT,一个MRT中包含一条或者是多条RedoLog,他们之间的关系如下如所示:

7.6 RedoLog的是如何写入缓冲区的?

这个问题可以理解为RedoLog的写入过程,要了解写入过程,必须先邪少RedoLog在内存和文件中时如何进行描述和组织的,我们提出一下几个问题:

7.6.1 用来组织RedoLog的数据结构是什么?

  • 用来组织RedoLog的数据结构是Redo页,页的大小是512B,也可以称为一个RedoLogBlock,RedoLogBlock的示意图如下;

    在一个RedoLogBlock中,包含用来存储管理信息的块头Log Block Header和块尾Log Block Trailer,其他的空阿金是真正用来存储体质的区域Log Block Body.

7.6.2 RedoLog Block在Log Buffer中是如何组织的?

  • 在内存中RedoLog存储在日志缓冲区Log Buffer中,日志缓冲区是服务器启动时==向操作系统申请的一片连续的内存区域,并被划分为若干个连续的RedoLogBlock,==用来存储即将要写入的磁盘日志文件数据,如下如所示:

    向日志缓冲区中写入日志是一个顺序写入的过程,那么有一个首先要解决的问题,当有一个日志需要写入缓冲区的时候,应该往哪个Block中的位置写呢?

7.6.3 从日志缓冲区写RedoLog时从内存中的哪个地址开始写

InnoDB提供了一个名为buf_free的全局变量,该变量表示后后续写入日志在LogBuffer中的其实位置,如图所示:

7.6.4 不同的事务在并发执行时如何执行RedoLog

  • 通过前面的介绍了解到,InnoDB以MTR为单位记录RedoLog,一个事务中包含多个MTR,一个MTR包含到多条RedoLog,这些RedoLog时一个不可分割的日志组.
  • 一个事物在执行过程中不是每生成一条RedoLog就写入到LogBuffer中,而是把生成的RedoLog先缓存到内存中的一个区域,当一个MTR执行完之后把这组日志一起复制到LogBuffer中.
  • 假设有两个事务T1,T2并发执行,每个事务中都包含2个MRT,即事务T1包含mtr_t1_1和mtr_t1_2,T2包含mtr_t2_1和mtr_t2_2,如下图所所示:

    在并发环境下,不同的事务中的MTR是交替执行的,当一个MTR执行完成之后对应的RedoLog会被写入LogBuffer中.
    LogBuffer中日志的写入形式如下如所示:

7.7 RedoLog的刷盘时机

  • LogBuffer空间不足的时候; LogBuffer空间是有限的,可以通过系统变量innodb_log_buffer_size设置,如果当前的LogBuffer中的RedoLog占用了LogBuffer总容量的一半左右就会触发刷盘.
  • 事务提交之前: 当事务提交时,事务中对应的MTR已经完全记录在了LogBuffer中,在数据真正落盘之前,需要把对应的RedoLog刷新到磁盘.
  • 后台线程定时刷盘: 后台的Master Thread线程,大约每秒会把LogBuffer中的RedoLog刷新到磁盘.
  • 正常关闭服务器时: 在服务关闭之前会把LogBuffer中的RedoLog刷新到磁盘.
  • 做检查点操作的时候,这个后面细说.

7.7.1 衍生问题1: 刷盘策略可以进行配置吗

通过设置系统变量innodb_flush_log_at_trx_commit设置写入和刷盘策略,默认值为1:

  • 0: 日志每秒写入系统缓冲区并刷新到磁盘,未写入系统缓冲区的事务日志可能会在MySQL崩溃时丢失.
  • 1: 日志在每次提交事务时写入系统缓冲区并刷新到磁盘.
  • 2: 日志在每次事务提交后写入系统缓冲区并每秒一次刷新到磁盘,未刷新到磁盘的日志可能在系统崩溃时丢失.

7.7.2 不同的刷盘策略有什么影响?

首先我们要知道LogBuffer,操作系统和磁盘中日志文件的关系,如图所示:

  • 值为0: 表示日志每秒写入操作系统缓存并刷新到磁盘,如果MySQL崩溃,那么在1秒内没有写入操作系统的RedoLog将会丢失.
  • 值为2: 日志在每次提交事务后写入系统缓冲区并每秒一次刷新到磁盘,此时已提交的事务RedoLog全部写入了系统缓存,MySQL无论是否崩溃,RedoLog都会以指定的时间刷新到磁盘,但是如果服务器崩溃,将会导致操作系统缓存中的RedoLog丢失.
  • 值为1: 日志在每次事务提交时写入系统缓冲区并刷新到磁盘,此时RedoLog从LogBuffer中写入操作系统并立即刷新到磁盘,从而尽可能保证日志的完整性,推荐使用.

7.8 RedoLog日志文件的格式?

7.8.1 LogBuffer中的RedoLogBlock与磁盘中的RedoLogBlock有哪些不同?

  • 在内存中的LogBuffer是一片连续的内存空间,被划分为了若干个512字节大小的RedoLogBlock用来保存RedoLog,将将LogBuffer中的RedoLog刷新到磁盘,本质上就是把RedoLogBlock写入日志文件中,所以RedoLog对应的日志文件其实也是有若干个512字节大小的Block组成.MySQL会根据配置生成一组撤销日志文件,每个文件的格式和大小都一样,都由两部分组成:
    • 管理区: 前2048个字节,也就是前4个Block存储一些日志文件的管理信息.
    • 数据区: 后面的都是用来存储LogBuffer对应的RedoLogBlock,即真正的RedoLog数据.
  • 所以LogBuffer中的RedoLogBlock与磁盘中的RedoLogBlock在结构上是相同的,只不过在磁盘上多了用于文件管理的文件头信息.

7.8.2 衍生问题2: 重做日志文件管理区包含了哪些信息

  • LOG_CHECKPOINT_1: 第一个日志文件中日志头的第一个检查点信息
  • LOG_ENCRYPTION: 日志文件头信息中的加密信息
  • LOG_CHECKPOINT_2 : 第一个日志文件中日志头的第二个检查点信息
  • LOG_FILE_HDR_SIZE: 日志文件头信息.其中最重要的信息就是日志文件中第一个LSN编号,用来判断RedoLog日志文件是否全部落盘,是否可以被覆盖.

7.9 什么是CheckPoint检查点?

  • RedoLog从内存刷到磁盘上的日志文件使用循环写入的方式,也就是从第一个日志文件顺序写到最后一个日志文件,当最后一个日志文件写满时又重新写第一个日志文件,那么就可能出现日志被覆盖的情况,那么哪些日志可以被覆盖,哪些不能被覆盖呢?

7.9.1 哪些RedoLog可以被覆盖

  • 首先回顾RedoLog的作用,RedoLog是用作崩溃后恢复没有落盘的事务,也就是说,当BufferPool中的脏页写入RedoLog,但是数据页还没有落盘时发生了崩溃,当服务器重启之后根据RedoLog进行恢复,这也是RedoLog的应用时机,所以这种状态下的RedoLog不能被覆盖.
  • 如果缓冲池中的脏页在记录RedoLog之后,也完成了真正的落盘操作,那么相应的RedoLog就没有用了,所以这部分RedoLog就可以被覆盖,如下图所示:
  • 经过分析可以看出,判断日志文件中的RedoLog是否可以覆盖的依据是他对应的数据页是否已经刷新到磁盘.

7.9.2 如何记录可以覆盖的日志文件位置?

前面介绍过InnoDB使用LSN是用来记录RedoLog总字节数,在这个基础上InnoDB采用一个全局变量checkpoint_lsn 来记录当前系统中可以被覆盖的日志总量是多少,也就是说checkpoint_lsn 记录已落盘的脏页对应的日志结束时的LSN的值,此时LSN小于checkpoint_lsn 的RedoLog就可以被覆盖

  • 当脏页刷新到磁盘之后,重新计算checkpoint_lsn的操作,就称为一次CheckPoint操作,也可以说是重置一次检查点 ,系统会用一个checkpoint_no变量记录发生checkpoint操作的次数,每做一次checkpoint操作checkpoint_no就加1.
  • 关于检查点相关的checkpoint_no,checkpoint_lsn以及写入偏移量的信息会被记录在第一个日志文件的管理区,同时InnoDB规定,当checkpoint_no的值是偶数时写到checkpoint1中,时奇数时写到checkpoint2中.
  • 总结: checkpoint也称为检查点,由于RedoLog文件是可以循环使用的,当最后一个文件写满时又会从第一个文件开始写入,这必将导致老的日志被覆盖,checkpoint是标记已经被刷新到磁盘的脏页对应的RedoLog可以被覆盖的一种操作,当日志的LSN已经小于落盘脏页对应的LSN都可以被覆盖.

7.10 如何根据RedoLog进行崩溃恢复?

7.10.1 如何确定哪些日志需要恢复

前面我们介绍过每一次checkpoint操作都会重新计算checkpoint_lsn,checkpoint_lsn之前的日志标识已经被刷到磁盘数据页所生成的RedoLog,既然已经被刷新到磁盘,也就没必要恢复,所以需要恢复的是checkpoint_lsn之后的日志.

7.10.2 如何获得最新的checkpoint_lsn和恢复的起点?

  • RedoLog文件组中的第一个文件的管理信息中有两个Block,checkpoint1和checkpoint2,其中都存储了checkpoint_lsn和checkpoint_no信息每次做checkpoint操作的时候,都会在这两个Block中交替写入checkpoint信息,只需要把这两个Block中保存的checkpoint_no值比较一下,哪个值大就表示那个就是最近一次checkpoint信息.这样我们就能拿到最近发生的checkpoint对应的checkpoint_lsn值以及他在RedoLog文件组中偏移量checkpoint_offset.

7.10.3 如何进行恢复?

假设现在的日志文件中有RedoLog,如图所示:

  • 第一条日志在checkpoint_lsn之前,表示已经落盘不用恢复.
  • checkpoint_lsn之后的日志可以通过顺序扫描的方式,根据日志记录的内容一次恢复对应的数据页.
  • InnoDB在顺序读取日志进行恢复的过程中采用了一些优化措施 : 首先根据日志的SpaceID和PageNo计算出哈希值,以这个哈希值为key,把SpaceID和pageNo相同的日志放到哈希表的同一个槽里,如果有多个SpaceId和PageNo相同的日志,那么按照日志生成的先后顺序使用链表连接起来,如下如所示:
  • 组织好之后,通过遍历哈希表,就可以把一个数据页中的修改全部恢复好,减少数据读取时的随机IO次数.

7.10.4 如何确定哪些日志在崩溃前已经落盘?

  • checkpoint_lsn之后的日志有可能根本没有落盘,但是也有可能已经落盘但是没有来得及做checkpoint,在恢复时如何区分呢?
  • 在页结构章节介绍过,磁盘上的每个页都包含一个File Header信息,其中又包含已被刷新到磁盘的lsn,在恢复执勤啊就可以通过当前日志对应的LSN与被刷新到磁盘的LSN进行比较,如果日志的LSN小于已经刷新的到磁盘的LSN,那就证明已经落盘,删除即可.
相关推荐
松涛和鸣1 小时前
72、IMX6ULL驱动实战:设备树(DTS/DTB)+ GPIO子系统+Platform总线
linux·服务器·arm开发·数据库·单片机
likangbinlxa1 小时前
【Oracle11g SQL详解】UPDATE 和 DELETE 操作的正确使用
数据库·sql
r i c k2 小时前
数据库系统学习笔记
数据库·笔记·学习
野犬寒鸦2 小时前
从零起步学习JVM || 第一章:类加载器与双亲委派机制模型详解
java·jvm·数据库·后端·学习
IvorySQL3 小时前
PostgreSQL 分区表的 ALTER TABLE 语句执行机制解析
数据库·postgresql·开源
·云扬·3 小时前
MySQL 8.0 Redo Log 归档与禁用实战指南
android·数据库·mysql
IT邦德3 小时前
Oracle 26ai DataGuard 搭建(RAC到单机)
数据库·oracle
惊讶的猫3 小时前
redis分片集群
数据库·redis·缓存·分片集群·海量数据存储·高并发写
不爱缺氧i3 小时前
完全卸载MariaDB
数据库·mariadb
纤纡.4 小时前
Linux中SQL 从基础到进阶:五大分类详解与表结构操作(ALTER/DROP)全攻略
linux·数据库·sql