MySQL持久化不为人知的一面⭐️卡顿现象的根源与对策

MySQL持久化不为人知的一面⭐️卡顿现象的根源与对策

2024新年新气象,小菜同学又踏上了求职之路,但求职路艰辛,新年第一次面试又被面试官给问住了

面试官:你有没有遇到过因为持久化,把线程的查询、修改请求卡住的情况?

小菜(得意的笑,还想给我挖坑):持久化时写redo log的,利用写redo log的顺序性来提升性能,避免随机IO,因此不会卡住其他线程的请求的

...

面试官:好,那我们今天的面试就到这里吧

经历本次面试,小菜同学又重新整理缓冲池、持久化相关的知识点终于搞懂卡顿的根源和对策

文章导图如下:

MySQL持久化.png

缓冲池

缓冲池的组成

缓冲池是一块内存区域,用于将磁盘中的页加载到内存,加快访问速度

当访问数据页时需要先判断页是否在缓冲池中,如果不在则需要从磁盘加载到缓冲池(内存)中

那如何判断某个页是否存在于缓冲池中呢?难度去遍历吗?(遍历是不可能遍历的,时间复杂度太高)

实际是通过Key:表空间 + 页号 Value:页 的方式建立散列表,达到O(1)的查找速度

数据页被加载到缓冲池后称为缓存页,每个缓存页对应一个控制块,控制块上记录数据页的相关信息

缓存页和对应的控制块组成chunk,chunk是申请连续空间的基本单位(当这片空间被缓存页、控制块没占满时还会剩下碎片)

image-20240202092434753.png

为了避免扩容时重新分配内存,还要将数据从旧的空间迁移到新的空间,使用chunk进行扩容

访问缓冲池的线程会加锁,如果并发量大且只有一个缓冲池,开销会很大

使用分段锁的思想:将一个缓冲池分为多个实例,每个实例相当于有一把锁(页hash到实例),每个实例存在数个chunk

image-20240202094123297.png

调整缓冲池参数如下:

  1. 使用 innodb_buffer_pool_instances 调整缓冲池实例的数量
  2. 使用 innodb_buffer_pool_chunk_size 设置每个实例中的chunk数量
  3. 使用 innodb_buffer_pool_size 规定缓冲池大小,并且其值必须是 innodb_buffer_pool_instanesinnodb_buffer_pool_chunk_size 的倍数
链表管理

缓存页有三种状态:

  1. 空闲:当还未从磁盘加载数据页时,缓存页是空闲的
  2. 已使用页干净:当从磁盘加载数据页到缓冲池时,对应缓存页被占用,但未在页上进行写操作(页不脏)
  3. 已使用脏页:当有写操作对页中某些记录进行修改时,页并不会立马写回磁盘(这样开销太大),而是通过写redo log的形式保证持久化(后文再说),这种被修改但未写回磁盘的页称为脏页

使用不同的链表管理控制块(对应缓存页):

  1. 空闲链表:管理空闲缓存页的控制块
  2. 脏页链表:管理脏页缓存页的控制块

注意:链表管理控制块相当于管理对应的缓存页

缓冲池的容量始终是有限的,当缓冲池满时需要将命中不高的页换出,将需要的页换进缓冲池

因此为了提升缓存命中率,使用LRU链表(LRU算法)管理缓存页

当一个页被查询时,LRU算法无论页是否存在都会放到链表头,如果链表已满,则将最后一个节点移除

这种场景下,如果进行范围扫描(页数量多),将会把大量页移除链表,全表扫描场景下情况会更糟糕,这会大大降低缓存命中率

为了避免以上场景发生,MySQL对LRU算法进行优化:

  1. 将链表分为冷(old)热(young)数据区,初次访问的页只放到old区的头部

    使用 innodb_old_blocks_pct 规定old区占比 (默认37%)

  2. 全表扫描可能多次访问同一页,所以在规定时间内多次访问某页,不会把它对应控制块放到young区头部

    使用 innodb_old_blocks_time 规定该时间ms (默认1000ms)

  3. 如果页对应控制块就在young头部附近就不移动(规定在1/4)

image-20240202100033340.png

注意:LRU链表中也可能存储脏页

持久化

redo log

在聊脏页刷新前需要先搞懂innodb如何持久化

redo log是Innodb存储引擎用于持久化、奔溃恢复的重要日志

前文说过,当数据页遇到写操作变成脏页时需要写入磁盘进行持久化

如果对每一条记录都这么做,遇到一个写操作就写入磁盘,而且写回磁盘时,由于页的无序此时会是随机IO,开销非常大

如果想要存一段时间,等该页的脏记录多了再同时刷盘性价比会高一些,但是如果该期间宕机了,那岂不是会发生数据丢失?(因为此时还没刷入磁盘)

为了防止数据丢失,在宕机时能够进行数据恢复,使用redo log记录页中修改的数据并以顺序写入的方式进行IO(顺序IO)

当脏页被真正刷入磁盘后,对应的redo log就没有用了,因此redo log被设计成环形文件,以覆盖的方式进行追加日志

redo log通常以ib_logfile 0...x命名(末尾为0-x)

image-20240202102105004.png

使用 innodb_log_group_home_dir 查看redo log文件所在位置

使用 innodb_log_files_in_group 设置redo log文件数,多个文件数串联形成环形文件

使用 innodb_log_file_size 规定每个redo log文件大小

通过这两个参数可以设置redo log文件的大小

当数据页变成脏页时,会往redo log buffer(缓冲区)上写redo log

由于每个事务中存在SQL,每个SQL都可能对应多个redo log,在往redo log buffer写redo log时,可能涉及到多个事务的redo log交替进行写

image-20240202110101323.png

在进行redo log的刷盘时,会先将数据写入OS的page cache(write),然后根据参数配置 innodb_flush_log_at_trx_commit 不同时机刷入磁盘(fsync)

image-20240202114207840.png

默认下参数为1,事务提交时会进行fsync将redo log刷入磁盘

当参数为0时,由后台线程进行write再fsync,吞吐量最高,宕机时会丢数据

当参数为2时,事务提交时只进行write写到OS的page cache,吞吐量也不错,但OS宕机时也会丢数据

bin log

redo log是Innodb(物理)奔溃恢复的日志,MySQL还存在逻辑恢复的日志binlog,binlog还用于主从复制

bin log的刷盘与redo log也是类似的,先进行write写到OS的page cache(过程快),再进行fsync刷入磁盘(慢)

可以使用 sync_binlog 控制binlog刷盘时机,类似redo log的 innodb_flush_log_at_trx_commit

默认下参数为1,事务每次提交后进行fsync刷入磁盘

当参数为0时,只write不fsync,由OS接管刷盘时间,吞吐量大,可能丢失数据

当参数为X时,经历X事务提交后进行fsync刷盘

在刷盘的过程中为了保证数据的一致性,在redo log刷盘的同时会对bin log一起刷盘

使用XA事务的两阶段提交:

  1. redo log prepare (write):redo log从缓冲区写入OS page cache
  2. bin log write:bin log从缓冲区写入OS page cache
  3. redo log prepare (fsync):redo log从page cache刷入磁盘
  4. bin log fsync:bin log从page cache刷入磁盘
  5. redo log commit:刷盘完成,持久化完成

注意:每个事务的redo log是交替写入buffer的,每次提交事务时可以把其他事务的redo log刷入磁盘(组提交)

崩溃回复时的判断:

  1. 如果redo log 是commit(已完成第五步)那么直接恢复数据
  2. 如果redo log 是prepare(未完成第五步),查看binlog是否完整;如果binlog完整(已完成第四步:bin log fsync)说明redo log、bin log都完成刷盘可以恢复数据,否则不恢复

为啥要设计成这样呢?

如果先写完redo log宕机没写bin log,那么主机会通过redo log恢复数据,而从机需要通过binlog恢复数据,此时binlog不存在就会导致数据不一致

如果先写完bin log宕机没写redo log,那么主机就无法通过redo log恢复数据,从而导致数据不一致

double write

在持久化的过程中还存在double write两次写

如果你理解redo log持久化的过程,是不是想说:两次写就是先写redo log再写数据页,分两次刷入磁盘

其实不是的,这里的两次写代表着数据页会分为两次写入磁盘,使用redo log恢复数据需要基于页的完整性,那在页还未刷入磁盘时如何保证页的完整性呢?

思想与redo log类似,通过先顺序写数据页的方式保证~(顺序IO代替随机IO)

checkpoint

将redo log刷入磁盘后,等待后续线程将对应的脏页刷入磁盘后,该redo log就可以被覆盖了

但是如何判断环形redo log可被覆盖呢?

在redo log上记录一些lsn(Log Sequeue Number),lsn是自增的(文件环形达到最大后又从起点开始)

lsn:标识写redo log序列号位置

flushed_to_disk_lsn:标识redo log刷入磁盘序列号位置

checkpoint_lsn:标识checkpoint推动到序列号的位置(可覆盖的位置)

后台线程会定期checkpoint推动可覆盖redo log的标记,每次进行checkpoint更新checkpoint_lsn的位置(更新可覆盖的redo log)

image-20240202142416028.png lsn与flushed_to_disk_lsn 之间的redo log是没有刷入磁盘的

flushed_to_disk_lsn与checkpoint_lsn之间的redo log是刷入磁盘的(但是它们对应的数据页可能有的被刷盘,有的没刷盘)

checkpoint_lsn前的redo log表示可覆盖的(对应数据页已经刷盘)

脏页刷新

知道MySQL的持久化机制后,再来看持久化时为啥会卡顿?

写操作太多,很多页没有刷盘,导致redo log占满,此时触发checkpoint将脏页刷入磁盘,空出可覆盖的redo log

又或者是缓冲池已满,要换进新的页时,会将old区末尾的页换出,如果该页是脏页,则又要进行刷盘

除了这种场景外还会有线程定时刷新、关闭前把脏页刷入磁盘等

当发生这种场景时,会暂停用户线程去进行刷盘操作从而造成阻塞(类似于JVM中的GC)

因此我们应该减低这种场景的发生,可以通过调整参数或升级磁盘等多方面实现

当前参数最好经过测试让DBA去调整,总结一下对应的参数

  • 缓冲池

    • 使用 innodb_buffer_pool_instances 调整缓冲池实例的数量
    • 使用 innodb_buffer_pool_chunk_size 设置每个实例中的chunk数量
    • 使用 innodb_buffer_pool_size 规定缓冲池大小,并且其值必须是 innodb_buffer_pool_instanesinnodb_buffer_pool_chunk_size 的倍数
  • LRU算法

    • 使用 innodb_old_blocks_pct 规定old区占比 (默认37%)
    • 使用 innodb_old_blocks_time 规定该时间ms (默认1000ms)
  • redo log

    • 使用 innodb_log_group_home_dir 查看redo log文件所在位置

    • 使用 innodb_log_files_in_group 设置redo log文件数,多个文件数串联形成环形文件

    • 使用 innodb_log_file_size 规定每个redo log文件大小

  • bin log、redo log刷盘策略

    • sync_binlog
    • innodb_flush_log_at_trx_commit
  • 调整io

    • 使用 innodb_io_capacity 调整IO能力(使用磁盘IOPS)
    • 使用 innodb_flush_neighbore (是否刷脏页的邻居页到磁盘,默认是,使用SSD可以关闭)

总结

本篇文章从MySQL的缓冲池开始,总结Innodb中进行持久化的实现原理

缓冲池由数个实例组成,实例由数个chunk组成,chunk由控制块、缓存页组成,每一个缓存页都有一个对应的控制块(缓冲池 -> 实例 -> chunk -> 控制块、缓存页)

缓存页分为空闲、已使用页干净、已使用脏页三种状态,使用空闲链表、脏页链表、LRU链表对缓存页的控制块进行管理

将LRU链表分为冷热数据区,从磁盘加载的页先放到冷数据区,经过一段时间多次读取后再放入热数据区头部,如果在短时间内多次访问一页则不会放入热数据区(防止范围、全表扫描导致缓存命中率降低),如果页就在热数据区头部附近则不会移动到头部(1/4)

使用先写redo log再将脏页刷盘的方式,用顺序IO替代随机IO

redo log 记录数据页修改的数据,用于实现物理上的数据恢复,由于redo log对应的页刷盘后,该redo log相当于无效,因此被设计成环形文件(可覆盖)

在生成redo log时,会将redo log写在redo log buffer缓冲池,由于每个事务可能对应多条redo log,redo log在缓冲池中是被交替写入的

redo log在进行刷盘时,会先从缓冲池写入操作系统的文件缓存page cache(write 快),再刷入磁盘(fsync 慢)

binlog 是MySQL逻辑上的数据恢复日志,在redo log进行刷盘时,为了保证数据一致性,bin log与redo log 基于XA协议使用两阶段提交

redo log 恢复数据基于页的完整性,double write 先让页顺序写到磁盘(保证页的可用),后续脏页再刷入磁盘

checkpoint 将脏页刷入磁盘,更新redo log上的checkpoint lsn(更新redo log 可覆盖范围)

当redo log被写满或缓冲池已满冷数据区末尾是脏页的场景,都会去让脏页刷新,导致用户线程阻塞,对于这种场景应该让DBA调整参数,升级IO能力解决

最后(不要白嫖,一键三连求求拉~)

本篇文章被收入专栏 MySQL进阶之路,感兴趣的同学可以持续关注喔

本篇文章笔记以及案例被收入 gitee-StudyJava、 github-StudyJava 感兴趣的同学可以stat下持续关注喔~

有什么问题可以在评论区交流,如果觉得菜菜写的不错,可以点赞、关注、收藏支持一下~

关注菜菜,分享更多干货,公众号:菜菜的后端私房菜

本文由博客一文多发平台 OpenWrite 发布!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/688404.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024.2.18

使用fgets统计给定文件的行数 #include<stdio.h> #include<string.h> int main(int argc, const char *argv[]) {FILE *fpNULL;if((fpfopen("./test.txt","w"))NULL){perror("open err");return -1;}fputc(h,fp);fputc(\n,fp);fput…

算法练习-赎金信(思路+流程图+代码)

难度参考 难度&#xff1a;中等 分类&#xff1a;哈希表 难度与分类由我所参与的培训课程提供&#xff0c;但需要注意的是&#xff0c;难度与分类仅供参考。且所在课程未提供测试平台&#xff0c;故实现代码主要为自行测试的那种&#xff0c;以下内容均为个人笔记&#xff0c;旨…

数智化转型|两大电网、五大发电2024年重点工作路线图!

2024年1月&#xff0c;两大电网、五大发电集团陆续召开2024年工作会议&#xff0c;明确了2024年工作目标及重点路线图。从工作会议内容来看&#xff0c;不难发现&#xff0c;加快推动数智化转型&#xff0c;建设新型电力系统成为几大集团未来共同发展目标。会议内容如下&#x…

大数据,对于生活的改变

谷歌通过对于疾病的查询量可以预测一个个h1n1病毒的大爆发&#xff0c; 大数据时代对于人的考验 用户的搜索记录就是一种信息&#xff0c;这种信息会满足其基础相关的词条与其有关的词条&#xff08;最为原始的搜索机制&#xff0c;国内的搜索引擎都是采用这种基础原理。&…

从源代码安装 rocSOLVER 并 调试 rocSOLVER 在 Ubuntu 22.04 平台

0, 下载并编译 rocBLAS 的调试版本 sudo apt install python3.10-venv sudo apt install libmsgpack-dev sudo pip install joblibgit clone --recursive https://github.com/ROCm/rocBLAS.git $ cd rocBLAS/ $ ./install.sh -i -g构建时间也不短 1&#xff0c;下载并编译 roc…

【数据结构与算法】手搓JDK底层ArrayList底层 - 动态数组

数组 在介绍数组之前&#xff0c;我们先来看一段chatGPT给出的对于数组描述&#xff1a; 数组&#xff08;Array&#xff09;是一种线性数据结构&#xff0c;它由一组连续的内存空间组成&#xff0c;用来存储相同类型的数据元素。数组具有固定的大小&#xff0c;一旦创建后&a…

分享几个丝滑oled代码

最近一段业余时间在捣鼓esp32&#xff0c;发现对于一个搞diy的来说&#xff0c;它的生态&#xff0c;不管是开发环境、氛围还是可玩度都是独一挡的&#xff0c;国内外基于此的扩展真是太多了&#xff0c;找了几个通过按键/旋钮进行0.96寸OLED控制的案例&#xff0c;超级丝滑&am…

SQL中的各种连接的区别总结

前言 今天主要的内容是要讲解SQL中关于Join、Inner Join、Left Join、Right Join、Full Join、On、 Where区别和用法&#xff0c;不用我说其实前面的这些基本SQL语法各位攻城狮基本上都用过。但是往往我们可能用的比较多的也就是左右连接和内连接了&#xff0c;而且对于许多初学…

JavaSE-02笔记【封装~this和static】

文章目录 1.封装&#xff08;掌握&#xff09;1.1 封装的理解1.2 不封装存在的问题1.3 怎么封装1.4 难点解惑1.5 练习 2. this 和 static2.1 this&#xff08;掌握&#xff09;2.1.1 this是什么2.1.2 this 在实例方法中使用2.1.3 this访问实例变量2.1.4 this扩展①2.1.5 this扩…

量子算法入门——3.狄拉克符号与量子态(2)

2. 光的极化和S-G实验 光的极化&#xff1a;表达出一方向电场的振动方式 S-G实验 银原子内部介绍 S-G实验过程 在炉子中将银原子高温灼烧&#xff0c;高温使得银原子具有极大的动能&#xff0c;从炉口向四周发射出来&#xff0c;炉口前设置两个小门构成两点一线&#xff…

Centos7挂载磁盘

1 查看未挂载的磁盘 命令&#xff1a; fdisk -l红框圈中的即是本次要挂载的磁盘&#xff0c;/dev/vdb 与 /dev/vda 相比&#xff0c;其没有下方的 /dev/vda1 等信息&#xff0c;代表 /dev/vdb 磁盘并没有进行过分区操作&#xff0c;是一个新加的硬盘。 2 对新建的磁盘进行分…

基于SSM的宁夏旅游网站平台(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的宁夏旅游网站平台&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring …

K8S之运用污点、容忍度设置Pod的调度约束

污点、容忍度 污点容忍度 taints 是键值数据&#xff0c;用在节点上&#xff0c;定义污点&#xff1b; tolerations 是键值数据&#xff0c;用在pod上&#xff0c;定义容忍度&#xff0c;能容忍哪些污点。 污点 污点是定义在k8s集群的节点上的键值属性数据&#xff0c;可以决…

Open CASCADE学习|管道建模

​这是用Open CASCADE Technology (OCCT)库来创建一个管道模型的示例。OCCT是一个开源的几何建模库&#xff0c;广泛应用于CAD/CAM/CAE和其他几何建模应用中。 在下面的代码中&#xff0c;首先创建了一些点&#xff0c;并用这些点来构建B样条曲线&#xff0c;进而创建边(Edges…

Java+Vue+MySQL,国产动漫网站全栈升级

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

软件测试经典面试题:如何进行支付功能的测试?

非现金支付时代&#xff0c;非现金支付已经成为了生活不可或缺的一部分&#xff0c;我们只需要一台手机便可走遍全国各地&#xff08;前提是支付宝&#xff0c;微信有钱<00>&#xff09;,那么作为测试人员&#xff0c;支付测试也是非常重要的一环&#xff0c;那么下面我就…

【漏洞复现-通达OA】通达OA swfupload_new存在前台SQL注入漏洞

一、漏洞简介 通达OA(Office Anywhere网络智能办公系统)是由北京通达信科科技有限公司自主研发的协同办公自动化软件,是与中国企业管理实践相结合形成的综合管理办公平台。通达OA为各行业不同规模的众多用户提供信息化管理能力,包括流程审批、行政办公、日常事务、数据统计…

作业2024/2/18

1.思维导图 2.定义一个基类Animal&#xff0c;其中有一个虚函数perform ()&#xff0c;用于在子类中实现不同的表演行为。 #include <iostream>using namespace std; class Animal { private:public:void virtual perform() 0;}; class Tiger:public Animal { private:…

树莓派驱动从认识框架到写驱动

​​​​​​参考文章&#xff1a;详细到吐血 —— 树莓派驱动开发入门&#xff1a;从读懂框架到自己写驱动_树莓派 编译驱动-CSDN博客 目录 一、对驱动的认识 为什么要学习驱动&#xff1f; 设备号的作用 二、基于内核驱动框架编写驱动代码流程 1、根据上层需求&…

ClickHouse从入门到精通(高级)

第1章 Explain查看执行计划 第2章 建表优化 第3章 ClickHouse语法优化规则 第4章 查询优化 第5章 数据一致性(重点) 第6章 物化视图 第7章 MaterializeMySQL引擎 第8章 常见问题排查