笔者在之前的一次面试过程中就遇到了这个问题,当时说得也不是很明白,今天写一个博客记录一下。
本博客将会从两部分说明,第一部分是MySQL的一些基本组件,第二部分就是具体sql语句的分析。
一、MySQL的基本组件
简单来说MySQL主要分为两个部分:Server层和存储引擎层
- Server层:包括连接器、查询缓存、分析器、优化器、执行器等,所有跨存储引擎的功能都在这一层实现,还有一个通用的binlog日志模块。
- 存储引擎:这一层主要负责的是数据库的存储和读取,采用的是可替换的插件式架构,常见的存储引擎有:InnoDB,MyISAM,Memory等。其中InnoDB有自己的日志模块redo log模块。
1.Server层基本组件介绍
1)连接器
连接器主要是和身份认证、权限相关的功能相关。主要负责在用户登陆数据库的时候进行身份认证,包括校验用户账户密码、权限等。当账户密码通过的时候,连接器会到权限列表中读取相应的权限数据,之后在这个连接里的所有权限逻辑判断都会依赖此次读取到的权限数据。只要连接不断开,就算管理员修改了权限,也不会影响查询。
2)查询缓存
查询缓存主要是用来缓存我们所执行的SELECT语句以及相关的结果集。在建立连接之后,当执行查询语句的时候,会先查询缓存,如果缓存命中,就会直接从缓存中返回给客户端。(就算是从缓存中获取也会在查询缓存的时候先验证权限,是否具有该表的查询权限)
缓存是以Key-Value的形式放在内存中,Key是查询语句,Value是结果集。如果查询缓存时没有命中,就会执行后续的操作,完成后也会把结果缓存起来。
注意:查询缓存在MySQL 8.0版本之后被移除
3)分析器
当没有缓存命中的时候就会进入分析器,分析器分为如下两步:
1.词法分析:首先提取关键字,查看是哪种操作,比如 SELECT,找出查询的表,查询的字段名,查询条件等。做完这些进入第二步。
2.语法分析:主要判断SQL有没有语法错误,是不是符合MySQL的语法规则。
4)优化器
优化器会以它认为最优的执行方案去执行,(有时候也可能不是最优,具体可以看笔者其他文章),比如多个索引时如何选择索引等。
5)执行器
在确定了执行方案之后,MySQL就开始执行了。第一步就是权限校验,如果没有权限就会返回错误信息,如果有权限的话就会去调用引擎的接口,然后返回接口执行的结果。(包括缓存查询结果)
二、语句分析
sql语句可以分为两种,一种是查询select,一种是更新(delete,update,insert)。
1.查询语句
SELECT * FROM tb_student S WHERE S.age = 18 AND S.name = 'CODE_WEN';
结合上面的说明,分析上面语句的执行过程:
- 先检查权限问题,如果没有权限就直接返回错误信息,如果有权限就会先查询缓存(MySQL 8.0版本之前),以这个sql语句为key,在内存中查询是否有结果,如果有直接返回结果,没有就执行词法分析。
- 通过分析器先进行词法分析,提取sql语句里面的关键字。以上面的语句为例:首先上面提取的语句是查询语句SELECT,然后提取查询的表名tb_student,需要查询所有字段,查询条件是age=18,name=‘CODE_WEN’。然后进行语法分析,判断sql语句是否正确,检查没问题之后就会执行下一步。
- 优化器确定执行方案。优化器根据自己的优化算法选择一个执行效率最好的一个方案。确认了执行计划之后就开始准备执行了。
- 进行权限校验,如果没有权限就会返回错误信息,有权限就会调用数据库引擎接口,返回引擎执行结果。
2.更新语句
UPDATE tb_student A set A.age = 19 where A.name = 'CODE_WEN';
这条语句也会按照上面的流程走,只是在执行操作的时候要记录日志。MySQL自带的日志模块是bin log(归档日志),所有的存储引擎都会使用,InnoDB引擎还自带一个redo log(重做日志)。下面以InnoDB为例。流程如下:
- 先查询nane=CODE_WEN的这条数据,如果有缓存也会用到缓存。
- 拿到查询的语句,把age改为19,然后调用引擎API接口,写入这一行数据。InnoDB把数据存放在内存中,同时记录redo log,此时redo log进入prepare状态,然后告诉执行器,执行完成,随时可以提交。
- 执行器收到通知后,记录bin log 日志,然后调用引擎接口,提交redo log为提交状态。
- 更新完成。
SQL执行过程
查询流程:权限校验 ==> 查询缓存 ==> 分析器 ==> 优化器 ==> 权限校验 ==> 执行器 ==> 引擎
更新流程:权限校验 ==> 查询缓存 ==> 分析器 ==> 权限校验 ==> 执行器 ==> 引擎 , redo log prepare ==> bin log ==> redo log commit