Undo 日志用什么存储结构支持无锁并发写入?
发布时间:2022-08-02 12:21:24 所属栏目:云计算 来源:互联网
导读:redo 日志只有崩溃恢复的时候才能派上用场,undo 日志不一样,它承担着多重职责,MySQL 崩溃恢复、以及正常提供服务期间,都有它的身影。 按照使用频次,undo 日志的多重职责如下: 职责 1,为 MVCC 服务,减少读写事务之间的相互影响,提升数据库的并发能力
redo 日志只有崩溃恢复的时候才能派上用场,undo 日志不一样,它承担着多重职责,MySQL 崩溃恢复、以及正常提供服务期间,都有它的身影。 按照使用频次,undo 日志的多重职责如下: 职责 1,为 MVCC 服务,减少读写事务之间的相互影响,提升数据库的并发能力。 职责 2,保证数据库运行过程中的数据一致性。事务回滚时,把事务中被修改的数据恢复到修改之前的状态。 职责 3,保证数据库崩溃之后的数据一致性。崩溃恢复过程中,恢复没有完成提交的事务,并根据事务的状态和 binlog 日志是否写入了该事务的 xid 信息,共同决定事务是提交还是回滚。 undo 日志需要为数据一致性和 MVCC 服务,除了要支持多事务同时写入日志,还要支持多事务同时读取日志。 为了有更好的读写并发性能,它拥有与 redo 日志完全不一样的存储结构。 本文我们就来聊聊 undo 日志的存储结构,它是怎么写入 undo 日志文件的,以及事务二阶段提交过程中和它有关的操作。 本文内容基于 MySQL 8.0.29 源码。 正文 1、 概述 undo 日志的存储结构比较复杂,我们先以倒序的方式来介绍一下存储结构的各个部分,以便大家有个整体了解。 undo log header:一个事务可能产生多条 undo 日志,也可能只产生一条 undo 日志,不管事务产生了多少条 undo 日志,这些日志都归属于事务对应的日志组,日志组由 undo log header 负责管理。 undo 页:undo log header 和 undo 日志都存储于 undo 页中。 undo 段:为了多个事务同时写 undo 日志不相互影响,undo 日志也使用了无锁设计,InnoDB 会为每个事务分配专属的 undo 段,每个事务只会往自己专属 undo 段的 undo 页中写入日志。 一个 undo 段可能会包含一个或多个 undo 页,多个 undo 页会形成 undo 页面链表。 inode:每个 undo 段都会关联一个 inode。 undo 段本身并不具备管理 undo 页的能力,有了 inode 这个外挂之后,undo 段就可以管理段中的 undo 页了。 回滚段:一个事务可能会需要 1 ~ 4 个 undo 段,很多个事务同时执行,就需要很多个 undo 段,这些 undo 段需要有一个地方统筹管理,这个地方就是回滚段。 undo slot:一个回滚段管理着 1024 个 undo 段,每个回滚段的段头页中都有 1024 个小格子,用来记录 undo 段中第一个 undo 页的页号,这个小格子就叫作 undo slot。 重要说明: 一个回滚段管理 1024 个 undo 段,这是基于 innodb_page_size 是 16K(默认值) 的情况,本文涉及到页大小的内容,都以 16K 的页为前提,后面就不再单独说明了。 undo 表空间:一个回滚段能够管理 1024 个 undo 段,看起来已经很多了,假设每个事务都只需要 1 个 undo 段,如果只有一个回滚段也只能支持 1024 个事务同时执行。 对于拥有几十核甚至百核以上 CPU 的服务器来说,这显然会限制它们的发挥。 为了充分发挥服务器的能力,有必要支持更多事务的同时执行,所以就有了 undo 表空间,一个 undo 表空间最多可以支持 128 个回滚段。 不止于此,InnoDB 还能够最多支持 127 个 undo 表空间,这样算起来,所有回滚段总共能够管理的 undo 段数量是:1024 * 128 * 127 = 16646144。 这么多 undo 段,是不是瞬间就有了地主家有余粮的感觉? 看了上面的介绍,相信大家能对 undo 日志有个整体了解,接下来我们就按照这个整体结构,自顶向下来详细介绍其中的每个部分。 2、undo 表空间 一个独立的 undo 表空间对应磁盘上的一个文件。 MySQL 8.0 开始,强制开启了独立的 undo 表空间,支持创建 2 ~ 127 个 undo 表空间,默认数量为 2,可以通过 CREATE UNDO TABLESPACE 增加 undo 表空间,通过 DROP UNDO TABLESPACE 减少 undo 表空间。 每个 undo 表空间都可以配置 1 ~ 128 个回滚段,可以通过系统变量 innodb_rollback_segments 来控制每个 undo 表空间中的回滚段数量,默认值为 128。 每个 undo 表空间中,page_no = 3 的页专门用于保存回滚段的段头页的页号,这个页的类型是 FIL_PAGE_TYPE_RSEG_ARRAY,从 Offset 56 开始,保存着 128 个回滚段的段头页的页号,如下图所示: 3、回滚段 (1)什么是回滚段? InnoDB 中凡是被称为段的东西,都是用来管理数据页的一种逻辑结构。 回滚段也不例外,它也是管理数据页的一种逻辑结构。 回滚段管理了什么页呢? 回滚段有一点点特殊,它只管理一个页,就是回滚段的段头页。 每个回滚段中只有段头页这一个数据页,由此可见,管理数据页并不是它最重要的职责。 在概述小节,我们介绍过,每个回滚段中都有 1024 个 undo slot,可以统筹管理 1024 个 undo 段,这才是回滚段最重要的职责。 基于前面的介绍,我们可以给回滚段下一个定义了:回滚段是一种逻辑结构,它负责段头页的分配,以及管理其中 1024 个 undo slot 对应的 undo 段。 (2)分配回滚段 开启一个读写事务,或者一个只读事务转换为读写事务时,InnoDB 会为事务分配一个回滚段。 默认配置下,2 个 undo 表空间总共有 256 个回滚段,这么多回滚段,就涉及到怎么均衡使用的问题了。 2 个 undo 表空间,在内存是中一个数组,下标分别为 0、1。 每个 undo 表空间中 128 个回滚段,在内存中也是一个数组,下标为 0 ~ 127。 以 undo 表空间下标、回滚段下标组成一个元组,用于表示默认配置下的 256 个回滚段,如下: (0, 0)、(0, 1)、…、(0, 126)、 (0, 127) (1, 0)、(1, 1)、…、(1, 126)、 (1, 127) 分配回滚段的逻辑是按照 undo 表空间、回滚段轮流着来,顺序是这样的: (0, 0)、(1, 0)、(0, 1)、(1, 1)、……、(0, 126)、(1, 126)、(0, 127)、(1, 127) 分配顺序用展示是这样的(按照箭头顺序分配): 每次分配时,都会记录这次分配的是哪个回滚段。 下次再分配时,按照上面的顺序,把最后一次分配的回滚段之后的那个回滚段分配给事务。 InnoDB 中的回滚段,分为普通表回滚段、用户临时表回滚段,前面介绍的是普通表回滚段分配逻辑。 用户临时表只有一个独立 undo 表空间,默认 128 个回滚段,需要分配临时表回滚段时,只要轮流分配就行了。 4、undo slot (1)什么是 undo slot? 每个回滚段的段头页中都有 1024 个小格子,每个小格子就是一个 undo slot,用于记录分配给事务的 undo 段的段头页页号,如下图所示: (2)寻找 undo slot 一条 DML 语句即将要修改数据之前,会先记录 undo 日志。 记录 undo 日志之前,需要先创建一个 undo 段。 undo 段要把自己交给回滚段管理,这需要在回滚段的段头页中找一个 undo slot 占个位。 寻找 undo slot 的过程简单粗暴,从回滚段 1024 个 slot 中的第一个 slot 开始遍历,读取 slot 的值,只要 slot 的值等于 FIL_NULL,就说明这个 slot 没有被别的 undo 段占用,当前 undo 就可以占上这个位置。 FIL_NULL 是 32 位无符号整数的最大值,十六进制表示为 0xFFFFFFFFUL,十进制表示为 4294967295。 如果遍历到最后一个 slot,都没有发现 值 = FIL_NULL 的 slot,那就说明分配给当前事务的回滚段没有可用的 slot 了。 这种情况下,InnoDB 并不会再重新给事务分配一个回滚段,而是直接报错:Too many active concurrent transactions。 (编辑:肇庆站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |