MySQL体系结构与SQL执行流程

261 阅读6分钟

MySQL在整体架构上,被划分为连接器解析器优化器执行引擎查询缓存等多个核心部件:

MySQL架构体系.png

连接器

以连接池的方式管理客户端的连接,也就是说,MySQL会为每一个连接创建或复用一条线程来处理其命令,可以通过以下SQL查看所有线程的信息:

SHOW PROCESSLIST;

执行该SQL语句,得到结果:

idUserHostdbCommandTimeStateInfo
13rootlocalhost:63617Query0initSHOW PROCESSLIST

其中idUserHost列的意义非常容易理解,这里就不再过多介绍了。

db字段表示当前正在访问的数据库,如果没有调用过USEE命令,基本上就会为空。

Command是非常重要的列,表示当前客户端正在执行的操作类型。该列可能的值有非常多,可参考官方文档中8.14.2 Thread Command Values部分,这里只介绍几个常见的值:

  • Daemon: 表示该线程是MySQL服务器内部所使用的,而非客户端线程;
  • Create DB: 表示正在执行创建表的操作;
  • Drop DB: 表示正在执行删除表的操作;
  • Init DB: 表示线程正在选择其默认数据库(也就是在执行USE操作);
  • Prepare: 表示该线程正在准备一条Prepare Statement语句;
  • Query:表示正在执行一条语句,注意这里没有限定一定是查询语句;
  • Sleep: 表示该线程正在等待客户端发出新的指令;
  • Time: 表示该线程目前未被使用;

接下来是Time列,表示该线程处于当前状态的时间,单位是秒。

State列也是非常重要的列,表示该线程的状态。同Command列一样,State列可能出现的值也有非常多,可参考官方文档中8.14.3 General Thread States部分。常见的值有以下几种:

  • checking permissions: 表示该线程正在检查服务器是否有执行该语句的权限;
  • starting: 表示语句开始执行的第一个阶段;
  • update: 表示线程准备开始更新表;
  • Updating: 表示线程正在搜索被更新的列,或正在更新这些列;
  • Writing to net: 表示服务器正在向网络写入数据包;

最后是Info列,表示当前正在执行的语句。有时候语句太长,Info会显示不全,这时候可以考虑使用SHOW FULL PROCESSLIT语句来显示完整的Info信息。

解析器

或称分析器。解析器主要完成两项工作:词法分析语法分析,最后生成"解析树"交由之后的组件进行处理。

词法分析是将整个SQL语句识别为一组"符号"并识别其中的关键字用于判断该SQL的目的是"做什么"。如以下SQL:

SELECT * FROM USER WHERE ID = 1;

SQL会被解析为8个"符号",并被识别为一个从USER表执行查询的查询语句。

词法分析完成之后,接着会进行语法分析。语法分析的主要工作是用来检查输入的SQL是否符合MySQL 的语法。如:

-- SELECT 关键字写错了
SELEC * FROM USER WHERE ID = 1;

执行该SQL会出现错误,提示:You have an error in your SQL syntax...

这就是语法分析器所分析的结果。

优化器

优化器的主要工作是根据"解析树"生成最优的执行计划。根据官方文档 MySQL Internals Manual: Chapter 7 The Optimizer中的介绍,优化工作分为主要优化其它优化两个类别。

主要优化包括:常量关系优化连接优化换位排序优化分组优化

其它优化包括:空值过滤分区优化

相关信息量非常庞大,建议直接参考官方文档。

执行引擎

执行引擎负责SQL语句的执行。其会根据SQL语句中所涉及表的存储引擎类型,与其对应的API接口进行交互,得到查询结果并返回给客户端。

查询缓存

这是一个在MySQL8中已经被删除的组件。目前可以在官方网站中MySQL 5.7 Reference Manual: 8.10.3 The MySQL Query Cache部分查询到对应的信息。且在该文档中高亮提示:

The query cache is deprecated as of MySQL 5.7.20, and is removed in MySQL 8.0.

当查询缓存被开启时,执行引擎会在将结果返回给客户端之前,将以完整的查询语句为Key,查询结果为Value的形式将查询结果缓存起来。

当新的查询语句提交到MySQL 时,查询缓存会逐字节检查新的查询语句,当新查询语句与缓存中的Key完全相同时,才会直接返回缓存中的值。这个完全相同的条件非常严格,包括大小写一致,如以下两条SQL不能被视为同一条SQL而使用查询缓存:

SELECT * FROM USER;
select * from user;

另外,即使是参数相同的情况下,预编译语句和普通查询语句也不能被识别为同一条SQL

SELECT * FROM USER WHERE ID = ?; -- 参数值为1
SELECT * FROM USER WHERE ID = 1;

包括使用了部分函数,如:NOW()RAND()等,都无法被缓存。

同时,查询缓存很容易被清除:只要某一张表有被执行INSERTUPDATEDELETETRUNCATE TABLEALTER TABLEDROP TABLEDROP DATABASE操作,该表所对应的所有缓存都会被无效化并从查询缓存组件中删除。

综上所述,查询缓存使用条件苛刻,易清理,从而导致在实际使用过程中缓存的命中率并不高。MySQL核心开发成员Matt Lord在一篇博客中提到了MySQL8删除掉查询缓存的原因,也可以从中了解到查询缓存的诸多弊端,请见这里

SQL执行流程

MySQL通过这些核心组件,串接起了SQL语句的执行流程:

SQL执行流程.png

① 客户端与MySQL 服务器建立连接,并提交SQL语句;

② (可选)当查询缓存可用并开启的情况下,查询缓存会逐字节检查SQL语句,寻找对应的缓存结果。当缓存命中时,直接返回查询结果;

SQL语句提交到了解析器中,解析器按顺序执行词法解析和语法解析,生成解析树;

④ 优化器根据解析树中的信息,生成最佳执行计划。期间会经过主要优化和其它优化这两种方式的优化;

⑤ 执行引擎根据执行计划对相关表的存储引擎类型,调用对应的API接口,查询对应数据;

⑥ 存储引擎操作底层数据文件,获取到查询结果并响应给执行引擎;

⑦(可选)当查询缓存可用并开启的情况下,执行引擎将查询结果缓存到查询缓存中;

⑧ 执行引擎返回查询结果;