MySQL(十九)MySQL事务日志(一)RedoLog

发布时间 2023-05-09 13:58:57作者: Tod4

MySQL(十九)MySQL事务日志(一)RedoLog


​ 事务的四种特性:原子性一致性持久性和隔离性都是基于什么机制实现的?

  • 事务的隔离性由锁机制实现

  • 而事务的原子性一致性持久性则由事务的 redoundo 日志来实现的

    • redo log重做日志,提供再写入操作,恢复提交事务修改的页的操作,保证了事务的持久性

      一般情况下,事务中的操作都是先对内存中的数据页进行的操作,只有内存的数据刷回磁盘中才能叫做持久化,这时候redo log就是为了恢复因为宕机导致内存中修改但是没能刷回磁盘的情况

    • undo log回滚日志,回滚行记录到某个特定的版本,用来保证事务的原子性一致性

​ undo不是redo的逆过程,两者都可以被视作恢复操作redo log是恢复磁盘记录保证数据持久性,undo log则是将操作恢复成之前的状态),但是:

  • redo log存储引擎层生成的日志,记录的是物理级别上的页修改操作,比如页号xxx,偏移量yyy写入了zzz数据。主要是为了保证数据的可靠性。
  • undo log也是存储引擎层生成的日志,但是记录的是逻辑操作的日志,比如对某一行数据进行了insert操作,则undo log就是与之相反的delete语句,即每个操作的逆操作。主要用于事务的回滚一致性非锁定读(undo log 回滚到某种特定的版本--MVCC多版本并发控制

1 Redo Log概述

​ InnoDB存储引擎是以为单位来管理存储空间的。在真正访问页面之前,需要把磁盘上的页缓存到内存中的buffer Pool中才可以访问。之后所有的变更(指事务commit后)必须先更新缓冲池中的数据,然后缓冲池中的脏页会以一定的频率刷入磁盘(Check Point机制,这么做的目的是优化CPU和磁盘之间IO速度的鸿沟,保证整体的性能不会下降太多。

1.1 为什么需要redo日志
  • 一方面,缓冲池可以帮助优化CPU和磁盘之间IO速度的鸿沟,Check Point机制可以保证所修改的页面都能刷回磁盘,然而由于Check Point机制不是每次变更都会触发,而是master线程隔一段时间去处理。所以最坏的情况就是,事务刚提交完写完缓冲池就宕机了,Check Point机制来不及刷盘操作。

  • 另一方面,事务要求应该有持久性特性,即一个更新提交完成的事务,即使数据库发生崩溃,这个事务对数据库的更改也不能消失

    那么,能通过更新一次就刷一次盘的方式来保证一致性吗?显然是不能的:这种简单粗暴的方式存在两个问题:

    修改量和磁盘刷新量不成正比,由于存储引擎是以页为单位来管理存储空间的,因此哪怕每次只是修改了一个页的一条记录的一个字段,也会将整个数据页刷回磁盘

    随机IO速度慢:一个事务会包含很多SQL语句,因此会牵扯到修改很多页面,这些修改的页面可能并不相邻,这也就意味着将某个事务的buffer pool中的页面刷回磁盘的时候,需要进行很多的随机IO

  • 因此redo日志的思路是:并不需要把所有修改的数据页全部刷回磁盘,只需要记录修改的地方即可。InnoDB引擎采用了WAL(Write Ahead Logging)技术,即先写日志再写磁盘,只有写入redo log成功,才算事务提交成功,当发生数据库宕机且未刷新回磁盘的时候,就可以通过redo log恢复。

image-20230504201039092
1.2 redo log的优点
  • 降低了刷盘频率
  • 占用空间小(只需要存储表空间id、页号、偏移量以及需要更新的值)
1.3 redo log的特点
  • redo log是顺序写入磁盘的:在执行事务的过程中,每执行一条语句,就可能产生若干条redo日志,这些日志是按照产生的顺序写入磁盘的,也就是顺序IO效率要比随机IO快很多(这里的IO不是指的刷盘IO!!!而是写入内存的缓冲池和写入磁盘redo log的对比
  • 事务执行过程中redo log不断记录:redo log是存储引擎层产生的,bin log是数据库层产生的,因此redo log 会在事务的操作执行期间不断写入日志,bin log则是事务执行完成之后一次性写入(用于主从复制)
1.4 redo log的组成

​ redo log 可以简单分为一下两个部分:

  • 重做日志的缓存(redo log buffer):保存在内存中,是易失的。在mysql启动的时候,就向操作系统申请了一大片称为redo log buffer的连续内存空间,这段内存空间被划分为了连续的redo log block。一个redo log block512字节的大小。

    image-20230504211620900

    参数设置:innodb_log_buffer_size,默认为15M

    mysql> select @@innodb_log_buffer_size
        -> ;
    +--------------------------+
    | @@innodb_log_buffer_size |
    +--------------------------+
    |                 16777216 |
    +--------------------------+
    1 row in set (0.00 sec)
    
    
  • 重做日志文件(redo log file):保存在硬盘中,是持久的。如下面的ib_logfile0ib_logfile1,这两个文件大小是相等的,是因为这个空间是先开辟出来的,防止无线扩大。

    image-20230504212320008

2 ? redo log写入的整体流程

image-20230504204741346
  1. 将数据页从磁盘拷贝到内存中,修改缓冲区中的数据的内存拷贝
  2. 生成一条重做日志,写入重做日志缓存(redo log buffer)中,记录的是数据被修改之后的值
  3. 当事务commit之后,将重做日志缓存(redo log buffer)中的内容以追加的方式刷新到redo log file
  4. 定期将内存修改的数据刷新到磁盘中

Write Ahead Log(预先日志持久化):在持久化数据页之前,先对日志进行持久化操作

2.1 redo log的刷盘策略

redo log的刷盘策略关注的是从redo log buffer写入磁盘的redo log file的过程(而不是上面的4实际写入磁盘的过程)redo log的写入并不是直接写入磁盘的,而是InnoDB存储引擎在写redo log的时候先写入redo log buffer,之后会以一定的频率写入到真正的log filer中。这里的一定频率就是要说的刷盘策略:

image-20230504205019122

redo log buffer在写入redo log file的过程也不是真正直接刷到磁盘中去的,而是先刷入文件系统缓存(Page Cache)(操作系统为了提高文件写入效率的优化),然后由操作系统来决定什么时候写入redo log file。这么做存在的问题就是,如果redo log buffer交给系统来同步,系统一旦宕机数据也就丢失了,虽然系统宕机的概率很小。

​ 针对这种情况,InnoDB给出innodb_flush_log_at_trx_commit的参数,该参数控制commit提交事务的时候,如何将redo log buffer中的日志数据刷新到redo log file

  • 0:每次事务提交的时候,不进行刷盘操作(系统默认的master线程会每隔1s进行一次重做日志的同步操作)
  • 1:每次提交事务都进行同步,刷盘操作(默认值
  • 2:每次提交事务都只把redo log buffer的日志记录放入page cache而不进行刷盘操作,由os来决定什么时候进行。
mysql> select @@innodb_flush_log_at_trx_commit;
+----------------------------------+
| @@innodb_flush_log_at_trx_commit |
+----------------------------------+
|                                1 |
+----------------------------------+
1 row in set (0.00 sec)

上面的同步指的是后台线程调用操作系统fsync,将内存中的文件缓存刷新到磁盘中进行同步

​ InnoDB存储引擎有一个后台master线程,每隔一秒就会对redo log buffer进行刷盘同步操作。因此,一个没提交的事务,也有可能被刷盘,因为事务在执行过程中redo log记录会写入redo log buffer,后台线程会每隔1s对其执行刷盘、同步操作。

image-20230505095443803

​ 所以redo真正的刷盘策略是下面的这样:

image-20230505095954033
流程图

默认情况,Inndb_flush_log_at_trx_commit为1的情况

image-20230505100239292

只要数据提交成功,redo log的记录就一定在磁盘中,不会有任何数据的丢失;如果没有提交宕机了,则重做日志缓存由于在内存也就消失了,因此不会有任何的问题。

这样可以保证ACID的D,数据绝对不会丢失,但是效率也是最差的。

Inndb_flush_log_at_trx_commit为2的情况

image-20230505101127651

Inndb_flush_log_at_trx_commit为2是每次提交事务都写入到page cache里面,如果mysql提交完成事务后宕机,则不会造成数据的丢失,因为日志记录已经写入到了文件系统缓存里面,这部分内容除非os宕机否则不会消失。

OS宕机的情况无法满足持久性,但是这种效率也是最高的。

Inndb_flush_log_at_trx_commit为0的情况

image-20230505102415569

Inndb_flush_log_at_trx_commit为0是完全由后台线程每隔一秒来负责刷盘,如果mysql宕机则会丢失这1s的数据

数值为0的刷盘策略理论上效率高于1但是小于2,这种策略存在风险,无法保证D

image-20230505103613850
2.3 写入redo log buffer过程
mini-transaction

​ MySQL将对底层页面的一次原子访问过程称之为一个mini-transaction,简称为mtr,比如:向某个索引对应的B+树中添加记录的过程就是一个mtr,一个mtr可以包含一组redo log日志记录(回想一下一个redo log日志记录的是向某个表的多少偏移量添加修改什么数据),在进行崩溃修复的时候这一组redo log日志记录就作为一个不可分割的整体。

image-20230505105415568
redo日志写入log buffer

​ 向log buffer中写入redo日志的过程是顺序的,也就是先往前面的block中写,当该block中的空间用完之后就往后面的block中写,因此block设置了一个两边buf_free指示当前block用到了哪里,也就是写入的时候应该从哪里开始。

image-20230505105638892

​ 每一个redo log block的大小是512kb,包括log block headerlog block bodylog block trailer。上面说到过,一个mtr可以产生若干条redo日志记录,由于mtr具有原子性,所以产生的几条redo日志记录也是一个不可分割的组,所以并不是每产生一条redo日志记录,就插入到redo buffer中。而是先暂存到一个地方,等到事务执行结束的时候,再将产生的同一组的redo日志记录全部复制到log buffer中

​ 假设有两个名为T1、T2的事务,每个事务包含两个mtr,命名为:

  • 事务T1的两个mtr:mtr_T1_1mtr_T1_2
  • 事务T2的两个mtr:mtr_T2_1mtr_T2_2
image-20230505110627700

​ 不同的事务可能是并发执行的,因此不同事务的mtr也会交替地写入log buffer中的(mtr内部的redo log record必须是相邻的):

image-20230505110903497
2.4 redo log block的结构图

​ 一个redo log block是由日志头日志尾日志体组成的。日志头占用12字节,日志尾占用8字节,所以一个blobk真正能够存储的数据就是512-12-8=492字节

为什么一个block要设计成512字节?

​ 因为机械磁盘的扇区就是512字节,如果写入两个扇区,就会导致盘片的转动,比如一个写入成功一个不成功,就不能保证原子性的写入。

2.5 redo log file
相关参数
  • innodb_log_group_home_dir

    mysql> show variables like 'innodb_log_group_home_dir';
    +---------------------------+-------+
    | Variable_name             | Value |
    +---------------------------+-------+
    | innodb_log_group_home_dir | ./    |
    +---------------------------+-------+
    1 row in set (0.01 sec)
    
  • innodb_files_in_group

    mysql> show variables like 'innodb_files_in_group';
    Empty set (0.00 sec)
    
  • innodb_log_file_size:单个redo log文件设置大小,最大为512G,是单个文件大小*文件个数

    mysql> show variables like 'innodb_log_file_size';
    +----------------------+----------+
    | Variable_name        | Value    |
    +----------------------+----------+
    | innodb_log_file_size | 50331648 |
    +----------------------+----------+
    1 row in set (0.00 sec)
    
    

    一般需要根据业务修改其大小,以便能够容纳更大的事务,编辑my.cnf文件并重启数据库

2.6 日志文件组
  • 磁盘中的redo log不只有一个,而是以一个日志文件组的形式出现的,这些文件是以ib_logfile[数字]的形式命名,每个redo日志的大小是相同的

  • 将redo日志记录写入到日志文件组的时候,首先从ib_logfile0开始写,然后写满后依次接着ib_logfile1...直到写满最后一个文件,就重新从ib_logfile0

image-20230505150237127
2.7 checkpoint机制

​ 为了防止日志文件组循环写入导致文件覆盖,引入了check poin机制:

  • 记录write poscheckpoint两个变量:
    • write pos为当前记录的位置,一边写一边后移
    • checkpoint是刷盘完要擦除的位置,也是需要往后推移
image-20230505151454176

​ 如上图:

  • checkpoint往后到write pos的1、2文件表示还没有进行刷盘的redo log记录
  • 0、3文件表示已经刷盘可以写入的文件
  • 如果write pos追上了checkpoint,则表明日志文件组已满