MySQL三大日志

一、概述

MySQL日志主要包括错误日志、查询日志、慢查询日志、重做日志、归档日志几大类。其中比较重要的归档日志(binlog)和重做日志(redo log)和回滚日志(undo log)。

640.png

二、undo log日志

2.1 概念

顾名思义,回滚日志(undo log)的字面意思就是撤销操作的日志,指的是使MySQL中的数据回到某个状态。

MySQL数据库中,事务开始之前,MySQL会将待修改的记录保存到undo log中,如果数据库崩溃或者事务需要回滚时,MySQL可以通过利用undo log日志,将数据库中的数据回滚到之前的状态。

MySQL新增、修改和删除数据时,在事务开始前,就会将信息写入undo log中。事务提交时,并不会立刻删除undo logInnoDB存储引擎会将事务对应的undo log放入待删除列表中,之后会通过后台的purge thread对待删除的列表进行删除处理。这里,值得注意的是:undo log是一种逻辑日志,记录的是一个变化过程。

比如,MySQL执行一个delete操作,undo log就会记录一个insert操作;MySQL执行一个insert操作,undo log就会记录一个delete操作;MySQL执行一个update操作,undo log就会记录一个相反的update操作。

undo log以段的方式来管理和记录日志信息,在InnoDB存储引擎的数据文件中,包含了一种叫做rollback segment的回滚段,其内部包含了1024undo log senment

2.2 undo log作用

undo log对于MySQL实现事务来说,起着至关重要的作用,它实现了事务的原子性和多版本并发控制,也就是我们经常说的MVCC

2.2.1 实现事务的原子性

undo log能够实现MySQL事务的原子性,在事务的处理过程中,如果MySQL出现了错误或者用户手动执行了事务的回滚操作(执行了rollback操作),MySQL可以利用undo log日志将数据库中的数据恢复到之前的状态。

2.2.2 实现MVCC机制

undo logMySQLInnoDB存储引擎中实现了多版本并发控制(MVCC)机制。事务未提交前,undo log保存了未提交之前的版本数据,undo log中的数据可以作为旧版本数据的副本或者快照以便其他并发事务进行读取操作。

2021030500291130.png

事务A手动开启事务后,对goods数据表中id1的数据进行更新操作,首先会把更新命中的数据写入到undo Buffer中。在事务A未提交之前,此时,事务B手动开启事务,对goods数据表中的id1的数据进行查询操作,此时的事务B会读取undo log中的数据并返回给客户端,这就是MySQL中的MVCC机制。

可以在MySQL中通过下面的命令来查看控制undo log日志的参数。

show variables like '%innodb_undo%';

另外,MVCC的实现依赖于:隐藏字段、Read Viewundo log。在内部实现中,InnoDB通过数据行的DB_TRX_IDRead View来判断数据的可见性,如不可见,则通过数据行的DB_ROLL_PTR找到undo log中的历史版本。每个事务读到的数据版本可能是不一样的,在同一个事务中,用户只能看到该事务创建Read View之前已经提交的修改和该事务本身做的修改。

三、redo log日志

3.1 概念

重做日志(redo log)又叫事务日志,是InnoDB存储引擎独有的,它让MySQL拥有了崩溃恢复能力。

比如MySQL实例挂了或宕机了,重启时,InnoDB存储引擎会使用redo log恢复数据,保证数据的持久性与完整性。

641.png

MySQL中数据是以页为单位,当执行查询时,会从硬盘把一页的数据加载出来,加载出来的数据叫数据页,会放入到Buffer Pool中。后续的查询都是先从Buffer Pool中找,没有命中再去硬盘加载,减少硬盘IO开销,提升性能。更新表数据的时候,也是如此,发现Buffer Pool里存在要更新的数据,就直接在Buffer Pool里更新。

然后会把“在某个数据页上做了什么修改”记录到重做日志缓存(redo log buffer)里,接着刷盘到redo log文件里。

642.png

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

每条redo记录由“表空间号 + 数据页号 + 偏移量 + 修改数据长度 + 具体修改的数据”组成

3.2 刷盘时机

InnoDB存储引擎为redo log的刷盘策略提供了innodb_flush_log_at_trx_commit参数,它支持三种策略:

  • 0:表示每次事务提交时不进行刷盘操作
  • 1:表示每次事务提交时都将进行刷盘操作(默认值)
  • 2:表示每次事务提交时都只把redo log buffer内容写入page cache

innodb_flush_log_at_trx_commit参数默认为1,也就是说当事务提交时会调用fsyncredo log进行刷盘

另外,InnoDB存储引擎有一个后台线程,每隔1秒,就会把redo log buffer中的内容写到文件系统缓存(page cache),然后调用fsync刷盘。

643.png

也就是说,一个没有提交事务的redo log记录,也可能会刷盘。

为什么呢?

因为在事务执行过程redo log记录是会写入redo log buffer中,这些redo log记录会被后台线程刷盘。

644.png

除了后台线程每秒1次的轮询操作,还有一种情况,当redo log buffer占用的空间即将达到innodb_log_buffer_size一半的时候,后台线程会主动刷盘。

mysql日志操作步骤:

log_buff —mysql写 (write) —> log_file —OS刷新 (flush) —> disk

0(延迟写):log_buff —每隔1秒—> log_file —实时—> disk
1(实时写,实时刷):log_buff —实时—> log_file —实时—> disk
2(实时写,延迟刷):log_buff —实时—> log_file —每隔1秒—> disk

下面是不同刷盘策略的流程图。

3.2.1 延迟写

645.png

0时,如果MySQL挂了或宕机可能会有1秒数据的丢失。

3.2.2 实时写,实时刷

646.png

1时,只要事务提交成功,redo log记录就一定在硬盘里,不会有任何数据丢失。

如果事务执行期间MySQL挂了或宕机,这部分日志丢了,但是事务并没有提交,所以日志丢了也不会有损失。

3.2.3 实时写,延迟刷

647.png

2时,只要事务提交成功,redo log buffer中的内容只写入文件系统缓存(page cache)。

如果仅仅只是MySQL挂了不会有任何数据丢失,但是宕机可能会有1秒数据的丢失。

3.3 日志文件组

硬盘上存储的redo log日志文件不只一个,而是以一个日志文件组的形式出现的,每个的redo日志文件大小都是一样的。

比如可以配置为一组4个文件,每个文件的大小是1GB,整个redo log日志文件组可以记录4G的内容。

它采用的是环形数组形式,从头开始写,写到末尾又回到头循环写,如下图所示。

648.webp

日志文件组中还有两个重要的属性

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

每次刷盘redo log记录到日志文件组中,write pos位置就会后移更新。每次MySQL加载日志文件组恢复数据时,会清空加载过的redo log记录,并把checkpoint后移更新。write poscheckpoint之间空着的部分可以用来写入新的redo log记录。

649.webp

如果write pos追上checkpoint,表示日志文件组满了,这时候不能再写入新的redo log记录,MySQL得停下来,清空一些记录,把checkpoint推进一下。

650.webp

3.4 小结

只要每次把修改后的数据页直接刷盘不就好了,还有redo log什么事?

实际上,数据页大小只有16KB,刷盘比较耗时,可能就修改了数据页里的几Byte数据,有必要把完整的数据页刷盘吗?而且数据页刷盘是随机写,因为一个数据页对应的位置可能在硬盘文件的随机位置,所以性能是很差。如果是写redo log,一行记录可能就占几十Byte,只包含表空间号、数据页号、磁盘文件偏移量、更新值,再加上是顺序写,所以刷盘速度很快。所以用redo log形式记录修改内容,性能会远远超过刷数据页的方式,这也让数据库的并发能力更强。

其实内存的数据页在一定时机也会刷盘,我们把这称为页合并,讲Buffer Pool的时候会对这块细说

四、binlog日志

4.1 概念

redo log它是物理日志,记录内容是“在某个数据页上做了什么修改”,属于InnoDB存储引擎。

binlog是逻辑日志又叫二进制日志或者归档日志,记录内容是语句的原始逻辑,类似于“给ID=2这一行的c字段加1”,属于MySQL Server层。

不管用什么存储引擎,只要发生了表数据更新,都会产生binlog日志。那binlog到底是用来干嘛的?

可以说MySQL数据库的数据备份、主备、主主、主从都离不开binlog,需要依靠binlog来同步数据,保证数据一致性。

651.webp

binlog会记录所有涉及更新数据的逻辑操作,并且是顺序写。

4.2 记录格式

binlog日志有三种格式,可以通过binlog_format参数指定。

  • statement
  • row
  • mixed

指定statement,记录的内容是SQL语句原文,比如执行一条update T set update_time=now() where id=1,记录的内容如下。

652.webp

同步数据时,会执行记录的SQL语句,但是有个问题,update_time=now()这里会获取当前系统时间,直接执行会导致与原库的数据不一致。为了解决这种问题,我们需要指定为row,记录的内容不再是简单的SQL语句了,还包含操作的具体数据,记录内容如下。

653.webp

row格式记录的内容看不到详细信息,要通过mysqlbinlog工具解析出来。

update_time=now()变成了具体的时间update_time=1627112756247,条件后面的@1、@2、@3都是该行数据第1个~3个字段的原始值(假设这张表只有3个字段)。

这样就能保证同步数据的一致性,通常情况下都是指定为row,这样可以为数据库的恢复与同步带来更好的可靠性。但是这种格式,需要更大的容量来记录,比较占用空间,恢复与同步时会更消耗IO资源,影响执行速度。所以就有了一种折中的方案,指定为mixed,记录的内容是前两者的混合。

MySQL会判断这条SQL语句是否可能引起数据不一致,如果是,就用row格式,否则就用statement格式。

4.3 写入机制

binlog的写入时机也非常简单,事务执行过程中,先把日志写到binlog cache,事务提交的时候,再把binlog cache写到binlog文件中。因为一个事务的binlog不能被拆开,无论这个事务多大,也要确保一次性写入,所以系统会给每个线程分配一个块内存作为binlog cache

可以通过binlog_cache_size参数控制单个线程binlog cache大小,如果存储内容超过了这个参数,就要暂存到磁盘(Swap)。

binlog日志刷盘流程如下

654.webp

  • 上图的write,是指把日志写入到文件系统的page cache,并没有把数据持久化到磁盘,所以速度比较快
  • 上图的fsync,才是将数据持久化到磁盘的操作

writefsync的时机,可以由参数sync_binlog控制,默认是0。为0的时候,表示每次提交事务都只write,由系统自行判断什么时候执行fsync

655.webp

虽然性能得到提升,但是机器宕机,page cache里面的binglog会丢失。为了安全起见,可以设置为1,表示每次提交事务都会执行fsync,就如同binlog日志刷盘流程一样。最后还有一种折中方式,可以设置为N(N>1),表示每次提交事务都write,但累积N个事务后才fsync

656.webp

在出现IO瓶颈的场景里,将sync_binlog设置成一个比较大的值,可以提升性能。同样的,如果机器宕机,会丢失最近N个事务的binlog日志。

五、两阶段提交

redo log(重做日志)让InnoDB存储引擎拥有了崩溃恢复能力。binlog(归档日志)保证了MySQL集群架构的数据一致性。虽然它们都属于持久化的保证,但是则重点不同。

在执行更新语句过程,会记录redo logbinlog两块日志,以基本的事务为单位,redo log在事务执行过程中可以不断写入,而binlog只有在提交事务时才写入,所以redo logbinlog的写入时机不一样。

657.webp

回到正题,redo logbinlog两份日志之间的逻辑不一致,会出现什么问题?

我们以update语句为例,假设id=2的记录,字段c值是0,把字段c值更新成1SQL语句为update T set c=1 where id=2

假设执行过程中写完redo log日志后,binlog日志写期间发生了异常,会出现什么情况呢?

658.webp

由于binlog没写完就异常,这时候binlog里面没有对应的修改记录。因此,之后用binlog日志恢复数据时,就会少这一次更新,恢复出来的这一行c值是0,而原库因为redo log日志恢复,这一行c值是1,最终数据不一致。

659.webp

为了解决两份日志之间的逻辑一致问题,InnoDB存储引擎使用两阶段提交方案。原理很简单,将redo log的写入拆成了两个步骤preparecommit,这就是两阶段提交

660.webp

使用两阶段提交后,写入binlog时发生异常也不会有影响,因为MySQL根据redo log日志恢复数据时,发现redo log还处于prepare阶段,并且没有对应binlog日志,就会回滚该事务。

661.webp

再看一个场景,redo log设置commit阶段发生异常,那会不会回滚事务呢?

662.webp

并不会回滚事务,它会执行上图框住的逻辑,虽然redo log是处于prepare阶段,但是能通过事务id找到对应的binlog日志,所以MySQL认为是完整的,就会提交事务恢复数据。

六、总结

MySQLInnoDB引擎使用redo log(重做日志)保证事务的持久性,使用undo log(回滚日志)来保证事务的原子性。通过读写锁和MVCC来实现事务的隔离性。MySQL数据库的数据备份、主备、主主、主从都离不开binlog,需要依靠binlog来同步数据,保证数据一致性。

参考

posted @ 2022-04-25 16:55  夏尔_717  阅读(224)  评论(0编辑  收藏  举报