• 【3. MySQL日志】


    概述

    事务有4种特性:原子性、一致性、隔离性和持久性。那么事务的四种特性到底是基于什么机制实现呢?

    • 事务的隔离性由 锁机制 实现。
    • 而事务的原子性、一致性和持久性由事务的 redo 日志undo 日志来保证。
      • undo log(回滚日志):是 Innodb 存储引擎层生成的日志,实现了事务中的原子性,主要用于事务回滚和 MVCC。(回滚行记录到某个特定版本,用来保证事务的原子性、一致性。)
      • redo log(重做日志):是 Innodb 存储引擎层生成的日志,实现了事务中的持久性,主要用于掉电等故障恢复;(提供再写入操作,恢复提交事务修改的页操作,用来保证事务 的持久性。)
      • binlog (归档日志):是 Server 层生成的日志,主要用于数据备份和主从复制;

    redo log(引擎层)

    1. 为什么使用redo log

    MySQL是先把磁盘上的数据加载到内存中,在内存中对数据进行修改,再写回到磁盘上。如果此时突然宕机,内存中的数据就会丢失。 怎么解决这个问题? 简单啊,事务提交前直接把数据写入磁盘就行啊。 这么做有什么问题?

    • 只修改一个页面里的一个字节,就要将整个页面刷入磁盘,太浪费资源了。毕竟一个页面16kb大小,你只改其中一点点东西,就要将16kb的内容刷入磁盘,听着也不合理。
    • 毕竟一个事务里的SQL可能牵涉到多个数据页的修改,而这些数据页可能不是相邻的,也就是属于随机IO。显然操作随机IO,速度会比较慢。

    于是,决定采用redo log解决上面的问题。当做数据修改的时候,不仅在内存中操作,还会在redo log中记录这次操作。当事务提交的时候,会将redo log日志进行刷盘(redo log一部分在内存中,一部分在磁盘上)。当数据库宕机重启的时候,会将redo log中的内容恢复到数据库中,再根据undo log和binlog内容决定回滚数据还是提交数据。

    2. redo log 优点

    • redo日志占用的空间非常小,只是记录哪一页修改了啥,修改什么内容是不知道的。体积小,刷盘快,降低刷盘频率。(具体数据是回滚还是提交,再根据undo log和redo log内容决定是回滚数据还是提交数据)
    • redo日志是顺序写入磁盘的,它是一直往末尾进行追加,属于顺序IO。
    • 事务执行过程中,redo log不断记录

    3. redo log 流程

    在这里插入图片描述

    第1步:先将原始数据从磁盘中读入内存中来,修改数据的内存拷贝
    第2步:生成一条重做日志并写入redo log buffer,记录的是数据被修改后的值(此时并不会直接将redo log buffer的内容进行刷盘,而是放在了操作系统的缓冲区中,由操作系统进行系统调用fsync()函数决定什么时候刷盘)
    第3步:当事务commit时,将redo log buffer中的内容刷新到 redo log file,对 redo log file采用追加写的方式
    第4步:定期将内存中修改的数据刷新到磁盘中
    Write-Ahead Log(预先日志持久化):在持久化一个数据页之前,先将内存中相应的日志页持久化(MySQL 的写操作并不是立刻写到磁盘上,而是先写日志,然后在合适的时间再写到磁盘上。)。

    被修改 Undo 页面,需要记录对应 redo log 吗?

    • 开启事务后,InnoDB 层更新记录前,首先要记录相应的 undo log,如果是更新操作,需要把被更新的列的旧值记下来,也就是要生成一条 undo log,undo log 会写入 Buffer Pool 中的 Undo 页面。不过,在内存修改该 Undo 页面后,需要记录对应的 redo log。

    4. 什么时候刷盘

    • 刷盘是指从redo log buffer到redo log file中,只要这个过程不出问题,就不会有事
    • 首先是从redo log buffer刷到 文件系统缓存(page cache)中去,然后由操作系统判断什么时候刷盘。

    InnoDB给出 innodb_flush_log_at_trx_commit 参数,该参数控制 commit提交事务时,如何将 redo log buffer 中的日志刷新到 redo log file 中。它支持三种策略:

    • 设置为0 :表示每次事务提交时不进行刷盘操作。(系统默认master thread每隔1s进行一次重做日
      志的同步)
    • 设置为1 :表示每次事务提交时都将进行同步,刷盘操作( 默认值
    • 设置为2 :表示每次事务提交时都只把 redo log buffer 内容写入 page cache,不进行同步。由os自
      己决定什么时候同步到磁盘文件。

    redo log 文件写满怎么办?

    「重做日志文件组」由有 2 个 redo log 文件组成

    • ib_logfile0 和 ib_logfile1
    • redo log File 设置的上限是 1 GB(总共2GB)
    • 重做日志文件组是以循环写的方式工作的,从头开始写,写到末尾就又回到开头

    流程

    • InnoDB 存储引擎会先写 ib_logfile0 文件,当 ib_logfile0 文件被写满的时候,会切换至 ib_logfile1 文件,当 ib_logfile1 文件也被写满时,MySQL 会被阻塞
    • 会停下来将 Buffer Pool 中的脏页刷新到磁盘中,然后标记 redo log 哪些记录可以被擦除,接着对旧的 redo log 记录进行擦除,等擦除完旧记录腾出了空间,继续执行新的更新操作。

    在这里插入图片描述

    undo log(引擎层)

    在这里插入图片描述

    • redo log是事务持久性的保证,undo log是事务原子性的保证。在事务中 更新数据 前置操作 其实是要先写入一个 undo log

    1. 为什么使用undo log

    事务需要保证原子性,也就是事务中的操作要么全部完成,要么什么也不做。但有时候事务执行到一半会出现一些情况,比如:

    • 情况一:事务执行过程中可能遇到各种错误,比如 服务器本身的错误 , 操作系统错误 ,甚至是突然 断电 导致的错误。
    • 情况二:程序员可以在事务执行过程中手动输入 ROLLBACK 语句结束当前事务的执行。

    以上情况出现,我们需要把数据改回原先的样子,这个过程称之为 回滚 ,这样就可以造成一个假象:这个事务看起来什么都没做,所以符合 原子性 要求。

    一条记录的每一次更新操作产生的 undo log 格式都有一个 roll_pointer 指针和一个 trx_id 事务id:

    • 通过 trx_id 可以知道该记录是被哪个事务修改的;
    • 通过 roll_pointer 指针可以将这些 undo log 串成一个链表,这个链表就被称为版本链;

    2. undo log作用

    作用1:回滚数据,保证事务的原子性

    • undo是逻辑日志,因此只是将数据库从逻辑上恢复到原来的样子,但是物理层面做了一些改变是不会恢复的(例如创建了一个页,里面插入一个数据,然后回滚是删除该条数据,但是整个页是不会删除的)

    作用2:MVCC

    • innodb存储引擎中MVCC的实现是通过undo来完成。当用户读取一行记录时,若该条记录已经被其他事务占用,当前事务可以通过undo读取之前的版本信息,以此实现非锁定读取

    3. undo log 流程

    在这里插入图片描述

    • 行格式有三个隐藏的列:
      • row_id:行ID,唯一标识一条记录(如果没有定义主键,也没有定义唯一索引,那么innodb会自动为表添加一个row_id的隐藏列作为主键)
      • transaction_id:事务ID(每个事务都会分配一个事务ID,当对某条记录发生变更时,就会将这个事务的事务ID写入trx_id中)
      • roll_pointer::回滚指针(本质上是指向undo log的指针)

    在这里插入图片描述

    undo log是如何删除的

    针对于insert undo log

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

    针对于update undo log

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

    总结

    • undo log是逻辑日志,对事务回滚时,只是将数据库逻辑地恢复到原来的样子。
    • redo log是物理日志,记录的是数据页的物理变化,undo log不是redo log的逆过程。

    4. undo log如何刷盘

    • undo log 和数据页的刷盘策略是一样的,都需要通过 redo log 保证持久化。
    • buffer pool 中有 undo 页,对 undo 页的修改也都会记录到 redo log。redo log 会每秒刷盘,提交事务时也会刷盘,数据页和 undo 页都是靠这个机制保证持久化的。

    bin log(数据层)

    • 主要实现主从同步,主机的数据会写到bin log中,从机拿到bin log日志进行同步,实现主从同步。

    binlog即binary log,二进制日志文件,也叫作变更日志(update log)。它记录了数据库所有执行的
    DDLDML 等数据库更新事件的语句,但是不包含没有修改任何数据的语句(如数据查询语句select、show等)。

    binlog主要应用场景:

    • 数据恢复:如果MySQL数据库意外停止,可以通过二进制日志文件来查看用户执行了哪些操作,对数据库服务器文件做了哪些修改,然后根据二进制日志文件中的记录来恢复数据库服务器。
    • 数据复制:由于日志的延续性和时效性,master把它的二进制传递给slaves来达到master-salve数据一致的目的。

    在这里插入图片描述

    写入日志流程

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

    • write,指的就是指把日志写入到 binlog 文件,但是并没有把数据持久化到磁盘,因为数据还缓存在文件系统的 page cache 里,write 的写入速度还是比较快的,因为不涉及磁盘 I/O。

    • fsync,才是将数据持久化到磁盘的操作,这里就会涉及磁盘 I/O,所以频繁的 fsync 会导致磁盘的 I/O 升高。

    • write和fsync的时机,可以由参数 sync_binlog 控制,默认是 0 。为0的时候,表示每次提交事务都只write,由系统自行判断什么时候执行fsync。虽然性能得到提升,但是机器宕机,page cache里面的binglog 会丢失。如下图

    在这里插入图片描述

    binlog与redolog对比

    1.适用对象不同:

    • redo log 它是 物理日志 ,记录内容是“在某个数据页上做了什么修改”,属于 InnoDB 存储引擎层产生的。(真实记录在物理磁盘页的)
    • binlog 是 逻辑日志 ,记录内容是语句的原始逻辑,类似于“给 ID=2 这一行的 c 字段加 1”,属于MySQL Server 层,所有存储引擎都可以使用;

    2.文件格式不同:

    • binlog 有 3 种格式类型,分别是 STATEMENT(默认格式)、ROW、 MIXED
    • redo log 是物理日志,记录的是在某个数据页做了什么修改,比如对 XXX 表空间中的 YYY 数据页 ZZZ 偏移量的地方做了AAA 更新;

    3.用途不同:

    • binlog 用于备份恢复、主从复制;
    • redo log 用于掉电等故障恢复。
    • redo log在事务执行的过程中不断写入,而binlog只有在提交事务时才写入,所以redolog与binlog的写入时机不一样

    4.写入方式不同

    • binlog 是追加写,写满一个文件,就创建一个新的文件继续写,不会覆盖以前的日志,保存的是全量的日志。
    • redo log 是循环写,日志空间大小是固定,全部写满就从头开始,保存未被刷入磁盘的脏页日志。
    两阶段提交
    • 在执行更新语句过程,会记录redo log与binlog两块日志,以基本的事务为单位,redo log在事务执行过程中可以不断写入,而binlog只有在提交事务时才写入,所以redo log与binlog的 写入时机 不一样。就会导致由于binlog没写完就异常,这时候binlog里面没有对应的修改记录。

    在这里插入图片描述

    • 为了解决两份日志之间的逻辑一致问题,InnoDB存储引擎使用两阶段提交方案。
      在这里插入图片描述
    • 使用两阶段提交后,写入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 也没有关系,一样会被认为事务已经执行成功;

    出现异常重启会出现什么现象?

    在这里插入图片描述
    不管是时刻 A(redo log 已经写入磁盘, binlog 还没写入磁盘),还是时刻 B (redo log 和 binlog 都已经写入磁盘,还没写入 commit 标识)崩溃,此时的 redo log 都处于 prepare 状态。

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

    注意:

    • 对于处于 prepare 阶段的 redo log,即可以提交事务,也可以回滚事务,这取决于是否能在 binlog 中查找到与 redo log 相同的 XID
    • 两阶段提交是以 binlog 写成功为事务提交成功的标识,因为 binlog 写成功了,就意味着能在 binlog 中查找到与 redo log 相同的 XID。
    • 事务没提交的时候,redo log 也是可能被持久化到磁盘的。

    两阶段提交带来的问题

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

  • 相关阅读:
    强推这款丝滑酷炫酷的keychron K8键盘
    【云原生】Docker 命令大全之镜像仓库
    Java 19新特性:Structured Concurrency (结构化并发编程)
    技术分享 | JMeter性能测试实现与分析
    深入剖析阻塞式socket的timeout
    重磅推出!CrownCAD 2023全功能试用通道已开启!
    华为云云耀云服务器L实例评测|使用Portainer快速部署物联网低代码平台Node-RED
    TLSF内存管理算法原理详解
    智能DNS云解析的宕机切换是如何实现的?-中科三方
    NTP服务器工作原理
  • 原文地址:https://blog.csdn.net/weixin_45043334/article/details/127708222