22Mysql日志

MySQL基础日志

1.1 日志分类

在任何一种数据库中,都会有各种各样的日志,记录着数据库工作的方方面面,以帮助数据库管理员追踪数据库曾经发生过的各种事件。MySQL有多种类型的日志,用于记录数据库的操作和状态。以下是一些常见的MySQL日志:

1、错误日志(Error Log):记录MySQL服务器在启动、运行过程中发生的错误和异常情况,如启动错误、语法错误等。

2、查询日志(Query Log):记录所有执行的查询语句,包括SELECT、INSERT、UPDATE、DELETE等操作。可以用于分析查询性能和调试问题,但需要注意对于高负载的系统,开启查询日志可能会对性能产生影响。

3、慢查询日志(Slow Query Log):记录执行时间超过指定阈值的查询语句。慢查询日志可以帮助你找出执行时间较长的查询,以便进行性能优化。

4、二进制日志(Binary Log):记录所有对数据库的更改操作,包括数据修改、表结构变更等。二进制日志可以用于数据恢复、主从复制等场景。

5、事务日志(Transaction Log):也称为重做日志(Redo Log),记录正在进行的事务的更改操作。事务日志用于保证数据库的ACID特性,并支持崩溃恢复。

1.2 错误日志

错误日志是 MySQL 中最重要的日志之一,它记录了当 mysqld 启动和停止以及服务器在运行过程中发生任何严重错误时的相关信息。当数据库出现任何故障导致无法正常使用时,可以首先查看此日志。

查看日志位置指令 :

1
show variables like 'log_error%';

执行结果如下所示:

log_error设置为stderr并且MySQL以守护进程(daemon)方式运行,那么错误日志将被重定向到系统日志文件(如/var/log/syslog)或其他操作系统特定的日志文件中,而不是直接输出到控制台。

可以通过如下配置,设置错误日志的输出位置:

打开MySQL的配置文件my.cnf。该文件通常位于MySQL安装目录下的/etc或者/etc/mysql子目录中。

1
2
[mysqld]
log_error = /var/lib/mysql/mysql-error.err

重启mysql进行测试。

查看日志内容 :

1
tail -f /var/lib/docker/volumes/mysql8_data/_data/mysql-error.err

1.3 查询日志

查询日志中记录了客户端的所有操作语句【CRUD】,而二进制日志不包含查询数据的SQL语句。

默认情况下, 查询日志是未开启的。

开启查询日志注意事项:

1、开启查询日志会对MySQL的性能产生一定影响,特别是在高负载环境下。因此,在生产环境中建议谨慎使用,并根据需要进行开启和关闭。

2、查询日志可能会记录大量的查询语句,导致日志文件过大。可以通过定期清理或限制日志文件大小来处理这个问题。

3、查询日志可能会包含敏感信息(如密码),因此要确保只有授权的人员可以访问查询日志文件。

1.4 慢查询日志

慢查询日志记录了所有执行时间超过参数 long_query_time 设置值,long_query_time 默认为 10 秒,最小为 0, 精度可以到微秒。

1.4.1 慢日志参数配置

默认情况下,MySQL数据库没有开启慢查询日志,需要我们手动来设置这个参数。当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。

慢查询日志默认是关闭的 。可以通过两个参数来控制慢查询日志 :

1
2
show variables like '%slow_query_log%';
SHOW VARIABLES LIKE '%long_query_time%'; -- 查看值:默认10秒

在mysql 的配置文件 /var/lib/mysql/my.cnf 中配置如下内容 :

1
2
3
4
5
6
[mysqld]
slow_query_log = 1 #开启
slow_query_log_file = /var/lib/mysql/slow-query.log # 若没有指定,默认名字为hostname_slow.log
long_query_time = 1 # 将默认的10秒为慢 修改为1秒为慢

log_queries_not_using_indexes = 1

重启mysql进行测试。

1.4.2 日志内容读取

1、执行查询操作

1
select * from t_emp ;

由于在查询的执行时间小于10s,因此该查询不会记录到慢查询日志中。

模拟慢查询效果:

1
select * from t_emp where id = 10 and sleep(2) ;   -- 使用SLEEP函数可以让查询暂停指定的时间

查看慢查询日志内容:

2 MySQL事务日志

2.1 事务概述

概述:就是由多个操作组成的一个逻辑单元,组成这个逻辑单元的多个操作要么都成功要么都失败。

作用:保证数据的一致性

举例:转账

2.2 ACID四大特性

A:原子性(Atomicity)

一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。(undo log)

C:一致性(Consistency)

事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。(MVCC undo log)

I:隔离性(Isolation)

指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。

事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。(锁)

D:持久性(Durability)

指的是只要事务成功结束,它对数据库所做的更新就必须保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。(redo log)

注意:新内容来了

1、事务的隔离性由 锁机制 实现。

2、而事务的原子性、一致性和持久性由事务的 redo日志undo日志来保证。

  • redo log称为重做日志 ,它记录了对数据库进行修改的操作,包括插入、更新和删除等。Redo日志的主要作用是保证数据库的持久性和恢复能力。

  • undo log称为回滚日志 ,它记录了对数据库进行修改的操作的逆操作,用于实现事务的回滚和MVCC(多版本并发控制)。

2.3 redo日志

2.3.1 简介

MySQL的Redo Log(重做日志)是一种用于数据持久性和故障恢复的重要机制。Redo Log记录了对数据库中数据的修改操作,以确保数据在发生故障时不会丢失,并且能够在数据库恢复后正确地恢复到修改前的状态。

目的:Redo Log的主要目的是记录对数据库的写操作(如INSERT、UPDATE、DELETE),而不是读操作。它是用于持久性的日志,在事务提交之前,将修改操作记录到Redo Log中,而不是直接写入磁盘上的数据文件。这样即使在数据库异常崩溃时,可以通过Redo Log来重新执行已提交的事务,保证数据的完整性。

Innodb引擎采用的是WAL技术(write-ahead logging) , 这种技术就是先写日志,再写磁盘,只有日志写入成功,为崩溃恢复准备

才算事务提交成功,这里的日志就是redo log。

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

1、重做日志的缓冲 (redo log buffer)

是一个内存缓冲区,用于暂时存储事务的Redo Log记录。当一个事务提交时,其对应的Redo Log记录会先写入到Redo Log Buffer中。然后,MySQL会根据Redo Log Buffer的刷新策略将其中的数据刷写到Redo Log File。

redo log buffer 大小,默认 16M ,最大值是4096M,最小值为1M。

1
show variables like '%innodb_log_buffer_size%';

2、重做日志文件 (redo log file) ,保存在硬盘中,是持久的。redo log日志文件如下所示:

1
2
cd /var/lib/docker/volumes/mysql8_data/_data/
ls -a

2.3.2 redolog工作流程和组成

逻辑上 redo log buffer 是一个 环形队列

  • write pos:当前写入位置。
  • checkpoint:当前已刷入磁盘并可覆盖的位置。
  • 规则:write pos 追上 checkpoint 时表示日志满了,不能写新事务 如下面这个图所示。
image-20251116090722076

checkPoint机制: 作用是将缓冲池中的脏页((更改完之后的数据)刷新到磁盘

write pos 是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头。checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。write pos 和 checkpoint 之间的是redolog中还空着的部分,可以用来记录新的操作。

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

有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失

整体写数据的流程如下所示

image-20251116090933421

整体流程说明:

第1步事务开始:先将原始数据从磁盘中读入内存中来,修改数据的内存拷贝

第2步事务修改数据:生成一条重做日志并写入redo log buffer,记录的是数据被修改后的值

第3步事务提交:当事务commit时,根据刷盘策略决定是否将redo log buffer中的内容刷新到 redo log file, 刷盘完成后,事务才算真正持久化成功 (满足 WAL:Write Ahead Logging)

第4步奔溃恢复:InnoDB 重启后会根据 redo log 恢复未刷盘的数据,保证 事务的持久性(D)

2.3.3 redo log的刷盘策略

redo log的写入并不是直接写入磁盘的,InnoDB引擎会在写redo log的时候先写redo log buffer,之后以 一定策略将redo log buffer刷入到真正的redo log file 中。这里的策略这就是我们要说的刷盘策略。

image-20251116091023088

InnoDB给出 innodb_flush_log_at_trx_commit 参数,该参数控制 commit提交事务时,如何将 redo log buffer 中的日志刷新到 redo log file 中。

1
2
-- 查看innodb_flush_log_at_trx_commit变量的值
SHOW VARIABLES LIKE '%innodb_flush_log_at_trx_commit%';

它支持三种策略:

0:事务提交时 不写磁盘,只是写入 Log Buffer;每秒刷一次盘。

  • 性能最好,持久性最差(宕机可能丢 1 秒数据 + 已提交事务)。

1(默认):事务每次提交时,写入 Log Buffer 并立刻刷盘(fsync)。

  • 最安全,保证事务提交后不会丢失。
  • 性能开销最大(频繁 fsync)。

2:事务提交时,写入 OS 缓冲区(write),由 OS 自己决定什么时候 fsync(通常 1s 内)。

  • 折中方案,宕机时可能丢 OS buffer 中的数据

2.4 undo日志

2.4.1 undo日志简介

MySQL中的Undo Log是用于支持事务原子性和一致性读的关键组件。它记录了对数据的修改操作,使得在事务回滚或进行一致性读取时,可以撤销或恢复数据到先前的状态。

当执行一个事务时,MySQL会将事务对数据的修改操作记录在Undo Log中,而不是直接在数据页上进行修改。这样做的好处是,即使事务执行过程中出现错误或被回滚,也可以通过Undo Log来撤销对数据的修改,使得数据库保持一致性。

在一致性读取(Consistent Read)中,如果一个事务在读取某个数据时,同时另一个事务在修改该数据,那么读取的事务会通过Undo Log来获取未修改的原始数据,从而保证读取的数据是一致的。

2.4.2 undo log存储结构

回滚段与undo页

InnoDB对undo log的管理采用段的方式,也就是回滚段(rollback segment) 。每个回滚段记录了 1024 个 undo log segment ,而在每个undo log segment段中进行 undo页 (存储的就是回滚记录)的申请。 在 InnoDB1.1版本之前 (不包括1.1版本),只有一个rollback segment,因此支持同时在线的事务限制为 1024 。虽然对绝大多数的应用来说都已经够用。 从1.1版本开始InnoDB支持最大 128个rollback segment ,故其支持同时在线的事务限制提高到 了 128*1024 。

undo页的重用

在MySQL中,undo页的重用是指当事务提交或回滚后,之前使用的undo页可以被重新利用来存储新的事务的undo信息。这个过程称为undo页的重用。

Undo页是用于实现事务的回滚和MVCC(多版本并发控制)机制的关键组成部分。当一个事务执行更新操作时,旧的数据会被写入到undo页中,以便在事务回滚时能够恢复到之前的状态。而在MVCC中,每个事务都可以看到自己开始之前的数据库快照,这些快照通过undo页来实现。

当一个事务提交或回滚后,其对应的undo页就不再需要了。为了节省空间和提高性能,MySQL会将这些不再需要的undo页标记为可重用状态,并将它们添加到一个undo页的空闲链表中。当新的事务需要分配undo页时MySQL会首先尝试从空闲链表中获取可重用的undo页,而不是分配新的页。

通过重用undo页,可以减少对磁盘空间的需求,提高系统性能。然而,如果系统中存在长时间运行的读事务或长时间运行的只读事务,可能会导致undo页无法及时重用,从而增加了undo段的大小和磁盘空间的占用。

回滚段与事务

1、每个事务只会使用一个回滚段,一个回滚段在同一时刻可能会服务于多个事务。

2、当一个事务开始的时候,会制定一个回滚段,在事务进行的过程中,当数据被修改时,原始的数据会被复制到回滚段。

3、当事务提交时,InnoDB存储引擎会做以下两件事情:

  • 将undo log放入列表中,以供之后的purge(清理)操作
  • 判断undo log所在的页是否可以重用,若可以分配给下个事务使用

回滚段中的数据分类

uncommitted undo information

未提交的回滚数据(uncommitted undo information)是指在事务执行过程中所做的修改,但尚未提交的数据

committed undo information

已经提交但未过期的回滚数据(committed undo information)是指在事务执行过程中所做的修改,并且已经成功提交到数据库中的数据。

expired undo information

事务已经提交并过期的数据(expired undo information)是指在事务执行过程中所做的修改,并且已经成功提交到数据库中,但由于某些原因被标记为可回收的数据

2.4.3 undo log类型

select 查询操作不会生成 undo log

在InnoDB存储引擎中,undo log分为:insert undo log和update undo log

insert undo log

Insert undo log(插入撤销日志)是数据库中用于记录插入操作的一种撤销日志。因为insert操作的记录,只对事务本身可见,对其他事务不可见(这是事务的隔离性的要求),因此 undo log可以在事务提交之后删除

update undo log

Update undo log(更新撤销日志)是数据库中用于记录更新操作(delete、update)的一种撤销日志。该undo log可能需要提供MVCC机制,因此不能在事务提交时就进行删除。提交是放入undo log链表,等待purge线程进行最后的删除。

2.4.4 undo log的生命周期

事务开始:

对于InnoDB引擎来说,每个行记录除了记录本身的数据之外,还有几个隐藏的列:

1、DB_ROW_ID: 如果没有为表显式的定义主键,并且表中也没有定义唯一索引,那么InnoDB会自动为表添加一个row_id的隐藏列作为主键。

2、DB_TRX_ID: 每个事务都会分配一个事务的ID,当对某条记录发生变更时,就会将这个事务的事务ID写入trx_id中。

3、DB_ROLL_PTR(roll_pointer ): 回滚指针,本质上就是指向undo log的指针。

image-20251116095953849

当我们执行INSERT时:

1
2
begin;
INSERT INTO user (name) VALUES ("tom");

插入的数据都会生成一条insert undo log , 并且数据的回滚指针会指向它。undo log会记录undo log的序号,插入主键的列和值…., 那么在进行rollback的时候,通过主键直接把对应的数据删除即可。

image-20251116100008460

当我们执行UPDATE时:

对于更新的操作会产生update undo log,并且会分更新主键的和不更新主键的,假设现在执行:

1
update user set name = 'Sun' where id = 1 ;

image-20251116100019766

这时会把老的记录写入新的undo log,让回滚指针指向新的undo log,它的undo log是1,并且新的undo log会指向老的undo log(undo no = 0)。

假设现在执行:

1
update user set id = 2 where id = 1 ;

对于更新主键的操作,会先把原来的数据deletemark标识打开,这时并没有真正的删除数据,真正的删除会交给清理线程去判断,然后在后面插入一条新的数据,新数据也会产生undo log,并且undo log的序号会递增。

可以发现每次对数据的变更都会产生一个undo log,当一条记录被变更多次时,那么就会产生多条undo log,undo log记录的是变更前的日志,并且每个undo log的序号是递增的,那么要当回滚的时候,按照序号依次向前,这样就会找到原始数据了。

备注:

“deletemark”这个词在MySQL或InnoDB中并不是一个实际存在的术语,它只是用于解释逻辑删除的概念,而实际上逻辑删除是通过添加特殊标志字段来实现的

undo log的删除

1、针对于insert undo log 因为insert操作的记录,只对事务本身可见,对其他事务不可见。故该undo log可以在事务提交后直接删除,不需要进行purge操作。

2、针对于update undo log 该undo log可能需要提供MVCC机制,因此不能在事务提交时就进行删除。提交时放入undo log链表,等待purge线程进行最后的删除。

undolog总结

  1. 事务开始: 当事务开始时,MySQL 会为该事务分配一个唯一的事务 ID,并为该事务启动一个对应的 undo log。这个 undo log 会在整个事务的生命周期中跟随事务,并记录事务所做的数据修改操作。
  2. 数据修改: 在事务执行期间,如果对数据库中的数据进行了修改操作(例如插入、更新、删除等),这些修改操作会被记录在事务对应的 undo log 中。这些修改是尚未提交的,只存在于事务的私有工作区中。
  3. 事务提交或回滚: 当事务执行完成后,会进入提交或回滚阶段。
    • 如果事务被提交,那么在提交之前,MySQL 会将事务的修改操作应用到数据库中,并将这些修改标记为已提交。这意味着这些修改将永久保存在数据库中,并可以被其他事务所见。
    • 如果事务被回滚,那么在回滚之前,MySQL 会撤销事务的修改操作,恢复数据到事务开始前的状态。这是通过 undo log 记录的原始数据来实现的。
  4. 回收: 当事务完成提交或回滚后,其对应的 undo log 将不再需要,因为数据已经被应用或回滚到了相应的状态。此时,MySQL 会对这些已经不再需要的 undo log 进行回收,释放相应的空间,以便重用。
  5. 版本链的维护: 在使用 MVCC 时,为了支持并发访问和事务隔离性,MySQL 需要维护一个版本链(version chain)。每个数据行都可能存在多个版本,每个版本都有一个对应的 undo log 记录。在查询时,MySQL 会根据事务的隔离级别和时间戳来选择合适的版本。

补充

有了undolog为啥还需要redolog呢?

Buffer Pool 是提高了读写效率没错,但是问题来了,Buffer Pool 是基于内存的,而内存总是不可靠,万一断电重启,还没来得及落盘的脏页数据就会丢失。

为了防止断电导致数据丢失的问题,当有一条记录需要更新的时候,InnoDB 引擎就会先更新内存(同时标记为脏页),然后将本次对这个页的修改以 redo log 的形式记录下来,这个时候更新就算完成了

后续,InnoDB 引擎会在适当的时候,由后台线程将缓存在 Buffer Pool 的脏页刷新到磁盘里,这就是 WAL (Write-Ahead Logging)技术

WAL 技术指的是, MySQL 的写操作并不是立刻写到磁盘上,而是先写日志,然后在合适的时间再写到磁盘上

过程如下图:

img

redo log 是物理日志,记录了某个数据页做了什么修改,比如对 XXX 表空间中的 YYY 数据页 ZZZ 偏移量的地方做了AAA 更新,每当执行一个事务就会产生这样的一条或者多条物理日志。

在事务提交时,只要先将 redo log 持久化到磁盘即可,可以不需要等到将缓存在 Buffer Pool 里的脏页数据持久化到磁盘。

当系统崩溃时,虽然脏页数据没有持久化,但是 redo log 已经持久化,接着 MySQL 重启后,可以根据 redo log 的内容,将所有数据恢复到最新的状态。

redo log 和 undo log 这两种日志是属于 InnoDB 存储引擎的日志,它们的区别在于:

  • redo log 记录了此次事务「完成后」的数据状态,记录的是更新之后的值;
  • undo log 记录了此次事务「开始前」的数据状态,记录的是更新之前的值;

事务提交之前发生了崩溃,重启后会通过 undo log 回滚事务,事务提交之后发生了崩溃,重启后会通过 redo log 恢复事务,如下图:

img

所以有了 redo log,再通过 WAL 技术,InnoDB 就可以保证即使数据库发生异常重启,之前已提交的记录都不会丢失,这个能力称为 crash-safe(崩溃恢复)。可以看出来, redo log 保证了事务四大特性中的持久性

写入 redo log 的方式使用了追加操作, 所以磁盘操作是顺序写,而写入数据需要先找到写入位置,然后才写到磁盘,所以磁盘操作是随机写

磁盘的「顺序写 」比「随机写」 高效的多,因此 redo log 写入磁盘的开销更小。

针对「顺序写」为什么比「随机写」更快这个问题,可以比喻为你有一个本子,按照顺序一页一页写肯定比写一个字都要找到对应页写快得多。

可以说这是 WAL 技术的另外一个优点:MySQL 的写操作从磁盘的「随机写」变成了「顺序写」,提升语句的执行性能。这是因为 MySQL 的写操作并不是立刻更新到磁盘上,而是先记录在日志上,然后在合适的时间再更新到磁盘上 。

两者的工作过程:

  1. 事务执行中
    → 先写 undo log(为回滚准备)
    → 再写 redo log(为崩溃恢复准备)
  2. 事务提交
    → 先确保 redo log 刷盘(持久化)
    → 再释放 undo log(节省空间)
  3. 系统崩溃恢复
    → 用 redo log 重做已提交事务
    → 用 undo log 回滚未提交事务

至此, 针对为什么需要 redo log

这个问题我们有两个答案:

  • 实现事务的持久性,让 MySQL 有 crash-safe 的能力,能够保证 MySQL 在任何时间段突然崩溃,重启后之前已提交的记录都不会丢失;
  • redo log 和 undo log 这两种日志是属于 InnoDB 存储引擎的日志,它们的区别在于:
    • redo log 记录了此次事务「完成后」的数据状态,记录的是更新之后的值;只解决“系统崩溃”的问题。
    • undo log 记录了此次事务「开始前」的数据状态,记录的是更新之前的值;只解决“事务中途失败”的问题
  • 将写操作从「随机写」变成了「顺序写」,提升 MySQL 写入磁盘的性能。

能不能只用binlog不用relo log?

不行,binlog是 server 层的日志,没办法记录哪些脏页还没有刷盘,redolog 是存储引擎层的日志,可以记录哪些脏页还没有刷盘,这样崩溃恢复的时候,就能恢复那些还没有被刷盘的脏页数据。

binlog 两阶段提交过程是怎么样的?

事务提交后,redo log 和 binlog 都要持久化到磁盘,但是这两个是独立的逻辑,可能出现半成功的状态,这样就造成两份日志之间的逻辑不一致。

在 MySQL 的 InnoDB 存储引擎中,开启 binlog 的情况下,MySQL 会同时维护 binlog 日志与 InnoDB 的 redo log,为了保证这两个日志的一致性,MySQL 使用了内部 XA 事务(是的,也有外部 XA 事务,跟本文不太相关,我就不介绍了),内部 XA 事务由 binlog 作为协调者,存储引擎是参与者。

当客户端执行 commit 语句或者在自动提交的情况下,MySQL 内部开启一个 XA 事务,分两阶段来完成 XA 事务的提交,如下图:

image-20240725231904598

从图中可看出,事务的提交过程有两个阶段,就是将 redo log 的写入拆成了两个步骤:prepare 和 commit,中间再穿插写入binlog,具体如下:

  • prepare 阶段:将 XID(内部 XA 事务的 ID) 写入到 redo log,同时将 redo log 对应的事务状态设置为 prepare,然后将 redo log 持久化到磁盘(innodb_flush_log_at_trx_commit = 1 的作用);
  • commit 阶段:把 XID 写入到 binlog,然后将 binlog 持久化到磁盘(sync_binlog = 1 的作用),接着调用引擎的提交事务接口,将 redo log 状态设置为 commit,此时该状态并不需要持久化到磁盘,只需要 write 到文件系统的 page cache 中就够了,因为只要 binlog 写磁盘成功,就算 redo log 的状态还是 prepare 也没有关系,一样会被认为事务已经执行成功;

我们来看看在两阶段提交的不同时刻,MySQL 异常重启会出现什么现象?下图中有时刻 A 和时刻 B 都有可能发生崩溃:

image-20240725231850469

不管是时刻 A(redo log 已经写入磁盘, binlog 还没写入磁盘),还是时刻 B (redo log 和 binlog 都已经写入磁盘,还没写入 commit 标识)崩溃,此时的 redo log 都处于 prepare 状态

在 MySQL 重启后会按顺序扫描 redo log 文件,碰到处于 prepare 状态的 redo log,就拿着 redo log 中的 XID 去 binlog 查看是否存在此 XID:

  • 如果 binlog 中没有当前内部 XA 事务的 XID,说明 redolog 完成刷盘,但是 binlog 还没有刷盘,则回滚事务。对应时刻 A 崩溃恢复的情况。
  • 如果 binlog 中有当前内部 XA 事务的 XID,说明 redolog 和 binlog 都已经完成了刷盘,则提交事务。对应时刻 B 崩溃恢复的情况。

可以看到,对于处于 prepare 阶段的 redo log,即可以提交事务,也可以回滚事务,这取决于是否能在 binlog 中查找到与 redo log 相同的 XID,如果有就提交事务,如果没有就回滚事务。这样就可以保证 redo log 和 binlog 这两份日志的一致性了。

所以说,两阶段提交是以 binlog 写成功为事务提交成功的标识,因为 binlog 写成功了,就意味着能在 binlog 中查找到与 redo log 相同的 XID。

背诵

“先写redo prepare,再写binlog,最后commit redo。
崩溃看binlog:有XID就提交,无XID就回滚!”

讲一下binlog

MySQL 在完成一条更新操作后,Server 层还会生成一条 binlog,等之后事务提交的时候,会将该事物执行过程中产生的所有 binlog 统一写 入 binlog 文件,binlog 是 MySQL 的 Server 层实现的日志,所有存储引擎都可以使用。

binlog 是追加写,写满一个文件,就创建一个新的文件继续写,不会覆盖以前的日志,保存的是全量的日志,用于备份恢复、主从复制;

binlog 文件是记录了所有数据库表结构变更和表数据修改的日志,不会记录查询类的操作,比如 SELECT 和 SHOW 操作。

binlog 有 3 种格式类型,分别是 STATEMENT(默认格式)、ROW、 MIXED

为什么要写RedoLog,而不是直接写到B+树里面?

因为 redolog 写入磁盘是顺序写,而 b+树里数据页写入磁盘是随机写,顺序写的性能会比随机写好,这样可以提升事务提交的效率。

最重要的是redolog具备故障恢复的能力,Redo Log 记录的是物理级别的修改,包括页的修改,如插入、更新、删除操作在磁盘上的物理位置和修改内容。

mysql 两次写(double write buffer)了解吗?

我们常见的服务器一般都是Linux操作系统,Linux文件系统页(OS Page)的大小默认是4KB。而MySQL的页(Page)大小默认是16KB。

MySQL程序是跑在Linux操作系统上的,需要跟操作系统交互,所以MySQL中一页数据刷到磁盘,要写4个文件系统里的页。

img

需要注意的是,这个操作并非原子操作,比如我操作系统写到第二个页的时候,Linux机器断电了,这时候就会出现问题了。造成”页数据损坏“。并且这种”页数据损坏“靠 redo日志是无法修复的。

Doublewrite Buffer的出现就是为了解决上面的这种情况,虽然名字带了Buffer,但实际上Doublewrite Buffer是内存+磁盘的结构。

img

Doublewrite Buffer 作用是,在把页写到数据文件之前,InnoDB先把它们写到一个叫doublewrite buffer(双写缓冲区)的共享表空间内,在写doublewrite buffer完成后,InnoDB才会把页写到数据文件的适当的位置。如果在写页的过程中发生意外崩溃,InnoDB在稍后的恢复过程中在doublewrite buffer中找到完好的page副本用于恢复,所以本质上是一个最近写回的页面的备份拷贝。

img

如上图所示,当有页数据要刷盘时:

  • 页数据先通过memcpy函数拷贝至内存中的Doublewrite Buffer(大小为约 2MB)中,Doublewrite Buffer 分为两个区域,每次写入一个区域(最多 1MB 的数据)。
  • Doublewrite Buffer的内存里的数据页,会fsync刷到Doublewrite Buffer的磁盘上,写两次到到共享表空间中(连续存储,顺序写,性能很高),每次写1MB;
  • 写入完成后,再将脏页刷到数据磁盘存储.ibd文件上(随机写);

当MySQL出现异常崩溃时,有如下几种情况发生:

  • 情况一:步骤1前宕机,刷盘未开始,数据在redo log,后期可以恢复
  • 情况二:步骤1后,步骤2前宕机,因为是在内存中,宕机清空内存,和情况1一样
  • 情况三:步骤2后,步骤3前宕机,因为DWB的磁盘有完整的数据,可以修复损坏的页数据

由此我们可以得出结论,double write buffer是针对实际的buffer数据页的原子性保证,就是避免MySQL异常崩溃时,写的那几个data page不会出错,要么都写了,要么什么都没有做。

为什么redolog无法代替double write buffer?

redolog的设计之初,是“账本的作用”,是一种操作日志,用于MySQL异常崩溃恢复使用,是InnoDB引擎特有的日志,本质上是物理日志,记录的是 “ 在某个数据页上做了什么修改 ” ,

但如果数据页本身已经发生了损坏,redolog来恢复已经损坏的数据块是无效的,数据块的本身已经损坏,再次重做依然是一个坏块。

所以此时需要一个数据块的副本来还原该损坏的数据块,再利用重做日志进行其他数据块的重做操作,这就是double write buffer的原因作用。

MySQL的Buffer Pool:数据库的”记忆库”

Buffer Pool(缓冲池)是MySQL InnoDB存储引擎中最重要的内存组件之一,相当于数据库的”快速记忆库”。 它主要用于缓存数据和索引,大幅减少磁盘I/O,让数据库运行得更快。

1. Buffer Pool的核心作用

作用 说明 速度对比
缓存数据页 将经常访问的表数据存储在内存中 内存访问:0.1ms 磁盘访问:10ms+
缓存索引页 将B+树索引结构缓存到内存 使查询更快定位数据
减少磁盘I/O 避免重复从磁盘读取相同数据 提升性能3-10倍

💡 简单比喻:就像你家的冰箱(Buffer Pool)会提前把常吃的菜(数据)放进去,不用每次做饭都去超市(磁盘)买,节省大量时间。

2. Buffer Pool的工作原理

读操作流程(缓存命中)

tongyi-mermaid-2025-11-17-121727

写操作流程(写入优化)

  1. 先修改内存:更新Buffer Pool中的数据页
  2. 记录日志:将修改写入redo log(保证数据安全)
  3. 异步刷盘:后台线程在适当时候将修改写入磁盘

✅ 这种”先内存后磁盘”的机制是InnoDB高性能的关键

3. Buffer Pool的内部结构

组件 功能 重要性
LRU列表 管理缓存页,最近最少使用的页会被淘汰 ⭐⭐⭐⭐
Change Buffer 缓存二级索引的更新(非聚集索引) ⭐⭐⭐
Adaptive Hash Index 基于热点数据构建哈希索引,加速等值查询 ⭐⭐⭐
Free List 记录空闲的缓存页 ⭐⭐

📌 关键点:InnoDB使用了改进的LRU算法(不是简单的LRU),避免”热点数据”被频繁淘汰。

4. 为什么Buffer Pool如此重要?

  • 磁盘I/O是数据库性能瓶颈:磁盘读写比内存慢10000倍以上
  • 典型场景:在电商大促时,Buffer Pool能确保系统快速响应
  • 配置影响:Buffer Pool占系统内存的50%-70%时性能最佳

end


22Mysql日志
http://example.com/2025/11/16/22Mysql日志/
作者
無鎏雲
发布于
2025年11月16日
许可协议