mysql使用变量填值为数组_MYSQL无锁化WAL系统

633946ae44b52c2a0c8552a5f58bcbc3.png

概述

数据库系统一般采用WAL(write ahead log)技术来实现原子性和持久性,MYSQL也不例外。WAL中记录事务的更新内容,通过WAL将随机的脏页写入变成顺序的日志刷盘,可极大提升数据库写入性能,因此,WAL的写入能力决定了数据库整体性能的上限,尤其是在高并发时。

在MYSQL 8以前,写日志被保护在一把大锁之下,本来并行事务日志写入被人为串行化处理。虽简化了逻辑,但也极大限制了整体的性能表现。8.0很大的一部分工作便是将日志系统并行化。

日志并行化

日志并行化的思路也很简单:将写日志拆分为两个过程:

  1. 从内存log buffer中为日志预留空间

2. 将日志内容拷贝至1预留的空间

而在这两个步骤中,只需要步骤1保证在多并发并发预留空间时的正确性即可,确保并发线程预留的日志空间不会交叉。一旦预留成功,步骤2各并发线程可互不干扰地执行拷贝至自己的预留空间即可,这天然可并发。

而在步骤1中也可以使用原子变量来代替代价较高锁实行预留,在mysql 8实现中,其实就两行代码:

Log_handle 

可以看到,只需要一个原子变量log.sn记录当前分配的位置信息,下次分配时更新该log.sn即可,非常简洁优雅。

8.0中引入的并行日志系统虽然很美好,但是也会带来一些小麻烦,我们下面会详细描述其引入的日志空洞问题并阐述其解决方案。

Log Buffer空洞问题

Mysql 8.0中使用了无锁预分配的方式可以使MTR并行地将WAL日志写入到Log Buffer,提升性能。但这样势必会带来Redo Log Buffer的空洞问题,如下:

9f38f26c96077e09c294d9b319752f58.png

上图中,3个线程分别分配了对应的redo buffer,线程1和3已经完成了wal日志内容的拷贝,而线程2则还在拷贝中,此时写入线程最多只能将thread-1的redo log写入日志文件。 为此,MySQL 8.0中引入了Link_buf

Link_buf原理

Link_buf用于辅助表示其他数据结构的使用情况,在Link_buf中,如果一个索引位置index处存储的是非0值n,则表示Link_buf辅助标记的那个数据结构,从index开始后面n个元素已被占用。

template 

Link_buf是一个定长数组,且保证数组的每个元素的更新是原子操作的。以环形的方式复用已经释放的空间。

同时Link_buf内部维护了一个变量m_tail表示当前最大可达的LSN。

Innodb日志系统中为Log Buffer维护了两个Link_buf类型的变量recent_writtenrecent_closed。示意图如下:

695811310632e3d953b5c83f8809a891.png

上图中,共有两处日志空洞,起始的LSN为lsn1与lsn3,均有4个字节。而lsn2处的redo log已经写入,共3个字节。在recent_written中,lsn1开始处的4个atomic均是0,lsn3同样如此,而lsn2处开始的存储的则是3,0,0表示从该位置起的3个字节已经成功写入了redo日志。

接下来当lsn1处的空洞被填充后,Link_buf中该处对应的内容就会被设置,如下:

b9d6ba9e65807835193e0bd27bab9df9.png

同理,当lsn3处的空洞也被填充后,状态变成下面这样:

bf7fd93c3ac577cefb1875021c31a3fd.png

Link_buf实现

初始化

bool 

从构造函数中可以看到,LinkBuf内核心成员是一维数组,数组的成员类型是原子类型的Distance(uint64_t),数组成员个数则由创建者决定,如Innodb中为recent_written创建的LinkBuf的数组成员个数为1MB,而为recent_closed创建的LinkBuf的数组成员个数为2MB。

同时,创建完成后会将数组的每个成员初始化为0。

mtr log拷贝完成

mtr在commit时会将其运行时产生的所有redo log拷贝至Innodb全局的redo log buffer,这借助了mtr_write_log_t对象来完成,且每次拷贝按照block为单位进行。需要说明的是:一个mtr中可能存在多个block来存储mtr运行时产生的redo log,每个block拷贝完成后均触发一次Link_buf的更新。

struct 

在这里会找到start_lsn对应的slot,并在该slot内设置值为end_lsn - start_lsn,记录该位置处已写入的内容数量。

log_advance_ready_for_write_lsn

Innodb将redo log buffer内容写入日志文件时需要保证不能存在空洞,即在写入前需要获得当前最大的无空洞lsn。这同样依赖LinkBuf。在后台写日志线程log_writerlog_advance_ready_for_write_lsn函数中完成。

void 

这里的关键在于函数Link_buf::advance_tail_until,即推进Link_buf::m_tail。

bool 

这里的原理也比较简单,可以用下面的图来表示:

d92636047e1c4c3a1bb54efab28c1b1e.png

简单来说,就是从上次尾部位置(m_tail)开始,顺序遍历数组,如果该项不为0,则推进m_tail,否则意味着出现了空洞,就不能再往下推进了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值