在 MySQL 中事务的实现离不开 Redo Log 和 Undo Log,其中事务的隔离性是由“锁”和“MVCC”机制实现的,原子性和持久性是由 Redo Log 实现的,一致性是由 Undo Log 实现的。
一、Redo Log 基本概念
Redo Log 也被称作重做日志,只在 InnoDB 存储引擎中产生,用来保证事务的原子性和持久性,确保 MySQL 事务提交后,事务所涉及的操作要么全部执行成功,要么全部执行失败。
1. 作用
Redo Log 主要记录的是物理日志,也就是对磁盘上的数据进行的修改操作,往往用来恢复事务提交后的物理数据页,不过只能恢复到最后一次提交的位置。
2. 组成部分
Redo Log 通常包含两部分:一部分是内存中的日志缓冲(Redo Log Buffer,比较容易丢失),另一部分是存放在磁盘上的重做日志文件(Redo Log File,持久化到磁盘上的,不容易丢失)。
3. Redo Log 会记录哪些信息
- 事务 ID:每个事务都有一个唯一的事务 ID,Redo Log 会记录每个修改操作所属的事务 ID,用于表示一系列操作属于同一个事务,以便在恢复过程中正确地处理事务边界;
- 操作类型:Redo Log 记录了每个数据修改操作的类型,比如 INSERT、UPDATE、DELETE 等;
- 页号和偏移量:Redo Log 会记录被修改的数据也得页号和偏移量,以确定具体是哪个数据页发生了修改,这些信息用于在恢复过程中定位被修改的数据页,并将对应的修改操作应用到数据页上;
- 修改前后的数据值:Redo Log 会记录被修改的数据想的修改前和修改后的值,这些信息用于在恢复过程中正确地应用数据修改操作,将数据恢复到提交事务的状态;
- 事务提交信息:对于已提交的事务,Redo Log 会记录事务的提交信息,包括事务 ID、提交时间戳等,这些信息用于在恢复过程中,确定事务的提交状态。
4. 哪些情况下会产生 Redo Log 日志
- Redo Log 日志主要是用来保证事务的原子性和持久性的,所以在使用事务的操作时就会产生对应的 Redo Log 日志;
- Redo Log 日志主要用于记录对数据库中数据的更改操作,以便在数据库崩溃时进行恢复操作,因此在 INSERT、UPDATE、DELETE 语句即使没有明确地使用事务也会被视为一个独立的事务,记录到 Redo Log 日志中。
二、Redo Log 的基本原理
MySQL 使用 Redo Log 保证事务的原子性和持久性,在 MySQL 发生故障时,尽力避免内存中脏页数据写入数据表的 IBD 文件中,在重启 MySQL 服务时,可以根据 Redo Log 恢复事务已提交但还未写入 IBD 文件中的数据,从而完成对事务的持久化操作。
上图以商城用户下单后系统创建订单记录为例进行 MySQL 保证数据的原子性和持久性的演示:
- 在 MySQL 事务提交时,首先将数据写入到 Redo Log Buffer 内存缓冲区中(此刻数据库宕机后数据会丢失,无法恢复,但数据库数据还未修改,也无需恢复,对应后端直接下单失败);
- Redo Log Buffer 会根据配置文件中的刷盘策略,将缓冲区(Redo Log Buffer)中的数据“持久化”到磁盘内的 Redo Log 文件(此刻数据不会丢失);
- 数据库宕机后可以通过 Redo Log 文件中的记录将数据“恢复”到数据库中的 ibd 文件中。
- 系统可以根据需要查询加载数据库中的数据(ibd 文件数据),也可以向订单表中写入数据(持久化数据到 ibd 文件中)。
三、Redo Log 的刷盘规则
MYSQL 的 InnoDB 存储引擎中,通过提交事务时,强制执行写日志操作机制实现事务的持久化(为了保证在事务提交时,将日志提交到事务日志中,默认每次将 Redo Log BUffer 中的日志写入 Redo Log 时,都会调用一次操作系统的 fsync() 操作);
如上图所示:因为 MYSQL 进程和其占用的内存空间都工作在操作系统的用户内存空间中,所以 MYSQL 的 Log Buffer 也工作在操作系统的用户空间中;从用户空间的 Log Buffer 写入磁盘的 Redo Log 文件时,需要经过内核空间的 OS Buffer(因为打开日志文件时,没有使用 O_DIRECT 标志位)向磁盘写入数据。
1. 在 InnoDB 存储引擎中,Redo Log 的三种刷盘规则
- 开启事务,发出事务提交指令后是否刷新日志由变量 innodb_flush_log_at_trx_commit 决定;
- 每秒刷新一次,刷新日志的频率由变量 innodb_flush_log_at_timeout 的值决定,默认值是 1s;
- 当 Log Buffer 中已经使用的内存超过一半时,也会触发刷盘操作。
由于第二种策略和第三种策略都会有丢失数据的风险,在 MYSQL 中刷盘策略默认使用的是第一种策略进行刷盘,而第一种刷盘策略并不是随着事务的提交立刻写入磁盘,而是根据一定规则将 Log Buffer 中的数据刷写到磁盘从而保证 Redo Log 文件中数据的持久性
2. 第一种刷盘规则中的 3 中刷盘策略
开启事务,发出事务提交指令后是否刷新日志由变量 innodb_flush_log_at_trx_commit 控制,该变量有 3 个可取值,分别是 0、1 和 2,默认值为 1,三种值对应的刷盘策略如下
- 变量值为 0 时:每次提交事务时,不会将 Log Buffer 中的日志写入 OS Buffer,而是通过一个一个单独线程,每秒写入 OS Buffer 并调用 fsync() 函数写入磁盘的 Redo Log 文件;
- 变量值为 1 时:每次提交事务都会将 Log Buffer 中日志写入 OS Buffer 并且会调用 fsync() 函数将日志写入到磁盘的 Redo Log 文件中;
- 变量值为 2 时:每次提交事务都只是将数据写入 OS Buffer,之后每隔 1s 通过 fsync() 函数将 OS Buffer 中的数据同步到磁盘中的 Redo Log 中。
总结:对比上述 3 中策略,变量值为 0 或 2 时,同步数据到磁盘中的 Redo Log 性能相差不大,但是可能会丢失 1s 的数据,变量值为 1 时不会丢失数据,但是性能是 3 种之中最差的,MYSQL 中如过没有通过 innodb_flush_log_at_timeout 的值则默认为 1。
四、Redo Log 的写入机制
Redo Log 主要记录的是物理日志,文件内容是以顺序循环的方式写入,一个文件写满时会写入另一个文件,最后一个文件写满时,会向第一个文件写入数据,并且是覆盖写
从上图中可以看出
- Wirte Pos:是数据表中当前记录所在的位置,随着不断地向数据表中写入数据,这个位置会向后移动,当移动到最后一个文件的最后一个文件的最后一个位置时,又会回到第一个文件的开始位置进行写操作;
- CheckPoint:是当前要擦除的位置,这个位置也是向后移动的,移动到最后一个文件的最后一个位置时,也会回到第一个文件的开始位置进行擦除,只不过在擦除记录之前,需要把记录更新到数据库的数据文件中。
- Wirte Pos 和 CheckPoint 之间存在间隔时,中间的间隔表示还可以记录新的操作到磁盘的 Redo Log 中,如果 Write Pos 移动的速度较快,追上了 CheckPoint,则表示磁盘 Redo Log 数据已经写满,不能在写入数据,此时需要停止写入数据,擦除一些记录。
五、Redo Log 的 LSN 机制
LSN(Log Sequence Number)表示日志的逻辑序列号,在 InnoDB 存储引擎中,LSN 占用 8 个字节的存储空间,并且 LSN 是单调递增的,一般可以从 LSN 中获取如下信息:
- Redo Log 写入数据的总量
- 检查点位置
- 数据页版本相关信息
1. LSN 值说明
LSN 除了存在于 Redo Log 中外,还存在于数据页中,在每个数据页的头部,有一个 fil_page_lsn 参数记录着当前页最终的 LSN 的值,将数据页中的 LSN 值和 Redo Log 中的 LSN 值进行比较,如果数据页中 LSN 的值小于 Redo Log 中的 LSN 的值,则表示丢失了一部分数据,可以通过 Redo Log 的记录来恢复数据,否则不需要恢复数据。
2. 通过命令查看 LSN 值信息
show engine innodb status \G
Log sequence number 55313029
Log buffer assigned up to 55313029
Log buffer completed up to 55313029
Log written up to 55313029
Log flushed up to 55313029
Added dirty pages up to 55313029
Pages flushed up to 55313029
Last checkpoint at 55313029
参数说明:
- Log sequence number (LSN) :日志序列号,指的是Redo Log中的一个位置标识符,用于标记已写入日志的位置;这个数字表示了当前的Redo Log中的最后一个日志条目的位置;
- Log buffer assigned up to:日志缓冲区分配到的位置;这表示Redo Log缓冲区中已分配但尚未写入磁盘的位置;
- Log buffer completed up to:日志缓冲区完成到的位置;这表示Redo Log缓冲区中已经完成写入到磁盘的位置;
- Log written up to:已写入到的位置;这表示Redo Log中已经写入到磁盘的位置;
- Log flushed up to:已刷新到的位置;这表示Redo Log中已经刷新到磁盘的位置;
- Added dirty pages up to:添加的脏页数;这表示已将脏页添加到缓冲池中的位置;
- Pages flushed up to:已刷新的页数;这表示已经刷新到磁盘的页数;
- Last checkpoint at:最后一次检查点的位置;这表示在此位置之前的日志已被提交,并且数据库引擎已经将内存中的数据和日志同步到磁盘。
3. Redo Log 相关参数
show variables like '%innodb_log%';
- innodb_log_buffer_size:表示 Log Buffer 的大小,默认为 8MB;
- innodb_log_file_size:表示事务日志的大小,默认为 5MB;
- innodb_log_files_group=2:表示事务日志组中的事务日志文件个数,默认为 2 个;
- innodb_log_group_home_dir=./ :表示事务日志组所在的目录,当前目录表示 MySQL 数据所在的目录。