目录

一、MySQL 执行流程图
如下是 MySQL 执行流程图:
- 客户端(运行程序)先通过 连接器 连接到 MySQL 服务器。MySQL 中建立一个新的线程来对连接进行半双工通信。
- 连接器通过数据库权限身份验证后,如果数据库缓存开启,先查询 数据库缓存 是否存在(之前执行过相同条件的 SQL 查询)。如果有缓存会直接返回缓存中的数据,如果没有则会进入分析器。
- 进入 分析器 后会对查询语句进行语法的分析,判断该查询语句 SQL 是否存在语法错误,如果存在查询语法错误,会直接返回给客户端错误,如果正确会进入优化器。
- 进入 优化器 后会对查询语句进行优化,如:如果一条语句用到了多个索引会判断那个索引性能更好。
- 最终会进入 执行器,开始执行查询语句,根据语句中的表结构使用对应的 存储引擎 进行查询,直到查询出满足条件的所有数据,然后进行返回。
二、MySQL的分层
大体来说,MySQL 可以分为 Server层 和 存储引擎 两部分:
Server层
:包括 连接器、查询缓存、分析器、优化器、执行器。存储引擎
:负责数据的存储和提取。其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多种存储模式。现如今最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 开始成为默认存储引擎。也就是说当我们不指定存储引擎时默认使用的就是 InnoDB,我们也可以在 create table 时通过
engine=memory
来执行存储引擎。
从 MySQL 执行流程图中可以看出,不同的存储引擎共用同一个 Server 层,也就是连接器到执行器那一部分。
2.1 连接阶段
首先,我们需要连接上数据库,这时候接待我们的就是 连接器。连接器主要负责的工作就是跟客户端 建立连接、获取权限、维持和管理连接。连接命令如下:
mysql -h$ip -P$port -u$user -p
# $ip: 服务器IP
# $port: MySQL端口号
# $user: 用户名
- 如果用户名或密码不对:则会收到一个
Access denied for user
错误,然后客户端程序结束执行。 - 如果用户名密码认证通过:连接器则会去权限表中查询该用户所拥有的权限,这个连接里的权限逻辑判断,全都依赖于此时读到的权限。
这就意味着,当一个用户成功建立连接后,即使使用管理员账户对其权限做了修改,也不会立即生效,只有重新建立连接后才会使用新的权限设置。
2.2 查询缓存阶段(Query Cache,MySQL 8.0已移除)
连接建立成功后,如果缓存开关打开,会先查询缓存。MySQL 拿到一个 SQL 语句之后会先到缓存看看是否在此之前执行过这条语句,之前执行的语句会以 key-value
的形式直接缓存在内存中。
- key 是查询语句,value 是查询结果。
如果你的查询能在缓存中找到相应的 key,则直接返回其对应的 value 给客户端。
如果语句不在查询缓存中,就会继续执行后面的阶段。执行完成后,执行结果会被存入查询缓存中。你可以看到,如果命中缓存,MySQL 不需要执行后面的复杂操作,就可以执行返回结果,这个效率会很高。
补充1: 可以通过如下命令查看是否开启查询缓存:
SHOW VARIABLES LIKE 'query_cache%';
重点关注以下参数:
query_cache_type
:缓存类型OFF
(0):完全关闭ON
(1):开启所有符合条件的查询缓存DEMAND
(2):仅缓存有SQL_CACHE
提示的查询
补充2: 可以通过如下命令查看缓存的运行状态:
SHOW STATUS LIKE 'Qcache%';
主要指标说明:
Qcache_free_blocks
:空闲内存块数Qcache_free_memory
:空闲内存量Qcache_hits
:缓存命中次数Qcache_inserts
:被加入到缓存的查询数量Qcache_lowmem_prunes
:因内存不足被删除的缓存数量
生产环境建议:
对于MySQL 5.7及以下版本:
- 查询缓存适用于读多写少的场景
- 高并发写入环境建议关闭(
query_cache_size=0
)
监控缓存效率:
-- 计算缓存命中率 SELECT (Qcache_hits/(Qcache_hits+Com_select))*100 AS hit_percentage FROM information_schema.GLOBAL_STATUS WHERE variable_name IN ('Qcache_hits','Com_select');
- 命中率低于20%建议关闭查询缓存
重要参数调整:
[mysqld] query_cache_size = 64M # 根据内存调整 query_cache_limit = 2M # 限制大结果集缓存 query_cache_min_res_unit = 4K
2.3 解析与预处理阶段(词法分析、语法分析、预处理器)
如果没有命中缓存,则真正开始执行语句了。
分析器首先进行 词法分析:我们输入的 SQL 是由多个字符串组成的,MySQL 需要识别出来里面的字符串分别是什么。
其次,需要对 SQL 进行 语法分析:根据词法分析的结果,语法分析器会根据语法规则来判断我们输入的这条 SQL 是否满足 MySQL 的语法规则。如果我们的语法不对的话,我们会受到 MySQL 的错误提示 You have an error in you SQL syntax
。
最后,使用 预处理器 对 SQL 进行处理:检查表和列是否存在、检查当前用户权限、展开 *
为所有列名。
例如要确认我们是否有操作这个表的执行权限:
- 如果没有权限则会返回没有权限的报错。如下所示:
mysql> select * from T where ID=10;
ERROR 1142 (42000): SELECT command denied to user 'b'@'localhost' for table 'T'
- 如果有权限,再继续进入优化阶段。
2.4 查询优化阶段
经过了分析器,MySQL 知道我们要做什么了,在它开始执行之前,还需要先经过 优化器 的处理,主要包含下面三块内容:
- 查询重写
- 优化器进行等价变换
- 例如将
WHERE 1=1 AND id>10
简化为WHERE id>10
- 生成执行计划
- 基于成本估算选择最优执行方案
- 决定:
- 在表里面有多个索引的时候,决定使用哪个索引;
- 决定多表时各个表的连接顺序;
- 是否使用临时表;
- 是否使用文件排序等。
- 执行计划缓存(MySQL 8.0+)
- 对预处理语句缓存执行计划
例如下面这个SQL:
select * from t1 join t2 using(ID) where t1.c=10 and t2.d=20;
补充:using() 等价于 on,即当 t1表 于 t2表 关联的字段相同时,using(field) 和 t1.field = t2.field 等价。
根据上面的SQL,我们可以得出两种方案:
- 既可以先从表t1里面取出 c=10 的 ID 值,再根据 ID 值取关联到表 t2,再判断 t2 里面的 d值是否等于20;
- 也可以先从表t2里面取出d=20的记录的ID值关联到表t1,再判断 t1 里面的 c值是否等于10。
这两种执行方法的逻辑和结果都是一样的,但是之心效率会有所不同,优化器的作用就是决定选择哪种方案。
2.5 执行引擎阶段
mysql> select * from T where ID=10;
比如在上面这个例子中的表T中ID字段是没有索引的,那么执行器的流程是这样的:
- 用 InnoDB 引擎接口取这个表的第一行,判断 ID 是否为 10,如果不是则跳过,如果是则将这行存在结果集中。
- 调用引擎接口取下一行,重复相同的逻辑判断,直到取到这个表的最后一行。
- 执行器将上述遍历过程的所有满足条件的行组成记录集作为结果集返回给客户端。
三、常见面试题
3.1 MySQL提示不存在此列
是执行到哪个节点报出的?
此错误是执行到分析器阶段爆出的,因为MySQL会在分析器阶段价差SQL语句的正确性
3.2 MySQL查询缓存的功能有何优缺点
MySQL查询缓存功能是在连接器之后发生的,它的优点是效率高,如果已经有缓存则会直接返回结果。查询缓存的缺点是失效太频繁导致缓存命中率比较低,任何更新表操作都会情况缓存查询,因此导致查询效率非常容易失效
3.3 如何关闭MySQL的查询缓存功能
MySQL查询缓存默认是开启的,配置 query_cache_type
参数为 OFF(0)
或 DEMAND(2)
(按需使用)关闭缓存。MySQL8.0之后直接删除了查询缓存的功能。
整理完毕,完结撒花~🌻
参考地址:
1.MySQL执行一条查询语句的内部执行过程,https://zhuanlan.zhihu.com/p/309241412
2.MySQL实战 | MySQL逻辑架构—一条查询SQL是如何执行的,https://cloud.tencent.com/developer/article/2177898