这篇文章整理自MySQL官方文档,介绍了8.0在预写式日志上实现上的修改,观点总结如下:

在8.0以前,为了保证flush list的顺序,redo log buffer写入过程需要加锁,无法实现并行,高并发的环境中,会同时有非常多的min-transaction(mtr)需要拷贝数据到Log Buffer,如果通过锁互斥,那么毫无疑问这里将成为明显的性能瓶颈。

为此,从MySQL 8.0开始,设计了一套无锁的写log机制,其核心思路是引入recent_written,允许不同的mtr,同时并发地写Log Buffer的不同位置。

1、写入RedoLog存在性能瓶颈

预写日志 (WAL) 是数据库最重要的组件之一,对数据文件的所有更改都记录在 WAL 中(在 InnoDB 中称为Redo日志),并且推迟将修改的页面刷新(Flushed)到磁盘的时间,同时仍然防止数据丢失。

在写入Redo日志时,数据密集型写入服务的性能受到线程同步的限制会明显下降。在具有多个 CPU 内核和快速存储设备(例如现代 SSD 磁盘)的服务器上测试性能时,这一点尤为明显。

我们需要一种新的设计来解决我们的客户和用户现在和将来面临的问题。通过新设计,我们希望确保它可以与现有 API 一起使用,最重要的是不会破坏 InnoDB 其余部分所依赖的契约,在这些限制条件下,这是一项具有挑战性的任务。

重做日志可以看作是一个生产者/消费者持久化队列。执行更新的用户线程可以看作是生产者,当 InnoDB 必须进行崩溃恢复时,恢复线程就是消费者。  InnoDB服务在预期内正常工作时不需要读取Redo log。

2、多线程写Redo日志的顺序问题

实现具有多个生产者的写日志可扩展模型只是问题的一部分。还有一些 InnoDB 特定的细节也需要起作用。最大的挑战是保持buffer pool中flush list上的脏页要满足按照LSN递增排布。

首先, 一个buffer pool实例维护一个flush list, 由mtr(mini transaction)负责原子的应用对物理页的修改, 因此, mtr是InnoDB对物理文件操作的最小事务单元。 redo log由mtr产生, 通常先写在mtr的cache里, 在mtr提交时, 将cache中的redo log刷入log buffer(公共buffer), 同时递增全局维护的日志编号(LSN, Log Sequence Number)。

随后Mtr负责将修改的脏页(或一列表脏页)加入到flush list上, 且满足flush list上的脏页是按照LSN递增排序的。

在8.0之前的实现中, 我们通过加内部锁log_sys_t::mutex 和 log_sys_t::flush_order_mutex 来保证flush list上页按照写log buffer 拿到的LSN有序。

因此, 8.0前的工作方式如下: 某个mtr将脏页输入flush list时, 会持有锁flush_order_mutex, 这时, 即便另外一个线程A需要添加脏页到其他bp(buffer pool)的flush list, 也必须陷入等待。 这种情况下, 这个线A程通过持有log_sys_t::mutex, 阻塞其他线程写log buffer。 单纯移除这两个锁结构, 会使得flush list中LSN递增的约束不工作

3、解决log buffer上的LSN可能不连续

我们还面临的第二个问题是, 由于各个事务可以交叉拷贝redolog 到 log buffer中, log buffer上的LSN可能存在空洞(如下图所示), 所以log buffer是不可以一口气flush full log buffer。

我们通过跟踪已经完成的写log buffer操作(如下图所示的)来解决第二个问题。

在设计上我们引入一个新的无锁数据结构(元素排列与原先log buffer对应关系如下图)。

数据结构如下图所示。 首先这是一个定长数组, 并且保证数组元素(slot)更新是原子的, 以环形形式复用已经释放的空间(所以是个环形数组啊哈)。 并启用单独的线程负责数组的遍历和空间回收, 线程在遇到空元素(empty slot)时暂停。 因此这个thread中还维护了这个数据结构中的最大可达LSN, 我们假设值为M。

我们引入了这个数据结构的两个变量: recent_written 和 recent_closed。 recent_written 维护写log buffer的完成状态, recent_written中维护的最大LSN, M表示, 所有小于这个M的LSN都已经将它的redo log写入log buffer。 而这个M也是(如果这下crash, 可能会触发的)崩溃恢复的截止位点, 同时也是下一个写log buffer操作的开始位点。

刷log buffer到磁盘和遍历recent_written是交由一个线程完成, 因此对log buffer的内存读写操作通过recent_written上顺序访问元素(slots)形成的屏障保证。

假设当前log buffer和recent_written状态如上图所示, 然后完成了一次buffer log 写, 使得log buffer状态如下图所示。

log buffer状态更新触发特定线程(log_writter)向后扫描recent_written, (如下图)

然后更新它维护的最大可达LSN值(可以保证无空洞的), 写入到变量buf_ready_for_write_lsn (这个变量顾名思义 XD)

我们还引入另一个无锁结构体的变量recent_closed, 用来完成原先log_sys_t::flush_order_mutex锁所作的工作, 用来维护flush list的LSN递增性质。 在介绍实现细节前, 我们还需要解释一些细节来, 才能清晰的阐释图和使用无锁结构维护(flush list/bp)整体的LSN单调。

4、使用CheckPoint保证flush list正确

那么首先, 每个bp中的flush list有专门的内部锁保护。 但是我们已经移除了了锁结构log_sys_t::flush_order_mutex, 这就使得并发写flush list的LSN递增性质保证不了。

虽然如此, flush list正确工作仍然必须满足以下两个原生约束:

  1. Checkpoint - 对于检查点推进的约束: 假设存在脏页P1, LSN = L1, 脏页P2, LSN = L2, 如果L2 > L1, 且P1脏页未落盘, 不允许刷新L2对应的脏页到磁盘。
  2. FLushing - flush list 上刷脏策略约束: 每次flush必须从oldest page(即, page对应的LSN最小)开始。 这个操作保证最早被修改的也最先从flush list更新到磁盘, 同时还向后推进checkpoint_lsn。

新引入的无锁结构recent_closed, 用来跟踪并发写flush list的状态, 同时也维护一个最大LSN, 我们标记为M, 满足, 小于当前LSN的脏页都已经加入到flush list中。

只有在M值与当前线程不那么远的时候, 才能将它的脏页刷flush list。 在线程将脏页写入flush list后, 更新recent_closed中的状态信息。

MySQL 8.0:无锁可扩展的 WAL 设计的更多相关文章

  1. 非阻塞同步算法与CAS(Compare and Swap)无锁算法

    锁(lock)的代价 锁是用来做并发最简单的方式,当然其代价也是最高的.内核态的锁的时候需要操作系统进行一次上下文切换,加锁.释放锁会导致比较多的上下文切换和调度延时,等待锁的线程会被挂起直至锁释放. ...

  2. 【Java并发编程】9、非阻塞同步算法与CAS(Compare and Swap)无锁算法

    转自:http://www.cnblogs.com/Mainz/p/3546347.html?utm_source=tuicool&utm_medium=referral 锁(lock)的代价 ...

  3. CAS(Compare and Swap)无锁算法-学习笔记

    非阻塞同步算法与CAS(Compare and Swap)无锁算法 这篇问题对java的CAS讲的非常透彻! 锁的代价 1. 内核态的锁的时候需要操作系统进行一次上下文切换,加锁.释放锁会导致比较多的 ...

  4. javaweb(三十八)——mysql事务和锁InnoDB(扩展)

    MySQL/InnoDB的加锁分析,一直是一个比较困难的话题.我在工作过程中,经常会有同事咨询这方面的问题.同时,微博上也经常会收到MySQL锁相关的私信,让我帮助解决一些死锁的问题.本文,准备就My ...

  5. MySQL 8.0有什么新功能

    https://mysqlserverteam.com/whats-new-in-mysql-8-0-generally-available/ 我们自豪地宣布MySQL 8.0的一般可用性. 现在下载 ...

  6. MySQL 8.0 新特性梳理汇总

    一 历史版本发布回顾 从上图可以看出,基本遵循 5+3+3 模式 5---GA发布后,5年 就停止通用常规的更新了(功能不再更新了): 3---企业版的,+3年功能不再更新了: 3 ---完全停止更新 ...

  7. 【实战Java高并发程序设计6】挑战无锁算法:无锁的Vector实现

    [实战Java高并发程序设计 1]Java中的指针:Unsafe类 [实战Java高并发程序设计 2]无锁的对象引用:AtomicReference [实战Java高并发程序设计 3]带有时间戳的对象 ...

  8. 【mysql】关于悲观锁

    关于mysql中的锁 在并发环境下,有可能会出现脏读(Dirty Read).不可重复读(Unrepeatable Read). 幻读(Phantom Read).更新丢失(Lost update)等 ...

  9. 实现无锁的栈与队列(5):Hazard Pointer

    两年多以前随手写了点与 lock free 相关的笔记:1,2,3,4,质量都不是很高其实(读者见谅),但两年来陆陆续续竟也有些阅读量了(可见剑走偏锋的技巧是多容易吸引眼球).笔记当中在解决内存释放和 ...

  10. PHP无锁内存nosql---Yac的实战

    无锁内存nosql---Yac的实战 最近在工作使用了yac,所以比较了下Memcache和Yac的高并发读写性能测试,发现Yac要比Memcache快很多(这里没有比较Yac和Apc的性能情况, 不 ...

随机推荐

  1. Flink笔记

    高可用(HA):直白来说就是系统不会因为某台机器,或某个实例挂了,就不能提供服务了.高可用需要做到分布式.负载均衡.自动侦查.自动切换.自动恢复等. 高吞吐: 单位时间内,能传输的数据量,对应指标就是 ...

  2. OpenCV 4.5.2环境配置 + 图片灰度化处理

    一,OpenCV环境配置 注意:以下配置内容为Android开发环境配置好的基础上的OpenCV配置环境 1.官网下载OpenCV的sdk包,下载的是4.5.2的Android版本 Releases ...

  3. 树莓派启动后自动发送本地IP 到指定邮箱

    在 /etc/init.d   目录下建立 GetLocalip.py 文件 #coding: utf-8 import smtplib from email.mime.text import MIM ...

  4. python读取与处理netcdf数据

    netcdf是气候数据中的主流格式,当涉及到大范围的全球数万个格网点数据时,使用python脚本可以较快地读取与处理. import netCDF4 from netCDF4 import Datas ...

  5. Android studio Internet跳转活动

    <?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android=" ...

  6. 数据库之【常用sql语句归纳】

    一.数据库操作: 1.创建数据库 create database dbname; 2.创建库是否存在,不存在就创建 create database if not exists dbname; 3.查看 ...

  7. 解决element-ui表格多重嵌套循环时,添加row-key="id" 出现Duplicate keys detected: 'XXX' 错误的问题

    Duplicate keys detected: 'XXX' 错误,怀疑是多重嵌套循环时 :key="item.id或index" 不能重复的问题,但设置了不同的 key 后并未解 ...

  8. P2212 Watering the Fields S

    题目描述 给定n个点,第i个点的坐标为(xi,yi)(xi​,yi​),如果想连通第i个点与第j个点,需要耗费的代价为两点的距离.第i个点与第j个点之间的距离使用欧几里得距离进行计算,即:(xi-xj ...

  9. IT工具知识-12:RTL8832AU网卡在WIN10更新KB5015807后出现无法正常连接的一种解决方法

    系统配置 硬件配置 使用网卡为Fenvi的FU-AX1800 USB外置网卡(官网驱动同AX1800P) 问题描述 在win10自动更新了KB5015807出现了wifi开机无法自动连接,wifi图标 ...

  10. 采用4-20mA电流的模拟量传输

    工业上常用的总线协议RS232,RS485等,都是传输数字信号的方式.工业上普遍需要测量各类非电物理量,例如温度.压力.速度.角度等,这些都需要转换成模拟量电信号才能传输到几百米外的控制室或显示设备上 ...