Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > mysql从sql到数据

MySQL | 从SQL到数据的完整路径

作者:海边的Kurisu

MySQL执行流程分为服务层和存储引擎层,服务层包括连接器、查询缓存、SQL语句解析、预处理、优化和执行,连接器负责建立连接、校验用户名和密码、处理长连接和短连接,本文介绍MySQL | 从SQL到数据的完整路径,感兴趣的朋友一起看看吧

一、引文

最近我正在学习 MySQL 的面试相关八股,打算单独开一个 MySQL 专栏来记录自己每天的学习,内容主要来自小林coding和 JavaGuide。今天了解到了 MySQL 的架构和执行流程,没想到一条简单的 sql 语句居然在 MySQL 中经历了这么多的流程。

二、MySQL 执行流程

MySQL 执行流程大概分为服务层和存储引擎层两部分,服务层中主要涉及到建立连接、查询缓存、SQL语句解析、预处理、优化、执行几个阶段,存储引擎层则是把执行结果拿到的数据返回。

1.服务层

(1)连接器

连接器这一环主要是让客户端(如 Java 程序、TablePlus等)基于 TCP 连接到 MySQL 的服务端。第一步肯定是先启动 MySQL 服务,然后基于 TCP 协议完成三次握手,连接层要做的事情第一个就是校验你的用户名和密码是否正确,只有你输入正确时,才会建立连接,同时记录你这个账号的权限,并在此后该连接的过程中都会基于刚连接时保存的权限一直执行该权限的逻辑。

在 MySQL 中也有长连接和短连接的概念,所谓的短连接就是每建立一次 MySQL 客户端连接,只能执行一条 SQL 语句,然后就立马断开连接。而长连接就是一次客户端连接中可以执行多条 SQL 语句。

长连接与短连接的性能差异极大,在 MySQL 中频繁地创建销毁连接都是极其消耗资源的,原因主要出在了巨大的“握手”开销上,如网络层面的 TCP 三次握手、MySQL 协议握手、TLS/SSL握手以及每次创建连接 MySQL 都要去系统表查询用户权限并加载到内存中。

与此同时 MySQL 的连接与内存是密切相关的。默认情况下,连接器每建立一次新的连接,MySQL 都会对应创建一个新的工作线程,即便这个线程是 Sleep 状态,也会占用相应的内存。为避免大量线程创建导致内存溢出以及 CPU 频繁切换线程,MySQL 还提供了线程池功能,让少量线程服务于大量连接,进而减小内存损耗。

一个连接占用的内存主要分为两部分:固定内存临时内存

(1) 固定内存 (Thread Static Memory)

这是连接建立后立即分配的,直到连接断开才释放:

(2) 会话级临时内存 (Session Private Memory)

这是最容易导致内存激增的部分。当连接开始执行复杂的 SQL 时,MySQL 会根据需要临时分配内存:

关键点: 这些内存是按需分配的。如果一个 SQL 不需要排序,就不会分配 sort_buffer。但如果设置得太大,成千上万个连接同时请求时,内存会瞬间被榨干。

管理与回收机制

(1) 空闲连接的管理

如果一个连接执行完任务后没有关闭(长连接),它会进入 Sleep 状态。

(2) 线程缓存 (Thread Cache)

为了避免频繁创建和销毁线程(这是很消耗资源的),MySQL 实现了一个 Thread Cache

常见指令:

show processlist // 查看 MySQL 服务端被多少客户端连接的情况
show variables like 'wait_timeout' // 查看 MySQL 中客户端连接的最大空闲时长
show variables like 'max_connections' // 查看 MySQL 中最大客户端连接数量
kill connection id // 删除 MySQL 的某个客户端连接,提供连接对应的 id 即可

(2)查询缓存

连接器建立连接之后,客户端就可以发送 SQL 语句给 MySQL 服务端了,拿到 SQL 语句先解析第一个字段查看是什么类型的 SQL 语句。如果发现是 SELECT,那就路由到查询缓存里,查询缓存里面的数据是k-v形式的键值对,key为 SQL 查询语句,value 为该查询语句返回的结果。如果拿着客户端发来的 SQL 语句命中了缓存,那就可以直接返回 value,反之再走下面的解析器层。

也许是受到了 Redis 的影响,让我一开始觉得这功能设计的很好。但仔细一想会发现,MySQL 里面的表不同于 专门做缓存的 Redis,MySQL 里面的数据肯定是更容易更新的,一旦表中数据更新那就意味着查询缓存失效,需要重建缓存。如果遇到一个重建条件非常苛刻的缓存,好不容易重建完,表又更新了,这缓存还一次没用呢,是不是就十分浪费资源。所以从 MySQL 8 起查询缓存这一环就直接被去除了。

(3)解析器

解析器主要做两件事,第一件事是词法分析,这一步主要是把输入转化成若干个Token,其中Token包含key和非key。比如,一个简单的SQL如下所示:

SELECT name FROM userInfo

分析之后,会得到4个Token,其中有2个Keyword,分别为select和from:

关键字非关键字关键字非关键字
selectnamefromuserInfo

第二件事就是语法分析,拿到上一步词法分析的结果后,再判断 SQL 语句是否符合语法规范,如果没问题那就构建语法树,方便后续获取 SQL 类型、表名、字段名。如果写错了(如 selec),会报错:You have an error in your SQL syntax

(4)预处理器

如果写了一个语法完全正确的树,但是表或者字段不存在,还是在解析的时候报错,因为解析器处理之后,还有一个预处理器,它用来判断解析树的语义是否正确,也就是表名和字段名是否存在。预处理后生成一个新的解析树。此外它还可以进行扩展字段,比如将 select * 中的 * 符号,扩展为表上的所有列;

(5)优化器

这是 MySQL 的“大脑”。当一个 SQL 有多种执行路径时,优化器会决定使用哪一种。

由于距离学 MySQL 已经过去好几个月了,早就已经忘记了 B+ 树索引、回表、覆盖索引这些概念了,所以当时看上图的案例有点晕,如果大家有相同的感觉可以跟我在下面一起复习:

Q:两种 B+ 树索引的区别

在 InnoDB 存储引擎中,根据叶子节点存放内容的不同,索引分为两类:

主键索引的 B+ 树(又称:聚簇索引 - Clustered Index)

二级索引的 B+ 树(又称:辅助索引 - Secondary Index)

Q:什么是“回表”?

通常情况下,如果你执行:
SELECT price FROM product WHERE name = 'apple';

这个回到主键索引再查一遍的过程,就叫 “回表”。回表会增加磁盘 I/O,降低性能。

Q:什么是覆盖索引?

覆盖索引并不是一种索引类型,而是一种查询现象
当一个索引包含(覆盖)了查询语句中需要的所有字段时,MySQL 就可以直接从这个索引中返回数据,而不需要回表

结合你图片中的例子:

查询语句:SELECT id FROM product WHERE id > 1 AND name LIKE 'i%';

此时:
MySQL 引擎只需要扫描 name 这棵 B+ 树,就能过滤出满足 i% 条件的记录,并且直接从这棵树里把 id 拿出来返回。它根本不需要去翻主键索引那棵树。 这就叫“覆盖索引”。

复习了上述概念我们也就能明白为什么优化器最后帮我们决定使用普通索引,做覆盖索引优化。而不是说直接查主键索引。一来是主键索引的叶子节点存储的信息过多,查询主键索引导致的磁盘 I/O 也就更多,自然更耗时间,相较之下二级索引的叶子节点里面只存了主键和索引字段。另一个原因就是我们查的是主键ID,它已经存在于二级索引的叶子节点了,因此没必要回表,节省了大笔开销。

常见指令:

explain + 查询 SQL 语句 // 输出这条 SQL 语句的执行计划

(6)执行器

开始执行 SQL。

索引下堆:索引下堆是一种查询优化策略,它能够减少二级索引查询过程中的回表操作,其本质在于把应该由 Server 层做的判断交给了存储引擎层。在小林coding中案例如下:

使用索引下堆前后的关键我用红线标注了起来,主要就是 reward 是否等于 1000000 这个判断由 Server 层转交给了存储引擎层,它的好处在于不用为了让 Server 层判断这一条件而特意回表,可以看到如果该条件不成立完全可以在存储引擎层就直接跳过该二级索引,进而节省了一部分的回表操作开销。

2.存储引擎层

存储引擎是数据真正存放的地方。

到此这篇关于MySQL | 从SQL到数据的完整路径的文章就介绍到这了,更多相关mysql从sql到数据内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文