MySQL8.0 redo日志系统优化
背景
现在主流的数据库系统的故障恢复逻辑都是基于经典的ARIES协议,也就是基于undo日志+redo日志的来进行故障恢复。redo日志是物理日志,一般采用WAL(Write-Ahead-Logging)机制,所以也称redo日志为wal日志,redo日志记录了所有数据的变更,undo日志是逻辑日志,记录了所有操作的前镜像,方便异常时进行回滚。用户在提交事务时,只要确保写redo日志成功即可,并不需要对应的数据页也实时落盘,这套机制的基本思想是利用空间换时间,用户事务的更新实际上在数据页和redo日志中记录了两份,传统的数据库存储引擎都是基于B+Tree来组织数据页,因此刷数据页是离散小块IO,而写redo是顺序IO,对磁盘介质更友好,而且OLTP场景下,业务对RT(ResponseTime)也比较敏感,所以这套机制非常流行。
redo日志是保证数据不丢的关键因素,而且每个事务在提交时,都需要写redo日志,可想而知这块资源竞争是非常激烈的。这个问题是所有基于WAL机制的数据库系统个的共性问题,下文的讨论以MySQL为例,并以此说明MySQL8.0在这块的优化。
最初的redo日志机制
在MySQL的日志系统中,这里讨论的是InnoDB引擎,mtr(mini-transaction)是最小事务单位,一个用户事务会对应若干个mtr,mtr保证内部操作的原子性,比如B+Tree分裂操作,必需在一个mtr中。用户执行操作时,会同时更新数据页和写redo日志,mtr是redo日志的载体,存在每个会话的私有变量中。mtr提交时,会将本地redo日志拷贝到全局的log_buffer中,为了保证日志有序性,需要加锁来访问log_buffer,这把锁就是log_sys_t::mutex,所以这个锁竞争非常激烈。在这个锁保护下,除了要将本地日志拷贝到全局buffer,还需要将数据页加入了flush_list,供后台线程刷脏,辅助数据库检查点持续往前推进。检查点一方面能控制全局的redo日志文件大小,让日志具备循环复用的能力;另一方面,也能提高故障恢复速度。因为故障恢复的本质就是利用落盘的redo日志来恢复没有落盘的数据页。所以最开始(MySQL5.1)只有一把锁,大并发场景下,这个锁竞争非常激烈,MySQL在多核系统下也无法提升性能。
拆分log_sys_t::mutex
既然锁竞争压力大,那么最直观的想法就是拆锁。首先按功能拆分,刚刚说到,在mutex保护下,做了两件事,一件是拷贝本地日志到全局log_buffer;另一件事是将事务修改的page加入到flush_list。日志系统将这两件事解耦,引入了log_sys_t::flush_order_mutex,减少log_sys_t::mutex的持锁时间。将本地日志拷贝到log_buffer后,就可以释放log_sys_t::mutex,这样拷贝日志的线程和处理flush_list的线程就可以并发起来。
除了这个,日志系统还引入了双log_buffer机制,这个主要是为了解决全局log_buffer的读写并发问题。一个buffer用于拷贝日志到log_buffer,另一个log_buffer则用于读取,写入到日志文件。当需要读log_buffer时,则可以切换log_buffer的角色,这样就消除了写日志文件带来的访问log_buffer锁竞争。
但是,拆分完锁后,多个用户线程仍然需要在log_sys_t::mutex保护下,串行写log_buffer,由于memcpy操作比较重,所以这个锁竞争仍然非常激烈,需要优化。
消除log_sys_t::mutex
为了解决log_sys_t::mutex并发问题,MySQL 8.0引入了新的log_buffer机制,借助于lock_free的link_buf数据结构,利用原子变量来进行预占位,这样多个线程就能并发写redo,这种机制带来了一个空洞问题,因为写日志速度不一样,可能导致后占位(lsn较大)的线程先写完。但是我们写日志线程肯定是不能将带有空洞的buffer写到日志文件,因此会维护一个滑动窗口,即最小的连续的lsn:buf_ready_for_write_lsn。写日志线程会不断的检查 link_buf变量recent_written,然后写日志,推进buf_ready_for_write_lsn。

前面我们提到了系统中有两把锁,log_sys_t::mutex和log_sys_t::flush_order_mutex,通过占位方式,解决了写log_buffer的问题,那么如何解决将脏页有序加入到flush_list的问题呢?MySQL 8.0实现中仍然借助于link_buf数据结构,原来要求是加入flush_list的数据页的oldest_modification_lsn一定是递增的。这里顺便说下oldest_modification_lsn的含义,oldest_modification_lsn是指page第一次被修改后,mtr在log_sys_t中分配的lsn,即使这个page在flush下去之前,又在内存中被修改过N次,仍然以第一次修改的lsn为准,这样做的目的是,确保数据页内存的修改与检查点推进能对应上,避免检查点推进了,但对应的脏页可能还未刷盘,造成数据丢失问题。
由于是并发乱序写log_buffer,那么无法保证按lsn递增有序加入到flush_list,也就无法推进检查点。MySQL 8.0通过限制大于一定阀值L的lsn加入到flush_list做为权衡,假设当前flush_list的lsn最大值为M,那么只有在M值与当前线程lsn相差范围在L以内时,才将脏页写入flush_list。同样的,推进M,也依赖于link_buf变量recent_closed。这种策略本质上放宽了之前对于flush_list中对于LSN全局有序的限制到L范围内的有序。

除了日志系统变成lock free,MySQL8.0还将写日志线程从用户线程中拆分出来,有单独的log writer线程和log flusher后台线程来处理写日志和sync日志。原来的写日志方式是,大家随机group-commit,由一个线程负责write/flush日志,其它线程等待,这种模式下group的大小比较随机。拆分后,处理更灵活,batch大小也更好控制,而且对于flush_log_at_trx_commit!=1的场景,只需要等log writer的通知即可。
总结
从MySQL日志系统优化的演进过程来看,始终是围绕锁log_sys_t::mutex展开。 从最初的按功能拆分出log_sys_t::flush_order_mutex,到按读写拆分实现为双log_buffer,以及最新的MySQL 8.0利用无锁机制彻底去掉这把锁,显然MySQL的并发能力是越来越强的。这种优化“套路”其实是比较朴素通用的,对于一个新的系统,通过一把大锁能简化并发逻辑,优先保证正确性。在系统慢慢演进过程中,我们可以按功能拆分锁,缓解锁冲突压力;如果某把锁处于核心链路,而且又成为瓶颈,那么再想办法继续拆,或者实现为无锁,彻底解决并发冲突问题,目的只有一个就是充分利用多核CPU资源,然线程多干活,减少响应时间的同时,拉高吞吐量,而不是都等待空闲着。文章中并没有涉及更多关于MySQL8.0日志系统优化的细节,官方文档已经写地足够好,大家可以详细看看。
参考文档
https://mysqlserverteam.com/mysql-8-0-new-lock-free-scalable-wal-design/
http://mysql.taobao.org/monthly/2018/07/01/
https://yq.aliyun.com/articles/592215?utm_content=m_49932
MySQL8.0 redo日志系统优化的更多相关文章
- Navicat 12 连接 Mysql8.0 使用日志
目前最新的Mysql8.0 + Navicat12,使用中常有一些棘手问题 解决了的都会贴出来,受益于小伙伴们 我们的目标是发现问题,解决问题,欢迎大家贴出自己使用时遇到的问题,集思广益 好了,上干货 ...
- MySQL8.0错误日志Error log
GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 理论知识 错误日志内容 错误日志包含mysqld启动和关闭的时间信息,还包含诊断消息,如服务器启动和关闭期间以及服务器运行 ...
- 深入解读MySQL8.0 新特性 :Crash Safe DDL
前言 在MySQL8.0之前的版本中,由于架构的原因,mysql在server层使用统一的frm文件来存储表元数据信息,这个信息能够被不同的存储引擎识别.而实际上innodb本身也存储有元数据信息.这 ...
- MySQL-08 MySQL8.0新特性
性能 MySQL 8.0 在一定的用户访问条件下,速度要比 MySQL 5.7 快 2 倍.MySQL 8.0 在以下方面带来了更好的性能:读/写工作负载.IO 密集型工作负载.以及高竞争(" ...
- MySQL8.0新特性实验1
Server层,选项持久化 mysql> show variables like '%max_connections%';+------------------------+-------+| ...
- MySQL8.0.17 - 初探 Clone Plugin
MySQL8.0.17推出了一个重量级的功能:clone plugin.允许用户可以将当前实例进行本地或者远程的clone.这在某些场景尤其想快速搭建复制备份或者在group replication里 ...
- Mysql8.0新特性【详细版本】
1. 账户与安全 用户创建与授权 之前:创建用户并授权 1 grant all privileges on *.* to 'myuser'@'%' identified by '3edc#EDC'; ...
- 万答#18,MySQL8.0 如何快速回收膨胀的UNDO表空间
欢迎来到 GreatSQL社区分享的MySQL技术文章,如有疑问或想学习的内容,可以在下方评论区留言,看到后会进行解答 GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 背 ...
- Mysql8.0修改lower_case_table_names参数导致重启失败
GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. GreatSQL是MySQL的国产分支版本,使用上与MySQL一致. 事件起因:在测试一个数据迁移工具时,源端oracle ...
随机推荐
- iOS渠道分包2种模式之包内注入文件分包
解决问题:商业模式中会存在这样的形式1款app需要不同的运用团队(工会)去分包推广,谁推广的包下载的人数都会在服务器记录,不同渠道的标示唯一来区分. iOS渠道分包模式有两种 一.IDFA模式 IDF ...
- python编程基础之三十六
文件处理:文件处理包括读文件,写文件 读文件: 1.打开文件 2.读取文件 3.关闭文件 写文件: 1.打开文件 2.写如文件 3.关闭文件 无论是读取文件还是写文件都时需要打开文件,和关闭文件 打开 ...
- 复杂模型可解释性方法——LIME
一.模型可解释性 近年来,机器学习(深度学习)取得了一系列骄人战绩,但是其模型的深度和复杂度远远超出了人类理解的范畴,或者称之为黑盒(机器是否同样不能理解?),当一个机器学习模型泛化性能很好时 ...
- 使用物理机安装Linux
现在Linux用的越来越多.之前的使用,要么是云服务器自带的centos 系统环境,只要购买了服务器就可以选择自己想要的系统环境.要么是在Windows中安装虚拟机. 今天,我正式使用公司里闲置 ...
- C语言打印当前所在函数名、文件名、行号
printf("[%s %s] %s: %s: %d\n", \ __DATE__, __TIME__, __FILE__, __func__, __LINE__); 内核驱动中: ...
- ZCU104搭建Ubuntu桌面系统-1安装Petalinux
参考教程: https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18841948/Zynq+UltraScalePlus+MPSoC+-+Ubu ...
- JZOJ5771【NOIP2008模拟】遨游
Description MWH寒假外出旅游,来到了S国.S国划分为N个省,第i个省有Ti座城市,编号分别为Ci1,Ci2,……CiTi(各省城市编号不会重复).所有城市间有M条双向的道路连接 ...
- [JZOJ5456]【NOIP2017提高A组冲刺11.6】奇怪的队列
Description nodgd的粉丝太多了,每天都会有很多人排队要签名.今天有
- 初识AutoIt
1.定义 AutoIt 目前最新是v3版本,这是一个使用类似BASIC脚本语言的免费软件,它设计用于Windows GUI(图形用户界面)中进行自动化操作.它利用模拟键盘按键,鼠标移动和窗口/控件的组 ...
- 【MySQL】事务隔离级别及ACID
注:begin或start transaction并不是一个事务的起点,而是在执行它们之后的第一个操作InnoDB表的语句,事务才真正开始.start transaction with consist ...