MySQL-- InnoDB存储引擎-重做日志( redo log )(2-3)

318 阅读8分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第27天,点击查看活动详情

4. 重做日志 ( redo log )

  • WAL(Write-Ahead Logging)机制

    WAL 的全称是 Write-Ahead Logging,中文称预写式日志(日志先行),是一种数据安全写入机制。就是先写日志,然后再写入磁盘,这样既能提高性能又可以保证数据的安全性。Mysql中的redo log就是采用WAL机制。

    为什么使用WAL ?

    磁盘的写操作是随机IO,比较耗性能,所以如果把每一次的更新操作都先写入log中,那么就成了顺序写操作,实际更新操作由后台线程再根据log异步写入。这样对于client端,延迟就降低了。并且,由于顺序写入大概率是在一个磁盘块内,这样产生的io次数也大大降低。所以WAL的核心在于将随机写转变为了顺序写,降低了客户端的延迟,提升了吞吐量.

  • redo log 基本概念

    InnoDB引擎对数据的更新,是先将更新记录写入redo log日志,然后会在系统空闲的时候或者是按照设定的更新策略再将日志中的内容更新到磁盘之中。这就是所谓的预写式技术(Write Ahead logging)。这种技术可以大大减少IO操作的频率,提升数据刷新的效率。

    redo log:被称作重做日志, 包括两部分:一个是内存中的日志缓冲: redo log buffer,另一个是磁盘上的日志文件: redo log file

    mysql 每执行一条 DML 语句,先将记录写入 redo log buffer ( redo日志记录的是事务对数据库做了哪些修改 ) 。后续某个时间点再一次性将多个操作记录写到 redo log file 。当故障发生致使内存数据丢失后,InnoDB会在重启时,经过重放 redo,将Page恢复到崩溃之前的状态 通过Redo log可以实现事务的持久性 。

  • Redo log数据落盘流程

    将内存中的数据页持久化到磁盘,需要下面的两个流程来完成 :

1、脏页落盘机制

image.png

脏页是指修改了Buffer Pool中的数据页后,导致了内存中的数据页和磁盘中的数据页不一致,这时就出现了脏页.

当进行数据页的修改操作时: 首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上。页从缓冲池刷新回磁盘的操作并不是在每次页发生更新时触发,而是通过一种称为CheckPoint的机制刷新回磁盘。

checkpoint机制

思考一下这个场景:如果重做日志可以无限地增大,同时缓冲池也足够大,那么是不需要将缓冲池中页的新版本刷新回磁盘。因为当发生宕机时,完全可以通过重做日志来恢复整个数据库系统中的数据到宕机发生的时刻。

Checkpoint(检查点)技术主要解决以下几个问题:

  1. 缩短数据库的恢复时间
  2. 缓冲池不够用时,将脏页刷新到磁盘
  3. 重做日志不可用时,刷新脏页。

脏页落盘的时机 采用CheckPoint检查点机制 以下机制都可通过参数控制

sharp checkpoint:强制落盘。把内存中所有的脏页都执行落盘操作。只有当关闭数据库之前才会执行。
fuzzy checkpoint:模糊落盘。把一部分脏页执行落盘操作
	1.Master Thrad Checkpoint 主线程定时将脏页写入磁盘 每秒或每10s执行一次脏页。
	2.FLUSH_LRU_LIST buffer pool有脏页换出,执行落盘
	3.Async/Sync Flush checkpoint 当redo log快写满的时候执行落盘
		a.当redo log超过75%小于90%会执行异步落盘
		b.当redo log超过90%,会执行同步落盘操作。会阻塞写操作。
	4.Dirty Page too much checkpoint 如果buffer pool中脏页太多,脏页率超过75%执行落盘

2、 redo log 持久化

缓冲区数据一般情况下是无法直接写入磁盘的,中间必须经过操作系统缓冲区( OS Buffer )。因此, redo log buffer 写入 redo logfile 实际上是先写入 OS Cache,然后再通过系统调用 fsync() 将其刷到 redo log file.

Redo Buffer 持久化到 redo log 的策略,可通过Innodb_flush_log_at_trx_commit 设置:

参数值含义
0 (延迟写)事务提交时不会将 redo log buffer中日志写入到 os buffer, 而是每秒写入 os cache并调用 fsync()写入到 redo log file中。 也就是说设置为0时是(大约)每秒刷新写入到磁盘中的,当系统崩溃,会丢失1秒钟的数据。
1 (实时写,实时刷)事务每次提交都会将 redo log buffer中的日志写入 os cache并 调用 fsync()刷到 redo log file中。这种方式即使系统崩溃也不会丢失任何数据,但是因为每次提交都写入磁盘,IO的性能较差。
2 (实时写, 延时刷)每次提交都仅写入到 os buffer,然后是每秒调用 fsync()os cache中的日志写入到 redo log file

一般建议选择取值2,因为 MySQL 挂了数据没有损失,整个服务器挂了才会损失1秒的事务提交数据

image.png

redo log日志格式

物理日志VS逻辑日志

  • 物理日志: 记录的是每一个page页中具体存储的值是多少,在这个数据页上做了什么修改. 比如: 某个事物将系统表空间中的第100个页面中偏移量为1000处的那个字节的值1改为2.
  • 逻辑日志: 记录的是每一个page页面中具体数据是怎么变动的,它会记录一个变动的过程或SQL语句的逻辑, 比如: 把一个page页中的一个数据从1改为2,再从2改为3,逻辑日志就会记录1->2,2->3这个数据变化的过程.

redo日志属于物理日志, 只是记录一下事务对数据库做了哪些修改。

image.png

  1. type: 该条日志的类型
  2. space ID : 表空间ID
  3. page number : 页号
  4. data : 该条redo日志的具体内容

redo log日志类型

redo log根据在页面中写入数据的多少,将redo日志划分为几种不同的类型(MySQL5.7中有53种类型)。

  • MLOG_1BYTE (type=1) : 表示在页面的某个偏移量处写入1字节的redo日志类型。
  • MLOG_2BYTE (type=2) : 表示在页面的某个偏移量处写入2字节的redo日志类型。
  • MLOG_4BYTE (type=4) : 表示在页面的某个偏移量处写入 4字节 的redo日志类型。
  • MLOG_8BYTE (type=8) : 表示在页面的某个偏移量处写入8字节的redo日志类型。

image.png

  • MLOG_WRITE_STRING(type=30) : 表示在页面的某个偏移量处写入一串数据,但是因为不能确定写入的具体数据占用多少字节,所以需要在日志结构中添加一个len字段。。

image.png

  • redo log 写入机制

在事务执行期间MySQL宕机了,redo log 缓冲区中的内容丢失了,也不会有损失,因为事务并没有提交(事务提交,必然写入日志完成).

redo log 三种状态

image.png

  1. 存在于redo log buffer 内存区域中
  2. 向磁盘写入,但是没有真正写入磁盘,而是保存在文件系统缓存中
  3. 持久化到磁盘

如果事务没有提交的时候,redo log buffer中的部分日志有可能被持久化到磁盘吗 ?

触发真正的fsync写盘的场景

  1. redo log buffer 占用的空间即将达到 innodb_log_buffer_size 一半的时候,后台线程会主动写盘。
  2. 并行的事务提交的时候,顺带将某个未提交的事务的redo log buffer 持久化到磁盘。因为redo log buffer 是共享的,因此一些正在执行中的事务的redo log信息也有可能被持久化到磁盘中。

组提交

MySQL 为了优化磁盘持久化的开销,会有一个 组提交(group commit)的机制

每个InnDB存储引擎至少有1个重做日志文件组(group),每个文件组下至少有两个重做日志文件,默认的为ib_logfile0ib_logfile1

  1. 事务日志组路径,当前目录表示MyQSL数据目录为日志组目录.
mysql> show variables like 'innodb_log_group_home_dir';
+---------------------------+-------+
| Variable_name             | Value |
+---------------------------+-------+
| innodb_log_group_home_dir | ./    |
+---------------------------+-------+
1 row in set (0.00 sec)
​
[root@localhost mysql]# ll ib_log*
-rw-r----- 1 mysql mysql 50331648 1月  29 03:39 ib_logfile0
-rw-r----- 1 mysql mysql 50331648 7月  11 2020 ib_logfile1
  1. 事务日志组中的事务日志文件个数,默认是2个.
mysql> show variables like 'innodb_log_files_in_group';
+---------------------------+-------+
| Variable_name             | Value |
+---------------------------+-------+
| innodb_log_files_in_group | 2     |
+---------------------------+-------+
1 row in set (0.01 sec)
  1. 日志组中每个重做日志的大小一致,并循环使用;
mysql> show variables like 'innodb_log_file_size';
+----------------------+----------+
| Variable_name        | Value    |
+----------------------+----------+
| innodb_log_file_size | 50331648 |
+----------------------+----------+
1 row in set (0.00 sec)

InnoDB 以环型方式(circular fashion)写入数据到重做日志文件,当文件满了的时候,会自动切换到日志文件2,当重做日志文件2也写满时,会再切换到重做日志文件1;

image.png

write pos: 表示日志当前记录的位置,当ib_logfile_4写满后,会从ib_logfile_1从头开始记录;

check point: 表示将日志记录的修改写进磁盘,完成数据落盘,数据落盘后checkpoint会将日志上的相关记录擦除掉,即 write pos -> checkpoint 之间的部分是redo log空着的部分,用于记录新的记录,checkpoint -> write pos 之间是redo log 待落盘的数据修改记录

如果 write pos 追上 checkpoint,表示写满,这时候不能再执行新的更新,得停下来先擦掉一些记录,把 checkpoint 推进一下。