MySQL的关键日志

发布于:2025-07-19 ⋅ 阅读:(18) ⋅ 点赞:(0)

1. 背景

在这里插入图片描述

2. redo log

redo log是InnoDB存储引擎独有的,使MySQL拥有崩溃恢复的能力。
在这里插入图片描述
MySQL中数据是以页为单位存储的,当你查询一条记录时,硬盘会把一整页的数据加载出来,加载出来的数据叫做数据页,存储在Buffer Pool中。后续的查询都是先从Buffer Pool中找,没找到再去硬盘加载其他页面直至命中,减少IO次数。更新数据的时候,也优先去Buffer Pool中找,如果存在需要更新的数据就直接更新。然后会把“在某个数据页做了什么修改”记录到redo log buffer里,在刷盘的时候会写入redo log中。
如下图所示:
在这里插入图片描述
注意:所以每条redo记录应该由“表空间号+数据页号+修改数据长度+具体修改的数据”组成。

2.1 刷盘时机

理想情况,事务一提交就会进行刷盘操作,但实际上的刷盘操作是根据策略来决定的。

InnoDB为redo log提供 innodb_flush_log_at_trx_commit 参数,来支持:

  • 0: 每次提交事务时不刷盘
  • 1: 每次提交事务时刷盘
  • 2: 每次提交事务时都只把redo log buffer写入page cache

默认参数为1,当事务提交的时候会调用fsync对redo log buffer 进行刷盘。

mysql> SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';
+--------------------------------+-------+
| Variable_name                  | Value |
+--------------------------------+-------+
| innodb_flush_log_at_trx_commit | 1     |
+--------------------------------+-------+
1 row in set (0.01 sec)

另外,Innodb存储引擎有一个后台线程,每隔1s,就会把redo log buffer中的内容写到文件系统缓存page cache,然后调用fsync刷盘。
在这里插入图片描述
所以一个事务如果没有commit,每隔1s就刷盘,不需要等待commit。

下面是各种刷盘策略的流程图:
在这里插入图片描述
如果挂了的话,有可能造成1s内的数据丢失。
在这里插入图片描述

在这里插入图片描述
如上图:当事务提交成功时,redo log buffer会被写入page cache,然后后台线程会刷盘写入redo log,由于后台线程1s执行1次,所以宕机后会造成1s内的redolog 数据丢失。

2.2日志文件组

redolog是为了防止脏页丢失而设计的,因此当Buffer Pool中的脏页丢失而设计的,如果随着系统运行,redo log中的数据就没用了,就需要擦除redo log数据。
硬盘上存储的redo log 日志文件不止一个,而是一个日志文件组,每个redo log 文件大小都是一样的。它采用的是环形数组形式,从头开始写,写到末尾循环写,如下图所示:
在这里插入图片描述
日志文件组有两个重要的属性:

  • write pos:是当前记录的位置,一边写一边后移。
  • checkpoint:是当前要擦除的位置,也是后台推移。

每次刷盘redo log 记录到日志文件组中,write pos就会后移更新。
每次MySQL加载日志文件恢复数据时,会清空加载过的redo log,并把checkpoint向后更新。
当write pos 追上checkpoint时,表示日志文件组满了,这时候不能再写入新的redo log记录,MYSQL得停下来,清空一些记录,把checkpoint向后推一下。

2.3 redo log小结

2.4 只要每次把修改后的数据页直接刷盘不就好了,为什么需要redo log 刷盘呢?

数据页大小是16KB刷盘比较耗时,有时候我们就修改了几bit的数据,没有必要把整页的数据刷盘。而数据页有可能分布在硬盘的各个位置,是随机写,性能差。
redolog一行就几十bit,只要包含了表空间号、数据页号、磁盘文件偏移量、修改值、再加上是顺序写,刷盘效率高。
所以用 redo log 形式记录修改内容,性能会远远超过刷数据页的方式,这也让数据库的并发能力更强。

3. binlog

redo log是物理日志,记录的是”在某个数据页做了什么修改“,属于Innodb存储引擎。
而binlog是逻辑日志,记录内容是语句的原始逻辑,属于MySQL Server层。所有的存储引擎只要发生了数据更新,都会产生binlog日志。

你听说过删库跑路吧,为了防止数据库表被删除带来的影响, server 层会将历史上所有变更操作记录到磁盘上的日志文件中,这个日志文件就是所谓的 binlog。一旦误删表,就可以利用 binlog 来恢复数据。

那么问题就来了,innodb 有一个 redo log 也做类似的事情,为什么还要多此一举?

这是因为 redo log 是环状写入的,后面写的内容最终会覆盖前面的内容,也就是不会记录所有历史写操作,而 binlog 却会记录所有历史变更。并且 binlog 位于 server 层,这样不管底层的存储引擎是什么,都能复用这部分能力。

3.1 binlog作用

MySQL数据库的数据备份、主备、主主、主从都离不开binlog,需要依赖binlog来同步数据,保证数据一致性。
在这里插入图片描述
binlog会记录所有涉及更新数据的逻辑规则,并按顺序写。

3.2 binlog记录格式

binlog有三种格式,通过binlog_format参数设置:

  • statement
  • row
  • mixed

设置statement记录的内容是SQL语句原文,比如执行一条
update T set update_time = now() where id = 1,记录内容如下:
在这里插入图片描述
同步数据时,会执行记录的SQL语句,但是有个问题update_time=now()会获取到当前系统的时间,直接执行会导致与原数据库不一致。

为了解决这个问题,将binlog_format设置成row,记录的不再是简单的SQL语句,还包含操作的具体数据,如下:
在这里插入图片描述
解释:row格式记录的内容看不到详细信息,通过mysqlbinlog工具解析出来。

row带来的好处就是同步数据的一致性,通常情况都设置成row,这样可以为数据库的同步与恢复带来更好的可靠性。但是这种格式比较占用空间,且恢复与同步时更消耗IO资源。

所以就设置成了折中的方案,设置成mixed。会引起问题的用row,不会引起问题的用statment。

3.3 写入机制

binlog的写入时机为事务执行过程中,先把日志写到binlog cache,事务提交的时候再把binlog cache 写到binlog文件中(实际上会先写入page cache,再由fsync写入binlog文件)。

因为一个事务的binlog不能被拆开,无论这个事务多大,都要确保一次性写入,所以系统会给每个线程分配一块内存作为binlog cache。可以通过binlog_cache_size参数控制单线程binlog_cache大小,如果存储内容超过了这个参数,就要暂存到磁盘。

binlog日志刷盘流程如下:
在这里插入图片描述
write和fsync的时机可以由参数sync_binlog控制,可以配置成0、1、N(N>1)。

  • 设置成0时:表示每次提交事务都只会write,由系统自行判断什么时候执行fsync。
  • 设置成1时:表示每次提交事务都会执行fync,就喝redo log 刷盘一样。
  • 设置成N时:表示每次提交事务都会write,积累N个事务后才fsync。

在这里插入图片描述
在这里插入图片描述

4. 两阶段提交

redo log 让InnoDB存储引擎拥有崩溃恢复能力。
binlog 保证了MySQL集群架构的数据一致性。
虽然他们都有持久化的保证,但是侧重点不同。

在执行更新语句过程,会记录redo log 与 binlog两块日志,以基本的事物单位,redo log 在事务执行过程中可以不断写入,而binlog只有在事务提交的时候才写入,所以redo log 与binlog的写入时机不一致。
在这里插入图片描述
如果redo log 与binlog两份日志之间的逻辑不一致,会有什么问题呢?
假想下面的场景:
在执行过程中写完redo log日志后,binlog日志写期间发生了异常,会出现什么情况呢?
在这里插入图片描述
由于binlog没写完就异常,这时候binlog里面没有对应的修改记录。因此,之后用binlog日志恢复数据时,就少一次更新,恢复出来的这一行c=0, 而原库因为redo log日志恢复,这一行c值是1,最终数据不一致。
在这里插入图片描述
为了解决数据两份日志之间的逻辑一致问题,InnoDB存储引擎使用两阶段提交方案。将redo log查分成两个步骤 prepare和commit阶段,这就是两阶段提交。
在这里插入图片描述
使用两阶段提交后,写入binlog时发生的异常也不会有影响,因为MySQL根据redo log 日志恢复数据时,发现redo log 还处于prepare阶段,并且没有对应binlog日志,就会回滚该事务。
在这里插入图片描述
再看一个场景,redo log设置commit阶段发生异常,如下图:
在这里插入图片描述
并不会回滚事务,它会执行上图框住的逻辑,虽然reldo log 处于prepare阶段,但是能通过事务id找到对应的binlog日志,所以MySQL认为是完整的,就会提交事务恢复。

5. undo log

undo log 日志保证了事务的ACID特性中的原子性。
在这里插入图片描述

每当InnoDB引擎对一条记录进行操作(修改、删除、新增)时,要把回滚时需要的信息都记录到undo log里,比如:

  • 在插入一条记录时,要把这条记录的主键值记下来,这样之后回滚时只需要把这个主键值对应的记录删除掉就好了。
  • 在删除一条记录时,要把这条记录中所有内容都记下来,回滚时再插入。
  • 更新时,需要记下更新前的旧值,回滚时再更新回去。

5.1 如何回滚

实际上mysql的表结构还有三个隐藏字段是我们不知道的。
创建该记录的事务ID,和回滚指针。
在这里插入图片描述
目前并不知道创建该记录的事务ID,就默认设置成null,隐式主键,1。第一条记录也没有其他版本,我们设置回滚指针为null。

因此每次修改该记录时,MySQL自动会生成旧记录缓存到undo log中,产生新记录并指向旧版本。这样就可以形成一个个版本链,来保证记录能够正常回退到更改之前的版本。

5.2 MVCC

undo log 还可以和ReadView + undo log实现多版本并发控制。
事务的隔离级别一共有四种,读未提交、读提交、可重复度和串行化
其中对于读提交和可重复读的隔离级别的事务来说,快照读的区别主要在于Read View的时机不同:

  • 读提交隔离级别在每个select都会生成一个新的Read View,意味着,事务期间多次读取同一条数据,前后两次读的数据可能会出现不一致。
  • 可重复度隔离级别是事务启动时生层一个Read View,然后整个事务期间都在用这个Read View,这样就保证了事务期间读取到的数据都是事务启动前的数据。
class Readview{
//...
private:
	//高水位:大于等于这个ID的事务,均不可见
	trx_id_t m_low_limit_id;
	//低水位:小于这个ID的事务均可见
	trx_id_t m_up_limit_id;
	//创建该Read View的事务id
	trx_id_t m_creator_trx_id;
	//创建视图时,活跃的事务id列表
	ids_t m_ids;
	//标记视图是否被关闭
	bool m_closed;
	
	//...省略
};

m_ids 一张表,用来维护Read View生成时刻,系统正在活跃的事务ID。
up_limit_id 记录m_ids列表中最小的事务ID
low_limit_id ReadView生成时刻系统尚未分配的下一个事务ID,也就是目前已出现过的事务ID的最大值+1
creator_trx_id 创建该ReadView的ID


实际在读取版本链的时候,能读取每个版本对应的事务ID,即:当前记录的DB_TRX_ID。所以,当前快照读的ReadView和某一个记录的DB_TRX_ID是如何决定当前快照读,应不应该读到当前版本的记录?

在这里插入图片描述
如果查到不该看到当前版本,就遍历下一个版本,直到符合条件,即可以看到。ReadView会在select的时候,自动生成。

6.binlog和redo log区别

  1. 适用对象不同
    binlog是MySQL的Server层实现的日志,所有存储引擎都可以使用;
    redo log 是Innodb存储引擎实现的日志。
  2. 文件格式不同
    binlog有三种格式,STATEMENT、ROW、MIXED
    redo log 是物理日志,记录的是在某个数据页做了什么修改,比如对XXX表空间中的YYY数据页ZZZ偏移量的地方做了AAA更新。
  3. 写入方式不同
    binlog是追加写,写满一个,再写一个,是全量日志。
    redo log 是循环写,日志空间大小是固定,全部写满就从头开始,保存未被刷入磁盘的脏页日志。
  4. 用途不同:
    binlog用于备份恢复、主从复制
    redo log 用于掉电故障。

参考链接:小林coding


网站公告

今日签到

点亮在社区的每一天
去签到