> 文档中心 > MySQL事务日志之redo日志

MySQL事务日志之redo日志

事务有4种特性:原子性、一致性、隔离性和持久性。那么事务的四种特性到底是基于什么机制实现呢?

  • 事务的隔离性由 锁机制 实现。
  • 事务的原子性,一致性,持久性由事务的redo日志undo日志来保证
    redo log称为重做日志,提供再写入操作,恢复提交事务修改的页操作,用来保证事务的持久性
    undo log称为回滚日志,回滚行记录到某个特定版本,用来保证事务的原子性,一致性
    在这里插入图片描述

redo日志

redo的组成

重做日志缓存保存在内存中,是易失的,连续的缓存空间,由若干个block组成
重做日志文件保存在硬盘中,是持久的,由若干个日志文件组成

redo流程

在这里插入图片描述

第1步:先将原始数据从磁盘中读入内存中来,修改数据的内存拷贝
第2步:生成一条重做日志并写入redo log buffer,记录的是数据被修改后的值
第3步:当事务commit时,将redo log buffer中的内容刷新到 redo log file,对 redo log file采用追加 写的方式
第4步:定期将内存中修改的数据刷新到磁盘中
Write-Ahead Log(预先日志持久化):在持久化一个数据页之前,先将内存中相应的日志页持久化。

redo log的刷盘策略

redo日志的写入并不是直接写入磁盘中的,而是在没有提交的时候就先写入redo log buffer,之后再以一定的频率写入到真正的redo log file中。这里我们讲述的就是mysql以什么样的频率进行刷盘
在这里插入图片描述
注意,redo log buffer刷盘到redo log file的过程并不是真正的刷到磁盘中去,只是刷入到 文件系统缓存 (page cache)中去(这是现代操作系统为了提高文件写入效率做的一个优化),真正的写入会交给系统自己来决定(比如page cache足够大了)。那么对于InnoDB来说就存在一个问题,如果交给系统来同步,同样如果系统宕机,那么数据也丢失了(虽然整个系统宕机的概率还是比较小的)。

针对这种情况,InnoDB给出 innodb_flush_log_at_trx_commit 参数,该参数控制 commit提交事务时,如何将 redo log buffer 中的日志刷新到 redo log file 中。它支持三种策略:

  • 设置为0,表示每次事务提交时不进行刷盘操作。(系统默认master thread每隔1s进行一次重做日志的同步)
    在这里插入图片描述

  • 设置为1 :表示每次事务提交时都将进行同步,刷盘操作( 默认值 )
    在这里插入图片描述

  • 设置为2 :表示每次事务提交时都只把 redo log buffer 内容写入 page cache,不进行同步。由os自己决定什么时候同步到磁盘文件
    在这里插入图片描述

还有一些情况会被刷新到磁盘:

  • log buffer空间不足时,如果当前写的redo日志已经占到redo buffer的50%,就需要把这些日志刷新到磁盘
  • 正常关闭服务器时
  • 后台有一个线程,就是上面那个后台线程。大约以1秒的频率刷新到磁盘

MTR(Mini-Transanction)

以组的形式写入redo日志

一条语句在执行过程中,可能会修改若干个页面。比如insert语句可能修改系统表页号为7的页面的Max Row ID属性,还会更新聚簇索引和二级索引对应的B+树。在执行过程中产生的redo日志被设计mysql的大叔划分成了若干个不可分割的组,比如:

  • 更新Max Row ID属性产生的redo日志,是不可分割的
  • 向聚簇索引对应B+树的页面插入一条记录的redo日志是一组,是不可分割的
  • 向二级索引插入记录也是一样,不可分割的

那什么叫做不可分割呢?我们以向B+树中插入一条记录为例讲解
在插入这条记录之前,我们需要先定位到插入记录该插入哪个位置,定位到具体的数据页之后,有两种情况:

  1. 该数据页剩余空间很多,可以直接插入,这种叫做乐观插入
  2. 该数据也剩余空间不足以插入这条记录,我们需要进行页分裂,把原来的一部分记录复制到新的页中,再进行插入,并且还要更新内节点,修改一些系统页面(修改各种段,区的统计信息,修改各种链表的信息,free链表等等),这个过程需要对多个页面进行修改,这就会产生多个redo日志,这种情况称为悲观插入

mysql中向b+树中插入一条记录必须是原子的,不能插到一半停止。比如说悲观插入过程中,新页面分配好了,数据也复制过去了,但是内节点还没有进行更改目录项,这样就会形成一颗不正确的b+树。

所以在这些需要保持原子性的操作(有多条redo日志)时,必须保证以组的形式来记录redo日志,这就是不可分割的。

MTR(Mini-Transanction)

对底层页面进行一次原子操作的过程称为MTR,比如前面说的给B+树插入一条记录就是一个MTR
在这里插入图片描述

写入redo log buffer 过程

通过MTR生成的redo日志都被mysql放到了大小为512字节的页中,这里把这种页叫做块block,实际上redo日志的内容是保存在了log
block body(大小为496字节)中,block示意图如下:
在这里插入图片描述
每一个MTR产生的多个redo日志,因为是不可分割的,所以必须放在一起。每次mtr执行过程中,没生成一条redo日志,都先将暂存到一个地方,当MTR结束后,才将产生的一组redo日志写入redo buffer中,如下图:
在这里插入图片描述

redo log file

日志文件组

MySQL的数据文件下默认有ib_logfile0和ib_logfile()这两个文件,redo buffer默认就是刷新到这两个文件里的。
在这里插入图片描述
总共的redo日志文件大小其实就是: innodb_log_file_size × innodb_log_files_in_group
采用循环使用的方式向redo日志文件组里写数据的话,会导致后写入的redo日志覆盖掉前边写的redo日志?当然!所以InnoDB的设计者提出了checkpoint的概念。

checkpoint

设计mysql的人提出了一个全局变量checkpoint_lsn,用于表示当前系统可以被覆盖的redo日志总量,初始值是8704
在这里插入图片描述
如果 write pos 追上 checkpoint ,表示日志文件组满了,这时候不能再写入新的 redo log记录,MySQL 得停下来,清空一些记录,把 checkpoint 推进一下。就是做一次checkpoint操作
在这里插入图片描述
注意:checkpoint不是用来刷新脏页的,是用来更新checkpoint lsn的,也就是让redo日志文件里的一些空间可以被覆盖。刷脏页是在另外的线程里的,一般情况下是异步刷新脏页。刷脏页和做checkpoint是两件事(详见《MySQL是怎样运行的》P334)

下一章引言
我们在执行一条事务时,在事务执行过程中,如果服务器突然挂机的话。如果我们的redo日志还没有写入磁盘还好,也就是还在redo buffer中,那就相当于什么也没有做。但是如果这些redo日志已经刷新到了磁盘中的话,下次开机就会把它们恢复过来,那么这样事情只做了一半,不符合我们的原子性。这时候就需要我们的undo日志出马了,详情请看下一篇文章