MySQL 8.0:无锁可扩展的 WAL 设计
这篇文章整理自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正确工作仍然必须满足以下两个原生约束:
- Checkpoint - 对于检查点推进的约束: 假设存在脏页P1, LSN = L1, 脏页P2, LSN = L2, 如果L2 > L1, 且P1脏页未落盘, 不允许刷新L2对应的脏页到磁盘。
- 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 设计的更多相关文章
- 非阻塞同步算法与CAS(Compare and Swap)无锁算法
锁(lock)的代价 锁是用来做并发最简单的方式,当然其代价也是最高的.内核态的锁的时候需要操作系统进行一次上下文切换,加锁.释放锁会导致比较多的上下文切换和调度延时,等待锁的线程会被挂起直至锁释放. ...
- 【Java并发编程】9、非阻塞同步算法与CAS(Compare and Swap)无锁算法
转自:http://www.cnblogs.com/Mainz/p/3546347.html?utm_source=tuicool&utm_medium=referral 锁(lock)的代价 ...
- CAS(Compare and Swap)无锁算法-学习笔记
非阻塞同步算法与CAS(Compare and Swap)无锁算法 这篇问题对java的CAS讲的非常透彻! 锁的代价 1. 内核态的锁的时候需要操作系统进行一次上下文切换,加锁.释放锁会导致比较多的 ...
- javaweb(三十八)——mysql事务和锁InnoDB(扩展)
MySQL/InnoDB的加锁分析,一直是一个比较困难的话题.我在工作过程中,经常会有同事咨询这方面的问题.同时,微博上也经常会收到MySQL锁相关的私信,让我帮助解决一些死锁的问题.本文,准备就My ...
- MySQL 8.0有什么新功能
https://mysqlserverteam.com/whats-new-in-mysql-8-0-generally-available/ 我们自豪地宣布MySQL 8.0的一般可用性. 现在下载 ...
- MySQL 8.0 新特性梳理汇总
一 历史版本发布回顾 从上图可以看出,基本遵循 5+3+3 模式 5---GA发布后,5年 就停止通用常规的更新了(功能不再更新了): 3---企业版的,+3年功能不再更新了: 3 ---完全停止更新 ...
- 【实战Java高并发程序设计6】挑战无锁算法:无锁的Vector实现
[实战Java高并发程序设计 1]Java中的指针:Unsafe类 [实战Java高并发程序设计 2]无锁的对象引用:AtomicReference [实战Java高并发程序设计 3]带有时间戳的对象 ...
- 【mysql】关于悲观锁
关于mysql中的锁 在并发环境下,有可能会出现脏读(Dirty Read).不可重复读(Unrepeatable Read). 幻读(Phantom Read).更新丢失(Lost update)等 ...
- 实现无锁的栈与队列(5):Hazard Pointer
两年多以前随手写了点与 lock free 相关的笔记:1,2,3,4,质量都不是很高其实(读者见谅),但两年来陆陆续续竟也有些阅读量了(可见剑走偏锋的技巧是多容易吸引眼球).笔记当中在解决内存释放和 ...
- PHP无锁内存nosql---Yac的实战
无锁内存nosql---Yac的实战 最近在工作使用了yac,所以比较了下Memcache和Yac的高并发读写性能测试,发现Yac要比Memcache快很多(这里没有比较Yac和Apc的性能情况, 不 ...
随机推荐
- Esp32 arduino 编译报 hwcrypto/aes.h: No such file or directory 错误
由于hwcrypto/* 在新版中移动到了 esp32/*. 编译会出现hwcrypto/aes.h: No such file or directory 的错误 解决方案如下: arduino 中E ...
- android studio真垃圾
开发人员写代码就行了,想用你写代码,安装配置费死个劲! 我不是针对你,除了visual studio ,所有的IDE都是垃圾.
- 图像处理|Matlab
图像处理 | Matlab 参考博文: 图像处理-平滑滤波 图像去噪-加性噪声(高斯/椒盐)
- 如何用虚拟机VMware Workstation安装CentOs-7
因为我是先安装虚拟机的,再安装CentOs的.在此建议大家先安装CentOs-7再安装虚拟机,比较方便. 1.首先进入centos官方网站下载,网站如下:https://www.centos.org/ ...
- 后端返回字符流,前端处理进行excel文件导出操作
针对于这种的文件导出,最关键的是响应类型的设置,也就是responseType的设置(responseType:"arraybuffer"或者responseType:" ...
- Nginx系列---【配置文件中静态资源文件禁止通过目录查看】
配置文件中静态资源文件禁止通过目录查看 1.问题 nginx作为文件服务器时,默认是可以通过目录路径查看该目录下的所有文件的,这样很不安全,容易造成静态资源泄露. 2.方案 location /ima ...
- C#和C++差异化对比
这里只记录和C++面向对象的区别,也并无比较成分,只做差异化学习使用. 1. 访问修饰符区别:多了一个Internal:成员仅能被同一个项目中的代码访问. 2. 字段的访问:增加了Get,Set访问器 ...
- Oracle联机备份与恢复
联机备份又称为热备份,在数据库运行的情况下对数据库进行物理备份.进行联机备份,数据库必须运行在归档日志(ARCHIVELOG)模式下. 联机完全备份步骤: 1.设置归档日志模式,创建恢复目录用的表空间 ...
- 重复引入reactor-netty
java.lang.NoClassDefFoundError: reactor/util/retry/Retry at reactor.netty.http.client.HttpClientConn ...
- alia linux
alias lrt='ls -lrt'