1. 日志流程图

  • 逻辑日志: 可以简单理解为记录的就是 sql 语句。
  • 物理日志mysql 数据最终是保存在数据页中的,物理日志记录的就是数据页变更。
  • 只有 Redolog 是物理日志,Binlog 和 Undolog 都是逻辑日志。

image.png

2. Binlog

逻辑日志

2.1. 记录内容 - DDL 和 DML

binlog 用于记录数据库执行的写入性操作 (DDL 和 DML)信息,以二进制的形式保存在磁盘中。binlogmysql逻辑日志并且由 Server 层进行记录,使用任何存储引擎的 mysql 数据库都会记录 binlog 日志
比如 update T set c=c+1 where ID=2; 这条 SQL,redo log 中记录的是 :xx页号,xx偏移量的数据修改为xxx;binlog 中记录的是:id = 2 这一行的 c 字段 +1

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

2.2. 记录时机 - 更新完数据页之后

更新完成之后,事务提交之前,先写入 binlog cache 中
提交之后,刷盘到 binlog file 中

2.3. 使用场景

在实际应用中,binlog 的主要使用场景有两个,分别是 主从复制 和 数据恢复 。

  1. 主从复制 :在 Master 端开启 binlog,然后将 binlog 发送到各个 Slave 端,Slave 端重放 binlog 从而达到主从数据一致。❕%%
    ▶1.🏡⭐️◼️Canal 的使用依赖于主从同步模式,尤其是 Binlog◼️⭐️-point-20230302-0741%%

    变形用法 : Canal
  2. 数据恢复 :通过使用 mysqlbinlog 工具来恢复数据。

2.4. 刷盘时机 -cache←前 commit 后→file

image.png

  •  上图的 write,是指把日志写入到文件系统的 page cache,并没有把数据持久化到磁盘,所以速度比较快
  •  上图的 fsync,才是将数据持久化到磁盘的操作,所以频繁的 fsync 会导致磁盘的 I/O 升高
    %%
    ▶1.🏡⭐️◼️binlog 日志记录逻辑 ?🔜MSTM📝 事务发起时,先写入 binlog cache,等事务提交时才写出到文件中◼️⭐️-point-20230301-1110%%

    对于 InnoDB 存储引擎而言,事务执行过程中,先把日志写到 binlog cache,事务提交的时候,再把 binlog cache 写到 binlog 文件中。那么 binlog 是什么时候刷到磁盘中的呢?mysql 通过 sync_binlog 参数控制 binlog 的刷盘时机,取值范围是 0-N
  • 0:不去强制要求,由系统自行判断何时写入磁盘;
  • 1:每次 commit 的时候都要将 binlog 写入磁盘(5.7 之后默认值)
  • N:每 N 个事务,才会将 binlog 写入磁盘。

从上面可以看出,sync_binlog 最安全的是设置是 1,这也是 MySQL 5.7.7
之后版本的默认值。但是设置一个大一些的值可以提升数据库性能,因此实际情况下也可以将值适当调大,牺牲一定的一致性来获取更好的性能

2.5. 日志格式

binlog 日志有三种格式,分别为 STATMENTROWMIXED

MySQL 5.7.7 之前,默认的格式是 STATEMENTMySQL 5.7.7 之后,默认值是 ROW。日志格式通过 binlog-format 指定。

  • ROW: 基于行的复制 (row-based replication, RBR),不记录每条 sql 语句的上下文信息,仅需记录哪条数据被修改了。
    • 优点: 不会出现某些特定情况下的存储过程、或 function、或 trigger 的调用和触发无法被正确复制的问题 ;
    • 缺点: 会产生大量的日志,尤其是 alter table 的时候会让日志暴涨

2.6. 主从复制 (主从同步)

MySQL 的主从复制依赖于 binlog ,也就是记录 MySQL 上的所有变化并以二进制形式保存在磁盘上。复制的过程就是将 binlog 中的数据从主库传输到从库上。

这个过程一般是 异步 的,也就是主库上执行事务操作的线程不会等待复制 binlog 的线程同步完成。
image.png
MySQL 集群的主从复制过程梳理成 3 个阶段:

  • 写入 Binlog:主库写 binlog 日志,提交事务,并更新本地存储数据。
  • 同步 Binlog:把 binlog 复制到所有从库上,每个从库把 binlog 写到暂存日志中。
  • 回放 Binlog:回放 binlog,并更新存储引擎中的数据。

具体详细过程如下:

  • MySQL 主库在收到客户端提交事务的请求之后,会先写入 binlog,再提交事务,更新存储引擎中的数据,事务提交完成后,返回给客户端“操作成功”的响应。
  • 从库会创建一个专门的 I/O 线程,连接主库的 log dump 线程,来接收主库的 binlog 日志,再把 binlog 信息写入 relay log 的中继日志里,再返回给主库“复制成功”的响应。
  • 从库会创建一个用于回放 binlog 的线程,去读 relay log 中继日志,然后回放 binlog 更新存储引擎中的数据,最终实现主从的数据一致性。

在完成主从复制之后,你就可以在写数据时只写主库,在读数据时只读从库,这样即使写请求会锁表或者锁记录,也不会影响读请求的执行。

从库是不是越多越好?

不是的。

因为从库数量增加,从库连接上来的 I/O 线程也比较多,主库也要创建同样多的 log dump 线程来处理复制的请求,对主库资源消耗比较高,同时还受限于主库的网络带宽

所以在实际使用中,一个主库一般跟 2~3 个从库(1 套数据库,1 主 2 从 1 备主),这就是一主多从的 MySQL 集群结构。

MySQL 主从复制还有哪些模型?

主要有三种:

  • 同步复制:MySQL 主库提交事务的线程要等待所有从库的复制成功响应,才返回客户端结果。这种方式在实际项目中,基本上没法用,原因有两个:一是性能很差,因为要复制到所有节点才返回响应;二是可用性也很差,主库和所有从库任何一个数据库出问题,都会影响业务。
  • 异步复制(默认模型):MySQL 主库提交事务的线程并不会等待 binlog 同步到各从库,就返回客户端结果。这种模式一旦主库宕机,数据就会发生丢失。
  • 半同步复制:MySQL 5.7 版本之后增加的一种复制方式,介于两者之间,事务线程不用等待所有的从库复制成功响应,只要一部分复制成功响应回来就行,比如一主二从的集群,只要数据成功复制到任意一个从库上,主库的事务线程就可以返回给客户端。这种 半同步复制的方式,兼顾了异步复制和同步复制的优点,即使出现主库宕机,至少还有一个从库有最新的数据,不存在数据丢失的风险

3. Buffer Pool

MySQL-1、基本原理

image.png

4. Redolog

https://xiaolincoding.com/mysql/log/how_update.html#redo-log-%E6%96%87%E4%BB%B6%E5%86%99%E6%BB%A1%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E

物理日志

4.1. 为什么需要 redo log

%%
▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230414-1643%%
❕ ^k16y1h

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

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

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

WAL 技术指的是, MySQL 的写操作并不是立刻写到磁盘上,而是先写日志,然后在合适的时间再写到磁盘上。这里与 redolog 从 redolog buffer 到 redolog file 的落盘细节无关。WAL 说的是 DML 先写出到顺序写的日志文件再写出到随机写的数据库磁盘文件的技术思想。

4.2. 记录内容 -DDL 和 DML

DML 和 DDL

4.3. 记录时机 - prepare<- 前 commit 后 ->commit

Buffer Pool 中的数据更改之后,写到 redolog Buffer 中,状态为 prepare
提交之后,状态修改为 commit。
提交之后有没有刷盘看参数 innodb_flush_log_at_trx_commit 的配置

4.4. Crash-Safe- 总会执行恢复

  • 实现事务的持久性,让 MySQL 有 crash-safe 的能力,能够保证 MySQL 在任何时间段突然崩溃,重启后之前已提交的记录都不会丢失;
  • 将写操作从「随机写」变成了「顺序写」,提升 MySQL 写入磁盘的性能。
  1. 因为 Innodb 是以 为单位进行磁盘交互的,而一个事务很可能只修改一个数据页里面的几个字节,这个时候将完整的数据页刷到磁盘的话,太浪费资源了!
  2. 一个事务可能涉及修改多个数据页,并且这些数据页在物理上并不连续,使用随机 IO 写入性能太差!
  3. 因此 mysql 设计了 redo log, 具体来说就是只记录事务对数据页做了哪些修改
    ,这样就能完美地解决性能问题了 (相对而言文件更小并且是顺序 IO)。

启动 innodb 的时候,不管上次是正常关闭还是异常关闭,总是会进行恢复操作。因为 redo log 记录的是数据页的物理变化,因此恢复的时候速度比逻辑日志 (如 binlog) 要快很多。
有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为 crash-safe。

4.5. 大小及存储逻辑

每次更新操作都要往 redo log 中写入,如果 redo log 满了,空间不够用了怎么办?
InnoDB 的 redo log 文件是固定大小的,比如可以配置一组 4 个文件,每个文件大小是 1GB,那么 redo log 中可以记录 4GB 的操作,InnoDB 会从第一个文件开始写入,直到第四个文件写满了,又回到第一个文件开头循环写,如下图。

image.png

图中的:

  1. write pos 和 checkpoint 的移动都是顺时针方向;
  2. write pos ~ checkpoint 之间的部分(图中的红色部分),用来记录新的更新操作;
  3. check point ~ write pos 之间的部分(图中蓝色部分):待落盘的脏数据页记录

如果 write pos 追上了 checkpoint,就意味着 redo log 文件满了,这时 MySQL 不能再执行新的更新操作,也就是说 MySQL 会被阻塞(_因此所以针对并发量大的系统,适当设置 redo log 的文件大小非常重要_),此时 会停下来将 Buffer Pool 中的脏页刷新到磁盘中,然后标记 redo log 哪些记录可以被擦除,接着对旧的 redo log 记录进行擦除,等擦除完旧记录腾出了空间,checkpoint 就会往后移动(图中顺时针),然后 MySQL 恢复正常运行,继续执行新的更新操作。

所以,一次 checkpoint 的过程就是脏页刷新到磁盘中变成干净页,然后标记 redo log 哪些记录可以被覆盖的过程。

4.6. redolog buffer

实际上,执行一个事务的过程中,产生的 redo log 也不是直接写入磁盘的,因为这样会产生大量的 I/O 操作,而且磁盘的运行速度远慢于内存。

所以,redo log 也有自己的缓存—— redo log buffer,每当产生一条 redo log 时,会先写入到 redo log buffer,后续在持久化到磁盘

redo log buffer 默认大小 16 MB,可以通过 innodb_log_Buffer_size 参数动态的调整大小,增大它的大小可以让 MySQL 处理「大事务」是不必写入磁盘,进而提升写 IO 性能。

4.7. 刷盘时机-redologbuffer→磁盘

指的是 redo log 从 redolog buffer 写出到磁盘的策略

4.7.1. 默认时机

  1. MySQL 正常关闭时;
  2. 当 redo log buffer 中记录的写入量大于 redo log buffer 内存空间的一半时,会触发落盘;
  3. InnoDB 的后台线程每隔 1 秒,将 redo log buffer 持久化到磁盘。
  4. 每次事务提交时都将缓存在 redo log buffer 里的 redo log 直接持久化到磁盘(这个策略可由 innodb_flush_log_at_trx_commit 参数控制

4.7.2. 提交事务时策略

除此之外,InnoDB 还提供了另外两种策略,由参数 innodb_flush_log_at_trx_commit 参数控制,可取的值有:0、1、2,默认值为 1,这三个值分别代表的策略如下:

  • 当设置该参数为 0 时,表示每次事务提交时 ,还是将 redo log 留在 redo log buffer 中 ,该模式下在事务提交时不会主动触发写入磁盘的操作。
  • 当设置该参数为 1 时,表示每次事务提交时,都将缓存在 redo log buffer 里的 redo log 直接持久化到磁盘,这样可以保证 MySQL 异常重启之后数据不会丢失。
  • 当设置该参数为 2 时,表示每次事务提交时,都只是缓存在 redo log buffer 里的 redo log 写到 redo log 文件,注意写入到「 redo log 文件」并不意味着写入到了磁盘,因为操作系统的文件系统中有个 Page Cache(如果你想了解 Page Cache,可以看这篇 (opens new window)),Page Cache 是专门用来缓存文件数据的,所以写入「 redo log文件」意味着写入到了操作系统的文件缓存。

redo log 包括两部分:一个是内存中的日志缓冲 (redo log buffer),另一个是磁盘上的日志文件 (redo log file)。mysql 每执行一条 DML 语句,都会先将记录写入 redo log buffer(3 种策略都会先写入 Redolog Buffer) ,后续某个时间点再一次性将多个操作记录写到 redo log file%%
▶5.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230316-0950%%
❕ ^avp3mz
在计算机操作系统中,用户空间 (user space) 下的缓冲区数据一般情况下是无法直接写入磁盘的,中间必须经过操作系统内核空间 (kernel space) 缓冲区 (OS Buffer)。因此,redo log buffer 写入 redo log file 实际上是先写入 OS Buffer,然后再通过系统调用 fsync() 将其刷到 redo log file 中,过程如下:

image.png

mysql 支持三种将 redo log buffer 写入 redo log file 的时机,可以通过 innodb_flush_log_at_trx_commit` 参数配置,各参数值含义如下:❕%%
▶2.🏡⭐️◼️3 种 Redolog 刷盘策略的区别 ?🔜MSTM📝 相同点:都会写入 RedologBuffer。不同点:commit 之后到达的位置 0 和 2 都需要一个缓冲,0 commit 到 RedologBuffer 作为缓冲,然后从 RedologBuffer 中每秒写入 oscache 紧接着刷入磁盘;2 commit 到 oscache 作为缓冲,然后从 oscache 中每秒刷入磁盘。而策略 1,写入 RedologBuffer 之后,紧接着写入 oscache、刷入磁盘,相当于直接 commit 到了磁盘◼️⭐️-point-20230301-1131%%

image.png

image.png

image-20211010192825378

https://blog.csdn.net/weixin_40471676/article/details/119732738

总结: 由于磁盘是一种相对较慢的存储设备,内存与磁盘的交互是一个相对较慢的过程 由于 innodb_log_file_size 定义的是一个相对较大的值,正常情况下,由前面两种 checkpoint 刷新脏页到磁盘,在前面两种 checkpoint 刷新脏页到磁盘之后,脏页对应的 redo log 空间随即释放,一般不会发生 Async/Sync Flush checkpoint。同时也要意识到,为了避免频繁低发生 Async/SyncFlush checkpoint,也应该将 innodb_log_file_size 配置的相对较大一些。

4.8. 两个日志文件

刚刚说的 redo log 是执行引擎层的 log 文件,我们都知道,MySQL 整体来看,分为 Server 层和引擎层,而 binlog 是 Server 层面的 log 文件,也就是所有执行引擎都有 binlog

为什么有了 binlog, 还要有 redo log?

这个问题跟 MySQL 的时间线有关系。

最开始 MySQL 里并没有 InnoDB 引擎,MySQL 自带的引擎是 MyISAM,但是 MyISAM 没有 crash-safe 的能力,binlog 日志只能用于归档。
而 InnoDB 是另一个公司以插件形式引入 MySQL 的,既然只依靠 binlog 是没有 crash-safe 能力的,所以 InnoDB 使用 redo log 来实现 crash-safe 能力。
%%
▶2.🏡⭐️◼️为什么 MySQL 会有 2 个日志文件 ?🔜MSTM📝 ◼️⭐️-point-20230228-1057%%

4.9. 二阶段提交⭐️🔴

image.png

image.png
image.png

image.png
image.png

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

举个例子,假设 id = 1 这行数据的字段 name 的值原本是 ‘jay’,然后执行 UPDATE t_user SET name = 'xiaolin' WHERE id = 1; 如果在持久化 redo log 和 binlog 两个日志的过程中,出现了半成功状态,那么就有两种情况:

  • 如果在将 redo log 刷入到磁盘之后, MySQL 突然宕机了,而 binlog 还没有来得及写入。MySQL 重启后,通过 redo log 能将 Buffer Pool 中 id = 1 这行数据的 name 字段恢复到新值 xiaolin,但是 binlog 里面没有记录这条更新语句,在主从架构中,binlog 会被复制到从库,由于 binlog 丢失了这条更新语句,从库的这一行 name 字段是旧值 jay,与主库的值不一致性;
  • 如果在将 binlog 刷入到磁盘之后, MySQL 突然宕机了,而 redo log 还没有来得及写入。由于 redo log 还没写,崩溃恢复以后这个事务无效,所以 id = 1 这行数据的 name 字段还是旧值 jay,而 binlog 里面记录了这条更新语句,在主从架构中,binlog 会被复制到从库,从库执行了这条更新语句,那么这一行 name 字段是新值 xiaolin,与主库的值不一致性;

可以看到,在持久化 redo log 和 binlog 这两份日志的时候,如果出现半成功的状态,就会造成主从环境的数据不一致性。这是因为 redo log 影响主库的数据,binlog 影响从库的数据,所以 redo log 和 binlog 必须保持一致才能保证主从数据一致。

MySQL 为了避免出现两份日志之间的逻辑不一致的问题,使用了「两阶段提交」来解决,两阶段提交其实是分布式事务一致性协议,它可以保证多个逻辑操作要不全部成功,要不全部失败,不会出现半成功的状态。

两阶段提交把单个事务的提交拆分成了 2 个阶段,分别是「准备(Prepare)阶段」和「提交(Commit)阶段」,每个阶段都由协调者(Coordinator)和参与者(Participant)共同完成。注意,不要把提交(Commit)阶段和 commit 语句混淆了,commit 语句执行的时候,会包含提交(Commit)阶段。

4.9.1. 解析

%%
▶3.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230304-0804%%

https://cloud.tencent.com/developer/article/1790507

https://xiaolincoding.com/mysql/log/how_update.html#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81%E4%B8%A4%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4

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

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

image.png

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

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

4.9.2. 如何判断 binlog 和 redolog 是否达成了一致

https://www.modb.pro/db/425141

当 MySQL 写完 redo log 并将它标记为 prepare 状态时,并且会在 redo log 中记录一个 XID,它全局唯一的标识着这个事务。而当你设置 sync_binlog=1 时,做完了上面第一阶段写 redo log 后,mysql 就会对应 binlog 并且会直接将其刷新到磁盘中。

下图就是磁盘上的 row 格式的 binlog 记录。binlog 结束的位置上也有一个 XID。
只要这个 XID 和 redo log 中记录的 XID 是一致的,MySQL 就会认为 binlog 和 redo log 逻辑上一致。就上面的场景来说就会 commit,而如果仅仅是 redo log 中记录了 XID,binlog 中没有,MySQL 就会 RollBack

对于处于 PREPARE 状态的事务,存储引擎既可以提交,也可以回滚,这取决于目前该事务对应的 binlog 是否已经写入硬盘。这时就会读取最后一个 binlog 日志文件,从日志文件中找一下有没有该 PREPARE 事务对应的 xid 记录,如果有的话,就将该事务提交,否则就回滚好了。

image.png

4.9.3. 事务没提交的时候,redo log 会被持久化到磁盘吗

会的!
事务执行中间过程的 redo log 也是直接写在 redo log buffer 中的,这些缓存在 redo log buffer 里的 redo log 也会被「后台线程」每隔一秒一起持久化到磁盘。

也就是说,事务没提交的时候,redo log 也是可能被持久化到磁盘的

有的同学可能会问,如果 mysql 崩溃了,还没提交事务的 redo log 已经被持久化磁盘了,mysql 重启后,数据不就不一致了?

放心,这种情况 mysql 重启会进行回滚操作,因为事务没提交的时候,binlog 是还没持久化到磁盘的。

redo log 可以在事务没提交之前持久化到磁盘,但是 binlog 必须在事务提交之后,才可以持久化到磁盘。

4.9.4. 存在问题

两阶段提交虽然保证了两个日志文件的数据一致性,但是性能很差,主要有两个方面的影响:

  • 磁盘 I/O 次数高:对于“双 1”配置,每个事务提交都会进行两次 fsync(刷盘),一次是 redo log 刷盘,另一次是 binlog 刷盘。
  • 锁竞争激烈:两阶段提交虽然能够保证「单事务」两个日志的内容一致,但在「多事务」的情况下,却不能保证两者的提交顺序一致,因此,在两阶段提交的流程基础上,还需要加一个锁来保证提交的原子性,从而保证多事务的情况下,两个日志的提交顺序一致

为什么两阶段提交的磁盘 I/O 次数会很高?

binlog 和 redo log 在内存中都对应的缓存空间,binlog 会缓存在 binlog cache,redo log 会缓存在 redo log buffer,它们持久化到磁盘的时机分别由下面这两个参数控制。一般我们为了避免日志丢失的风险,会将这两个参数设置为 1:

  • sync_binlog = 1 的时候,表示每次提交事务都会将 binlog cache 里的 binlog 直接持久到磁盘;
  • innodb_flush_log_at_trx_commit = 1 时,表示每次事务提交时,都将缓存在 redo log buffer 里的 redo log 直接持久化到磁盘;

可以看到,如果 sync_binlog 和 当 innodb_flush_log_at_trx_commit 都设置为 1,那么在每个事务提交过程中, 都会 至少调用 2 次刷盘操作,一次是 redo log 刷盘,一次是 binlog 落盘,所以这会成为性能瓶颈。

为什么锁竞争激烈?

在早期的 MySQL 版本中,通过使用 prepare_commit_mutex 锁来保证事务提交的顺序,在一个事务获取到锁时才能进入 prepare 阶段,一直到 commit 阶段结束才能释放锁,下个事务才可以继续进行 prepare 操作。

通过加锁虽然完美地解决了顺序一致性的问题,但在并发量较大的时候,就会导致对锁的争用,性能不佳。

4.10. redolog 与 binlog 区别⭐️🔴

%%
▶14.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230305-1228%%
❕ ^63ep3q

redo logbinlog
文件大小redo log 的大小是固定的。binlog 可通过配置参数 max_binlog_size 设置每个 binlog 文件的大小。
实现方式redo log 是 InnoDB 引擎层实现的,并不是所有引擎都有。binlog 是 Server 层实现的,所有引擎都可以使用 binlog 日志
记录方式redo log 采用循环写的方式记录,当写到结尾时,会回到开头循环写日志。binlog 通过追加的方式记录,当文件大小大于给定值后,后续的日志会记录到新的文件上
适用场景redo log 适用于崩溃恢复 (crash-safe)binlog 适用于主从复制和数据恢复

binlogredo log 的区别可知:binlog 日志只用于归档,只依靠 binlog 是没有 crash-safe 能力的。但只有 redo log 也不行,因为 redo logInnoDB
特有的,且日志上的记录落盘后会被覆盖掉。因此需要 binlogredo log
二者同时记录,才能保证当数据库发生宕机重启时,数据不会丢失

  • redo log 是 InnoDB 引擎特有的;而 binlog 是 MySQL Server 层实现的
  • redo log 是物理日志,记录的是“在某个数据页做了什么修改”;而 binlog 是逻辑日志,记录的是语句的原始逻辑。比如 update T set c=c+1 where ID=2; 这条 SQL,redo log 中记录的是 :xx页号,xx偏移量的数据修改为xxx; binlog 中记录的是:id = 2 这一行的 c 字段 +1
  • redo log 是循环写的,固定空间会用完;binlog 可以追加写入,一个文件写满了会切换到下一个文件写,并不会覆盖之前的记录
  • 记录内容时间不同,redo log 记录事务发起后的 DML 和 DDL 语句;binlog 记录commit 完成后的 DML 语句和 DDL 语句
  • 作用不同,redo log 作为异常宕机或者介质故障后的数据恢复使用;binlog 作为恢复数据使用,主从复制搭建。

image.png

5. Undolog

5.1. 记录内容 -DDL 和 DML

DML 和 DDL

数据库事务四大特性中有一个是 原子性 ,具体来说就是 原子性是指对数据库的一系列操作,要么全部成功,要么全部失败,不可能出现部分成功的情况
实际上, 原子性 底层就是通过 undo log 实现的。undo log 主要记录了数据的逻辑变化,比如一条 INSERT 语句,对应一条 DELETEundo log,对于每个 UPDATE 语句,对应一条相反的 UPDATEundo log,这样在发生错误时,就能回滚到事务之前的数据状态。同时,undo log 也是 MVCC (多版本并发控制) 实现的关键

5.2. 记录时机 - commit 数据页之前

undo log 是一种用于撤销回退的日志。在事务没提交之前,确切来说是在 Buffer Pool 中的数据修改之前,MySQL 会先记录更新前的数据到 undo log 日志文件里面,当事务回滚时,可以利用 undo log 来进行回滚。如下图:

image.png

5.3. 作用 - 回滚 + MVCC

  • 实现事务回滚,保障事务的原子性。事务处理过程中,如果出现了错误或者用户执行了 ROLLBACK 语句,MySQL 可以利用 undo log 中的历史数据将数据恢复到事务开始之前的状态。
  • 实现 MVCC(多版本并发控制)关键因素之一。MVCC 是通过 ReadView + undo log 实现的。undo log 为每条记录保存多份历史数据,MySQL 在执行快照读(普通 select 语句)的时候,会根据事务的 Read View 里的信息,顺着 undo log 的版本链找到满足其可见性的记录。

undo log 和 redo log 也是引擎层的 log 文件,undo log 提供了回滚和多个行版本控制(MVCC)功能。在数据库修改操作时,不仅记录了 redo log,还记录了 undo log,如果因为某些原因导致事务执行失败回滚了,可以借助 undo log 进行回滚。

虽然 undo log 和 redo log 都是 InnoDB 特有的,但 undo log 记录的是逻辑日志,redo log 记录的是物理日志。对记录做变更操作(insert,update,delete)时不仅会产生 redo 记录,也会产生 undo 记录要把回滚时需要的信息都记录到 undo log 里,比如:

  • 插入 一条记录时,要把这条记录的主键值记下来,这样之后回滚时只需要把这个主键值对应的记录 删掉 就好了;
  • 删除 一条记录时,要把这条记录中的内容都记下来,这样之后回滚时再把由这些内容组成的记录 插入 到表中就好了;
  • 更新 一条记录时,要把被更新的列的旧值记下来,这样之后回滚时再把这些列 更新为旧值 就好了。

而多版本并发控制(MVCC) ,也用到了 undo log ,当读取的某一行被其他事务锁定时,它可以从 undo log 中获取该行记录以前的数据是什么,从而提供该行版本信息,让用户实现非锁定一致性读取。

5.4. 大小和存储逻辑

undo 记录默认被记录到系统表空间(ibdata1)中,但是从 MySQL5.6 开始,就可以使用独立的 undo 表空间了。不用担心 undo 会把 ibdata1 文件弄大。

undo log 是采用段 (segment) 的方式来记录的,每个 undo 操作在记录的时候占用一个 undo log segment

rollback segment 称为回滚段,每个回滚段中有 1024 个 undo log segment,在以前的版本中,只支持一个 rollback segment,也就是只能记录 1024 个 undo log segment,MySQL 5.5 以后,可以支持 128 个 rollback segment,即支持 128✖️1024 个 undo 操作,还可以通过变量 innodb_undo_logs 自定义 rollback segment 数量,默认是 128

https://www.51cto.com/article/720454.html
https://juejin.cn/post/7157956679932313608#heading-1

5.5. redolog 和 undolog 区别

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

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

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

image.png

6. update 操作流程

update T set c=c+1 where ID=2;

1、执行器先通过引擎查询到 id = 2 这行数据,id 是主键,直接遍历主键索引树直接插到这行数据,如果这行数据所在的数据页在内存中,就直接返回结果给执行器,否则,需要先从磁盘读入内存,然后再返回。

2、执行器拿到引擎给的行数据,把这个值 +1,得到新的一行数据,再调用引擎接口写入这行数据,发起事务

3、引擎将这行数据更新到内存中,同时记录到 redo log 中,此时 redo log 处于 perpare 状态,此时就告知执行器已经更新完成了,随时可以提交事务。

4、执行器生成这个操作的 binlog,并把 binlog 写入 page cache。

5、提交事务:执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,把 binlog 写入磁盘,更新完成

如下图为 update 语句的执行流程,深色代表 MySQL 执行器中执行的,浅色代表 InnoDB 内部执行的。
image.png
%%
▶2.🏡⭐️◼️不用纠结 redolog 的 prepare 和 commit 是在 redolog Buffer 中还是在磁盘中。因为这个是通过参数可变的。关键就在于 redolog 这两种状态的变化时机和原因。按推断来讲,prepare 时还没有提交,所以应该在内存。而 commit 状态就是根据参数来确定。◼️⭐️-point-20230302-0748%%

image.png

7. 总结

%%
▶57.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230304-1846%%
❕ ^hyl1t6

image.png

redo log 用来保证 crash-safe,binlog 用来保证可以将数据库状态恢复到任一时刻,undo log 是用来保证事务需要回滚时数据状态的回滚和 MVCC 时,记录各版本数据信息。

8. 实战经验

8.1. MySQL 磁盘 I/O 很高,有什么优化的方法?

现在我们知道事务在提交的时候,需要将 binlog 和 redo log 持久化到磁盘,那么如果出现 MySQL 磁盘 I/O 很高的现象,我们可以通过控制以下参数,来 “延迟” binlog 和 redo log 刷盘的时机,从而降低磁盘 I/O 的频率:

  • 设置组提交的两个参数: binlog_group_commit_sync_delay 和 binlog_group_commit_sync_no_delay_count 参数,延迟 binlog 刷盘的时机,从而减少 binlog 的刷盘次数。这个方法是基于“额外的故意等待”来实现的,因此可能会增加语句的响应时间,但即使 MySQL 进程中途挂了,也没有丢失数据的风险,因为 binlog 早被写入到 page cache 了,只要系统没有宕机,缓存在 page cache 里的 binlog 就会被持久化到磁盘。
  • sync_binlog 设置为大于 1 的值(比较常见是 100~1000),表示每次提交事务都 write,但累积 N 个事务后才 fsync,相当于延迟了 binlog 刷盘的时机。但是这样做的风险是,主机掉电时会丢 N 个事务的 binlog 日志。
  • innodb_flush_log_at_trx_commit 设置为 2。表示每次事务提交时,都只是缓存在 redo log buffer 里的 redo log 写到 redo log 文件,注意写入到「 redo log 文件」并不意味着写入到了磁盘,因为操作系统的文件系统中有个 Page Cache,专门用来缓存文件数据的,所以写入「 redo log 文件」意味着写入到了操作系统的文件缓存,然后交由操作系统控制持久化到磁盘的时机。但是这样做的风险是,主机掉电的时候会丢数据。

9. 参考与感谢

https://segmentfault.com/a/1190000023827696

https://www.cnblogs.com/renolei/p/5325435.html

https://www.51cto.com/article/681113.html

https://xiaolincoding.com/mysql/log/how_update.html#%E4%B8%A4%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4%E7%9A%84%E8%BF%87%E7%A8%8B%E6%98%AF%E6%80%8E%E6%A0%B7%E7%9A%84

https://juejin.cn/post/7157956679932313608#heading-1

动画: https://heapdump.cn/article/3890459