面试必须要知道的MySQL知识(上)
欢迎点击关注公众号,利用碎片化时间学习,每天进步一点点。
来源:https://blog.csdn.net/a18602320276/article
/details/122917218
本文跟你来聊聊MySQL一些底层原理知识,由于文章内容较多,所以拆分为好几篇文章,欢迎大家关注公众号,及时获得下文。
1 写在前面的话
2 MySQL 架构设计
2.1 程序是如何跟 MySQL 打交道的
MySQL 作为标准的 C/S 架构,分为客户端和服务端。我们写代码的时候,通常会在代码中引入驱动和 client,然后在配置文件中填写 server 地址和账号密码,用于连接到 MySQL 服务器。大概的流程看起来就向下面一样。
2.2 程序是如何跟 MySQL 打交道的图解
2.3 服务端流程分析
客户端向服务端发送请求并得到回复的过程本质上是一个进程间通信的过程,这个处理连接的过程,MySQL 支持以下 3 种方式:
TCP/IP(端口:3306);
命名管道和共享内存(这个针对于 windows 系统);
Unix 域套接字文件(这个针对于 linux 系统)。
处理连接后,MySQL 会对我们发送的请求进行解析与优化,这个过程大概分为 3 步:
查询缓存;
语法解析;
查询优化。
然后再经过存储引擎,最后持久化。
2.4 服务端流程图解
为了方便大家记忆这个过程,画了下面一张图
3 InnoDB 架构设计
3.1 设计思路
数据需要持久化;
支持的并发不能太低,速度要可以;
一旦宕机,需要尽可能的减少数据的丢失,能够快速恢复数据;
如果某一操作有问题,应该可以快速回滚。
针对第一点,咱们可以将数据写入磁盘中(MySQL 的磁盘文件);
针对第二点,咱们可以考虑先基于内存处理,然后再写入磁盘(MySQL 是通过 Buffer Pool 缓冲池实现的);
针对第三点,咱们可以记录一下当前的操作记录,类比与 redis 的 AOF 文件(MySQL 中叫 redo log);
针对第四点,咱们可以设计一个文件,专门存放每条操作记录更新前的值(MySQL 中叫 undo log)。
接下来,我们借助一个更新语句,来看看 InnoDB 存储引擎的架构设计。
首先我们从磁盘文件中读取数据,在更新内存数据之前,将旧数据写入 undo log,同时写入 redo log,整个流程如下(其中的 OS cache 和 Redo Log Buffer,读者可以将其看做缓存,后面有涉及,将会详细讲解):
3.2 图解
4 MySQL 物理数据模型
4.1 数据在磁盘上的存储格式
4.2 null列表与数据头
4.3 行溢出
5 BufferPool
5.1 free 链表
5.1.1 概念
然后当我们进行增删改操作的时候,BufferPool 才会将数据对应的数据页读出来,放在缓存页中。这个时候,就出现一个问题了,我们怎么知道哪些缓存页是空的呢?MySQL 为我们引入了另外一个概念,free 链表。他是一个双向链表数据结构,每一个节点都存放了空置的描述数据的地址,并且他还有一个基础节点,存放的是控制缓存页的个数。
5.1.2 缓存页 hash 表
5.1.3 图解
5.2 flush 链表
5.2.1 概念
如果你在执行增删改的时候,发现数据页没有被缓存,那么 MySQL 就会通过 free 链表找到对应的描述数据,最后缓存到缓存页中,并且断开 free 链表中对应的描述数据节点。但是这又会引出另外一个问题,只要你改变了缓存页的数据,那么缓存页肯定就和磁盘上的数据页不一致了,这个时候需要将缓存页的数据刷到磁盘上去,那么刷哪些数据呢,总不能全量刷盘吧?于是 MySQL 引入了另外一个链表 flush 链表。他的数据结构和 free 链表一致,只不过,他的节点放置的是需要被刷回磁盘的描述数据地址。
5.2.2 图解
5.3 LRU 链表
5.3.1 概念
从前面的文章中我们知道了 BufferPool 中存在缓存页,但是我们思考一下,缓存页是启动的时候就分配好了的,如果满了怎么办?要么扩容,要么淘汰。MySQL 使用的是 LRU 算法淘汰部分缓存。而这个 LRU 算法,是基于 LRU 链表的。最近被访问过的缓存页,会被挪到链表最前面,因此最少访问的缓存页就会在链表的最尾部,淘汰缓存时,我们只需要淘汰最后的数据页即可。
5.3.2 图解
5.3.3 LRU 链表存在的问题
1)预读机制对 LRU 链表的影响
为了提高效率,MySQL 从磁盘上加载数据到缓存的时候,他可能会把数据页相邻的其他数据页也加载到缓存中去,这个就是 MySQL 的预读机制。
我们思考一下这样的预读机制会对 LRU 链表造成什么影响呢?
这些被查出来的预读数据,可能根本不常用,但是他还是被放在了 LRU 链表的前面,从而导致他们不能被及时淘汰。
2)触发预读机制的常见情况
innodb_read_ahead_threshold
默认 56,如果顺序访问一个区里的多个数据页的数量超过了这个阀值,那么就会把相邻区中所有的数据页都加载到缓存里去。
innodb_random_read_ahead
默认 off,如果 Buffer Pool 里缓存了一个区 13 个连续的数据页,且这些数据页会被频繁访问,那么就会把这个区的其他数据页加载到缓存里去。
3)全表扫描对 LRU 链表的影响
select * from table
全表扫描,会将表里所有的数据页都从磁盘加载到 Buffer Pool 里面去,导致 LRU 尾部的链表反而是频繁被访问的数据。
4)图解
5.3.4 MySQL 对 LRU 算法的优化
通过上面的分析,我们知道了 LRU 算法可能会存在的一些问题,写 MySQL 的大佬们当然也想到了这些问题,下文列举了 MySQL 对 LRU 算法的两种优化。
1)通过冷热数据分离,优化 LRU 算法
前面的问题为什么会出现呢?很大原因是因为所有数据都放在 LRU 链表中,如果我们把他分成冷热数据两部分,预读数据、全表扫描和其他不常用的数据放在冷数据区,其他常用的放在热数据区,缓存淘汰的时候,只淘汰冷数据区的数据,是不是就解决这个问题了?这个思想跟秒杀系统中热数据放 redis,冷数据放数据库,个人感觉也是异曲同工。
以下是相关的两个参数,了解即可,一般不会去修改他。
2)通过定时任务,优化 LRU 算法
为了提升效率,MySQL 开启一个后台线程,定时把冷数据尾部的一些数据输入磁盘。
5.4 free 链表、flush 链表、LRU 链表,修改数据的动态联系
本文完。由于篇幅有限,下文我会另起一些文章,欢迎大家持续关注本公众号,及时获得下文知识。
如果觉得这篇文章对你有所帮助,还请帮忙点赞、在看、转发一下,非常感谢!
往期热门文章推荐