文章目录
- MySQL 基本架构概览
- Server层介绍
- SQL执行过程
- 查询语句
- 更新语句
- SQL执行过程的日志问题
本篇文章会分析下一个 sql 语句在 MySQL 中的执行流程,包括 sql 的查询在 MySQL 内部会怎么流转,sql 语句的更新是怎么完成的。
在分析之前我会先带着你看看 MySQL 的基础架构,知道了 MySQL 由那些组件组成已经这些组件的作用是什么,可以帮助我们理解和解决这些问题。
MySQL 基本架构概览
简单来说 MySQL 主要分为 Server 层和存储引擎层:
- Server 层:主要包括连接器、查询缓存、分析器、优化器、执行器等,所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图,函数等,还有一个通用的日志模块 binglog 日志模块。
- 存储引擎: 主要负责数据的存储和读取,采用可以替换的插件式架构,支持 InnoDB、MyISAM、Memory 等多个存储引擎,其中 InnoDB 引擎有自有的日志模块 redolog 模块。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始就被当做默认存储引擎了。
Server层介绍
- 连接器
连接器主要和身份认证和权限相关的功能相关,就好比一个级别很高的门卫一样。
主要负责用户登录数据库,进行用户的身份认证,包括校验账户密码,权限等操作,如果用户账户密码已通过,连接器会到权限表中查询该用户的所有权限,之后在这个连接里的权限逻辑判断都是会依赖此时读取到的权限数据,也就是说,后续只要这个连接不断开,即时管理员修改了该用户的权限,该用户也是不受影响的。
- 查询缓存(MySQL 8.0 版本后移除)
查询缓存主要用来缓存我们所执行的 SELECT 语句以及该语句的结果集。
连接建立后,执行查询语句的时候,会先查询缓存,MySQL 会先校验这个 sql 是否执行过,以 Key-Value 的形式缓存在内存中,Key 是查询预计,Value 是结果集。如果缓存 key 被命中,就会直接返回给客户端,如果没有命中,就会执行后续的操作,完成后也会把结果缓存起来,方便下一次调用。当然在真正执行缓存查询的时候还是会校验用户的权限,是否有该表的查询条件。
MySQL 查询不建议使用缓存,因为查询缓存失效在实际业务场景中可能会非常频繁,假如你对一个表更新的话,这个表上的所有的查询缓存都会被清空。对于不经常更新的数据来说,使用缓存还是可以的。
所以,一般在大多数情况下我们都是不推荐去使用查询缓存的。
MySQL 8.0 版本后删除了缓存的功能,官方也是认为该功能在实际的应用场景比较少,所以干脆直接删掉了。
- 分析器
MySQL 没有命中缓存,那么就会进入分析器,分析器主要是用来分析 SQL 语句是来干嘛的,分析器也会分为几步:
第一步,词法分析,一条 SQL 语句有多个字符串组成,首先要提取关键字,比如 select,提出查询的表,提出字段名,提出查询条件等等。做完这些操作后,就会进入第二步。
第二步,语法分析,主要就是判断你输入的 sql 是否正确,是否符合 MySQL 的语法。
完成这 2 步之后,MySQL 就准备开始执行了,但是如何执行,怎么执行是最好的结果呢?这个时候就需要优化器上场了。
- 优化器
优化器的作用就是它认为的最优的执行方案去执行(有时候可能也不是最优,这篇文章涉及对这部分知识的深入讲解),比如多个索引的时候该如何选择索引,多表查询的时候如何选择关联顺序等。
可以说,经过了优化器之后可以说这个语句具体该如何执行就已经定下来。
- 执行器
当选择了执行方案后,MySQL 就准备开始执行了,首先执行前会校验该用户有没有权限,如果没有权限,就会返回错误信息,如果有权限,就会去调用引擎的接口,返回接口执行的结果。
SQL执行过程
sql 可以分为两种,一种是查询,一种是更新(增加,更新,删除)。
- 更新语句执行流程如下:
(1)分析器
(2)权限校验
(3)执行器调用引擎
(4)引擎执行
(5)写入redo log(prepare 状态)
(6)写入binlog
(7)写入redo log(commit状态)
我们可以详细的说明下这两个执行过程。
查询语句
查询语句,语句如下:
结合上面的说明,我们分析下这个语句的执行流程:
- 连接器
连接器是半双工的通信,在mysql与客户端连接后,验证请求用户的权限:首先检查账户和密码是否正确,如果用户的账户和密码验证通过,会在mysql自带的权限表中查询当前用户的权限。 - 查询缓存
在 MySQL8.0 版本以前,会查询缓存,缓存以key和value的哈希表形式存储,key是具体的sql语句,value是结果的集合。如果无法命中缓存,就继续走到分析器的的一步,如果命中缓存就直接返回给客户端 。
不过需要注意的是在 MySQL8.0 版本以后,缓存被官方删除掉了。之所以删除掉,是因为如果在一个写多读少的环境中查询缓存的失效非常频繁,所以一般是建议在客户端上做缓存。 - 分析器
通过分析器对sql语句做语法分析和词法分析,生成语法树。在这个阶段会解析sql语句的关键词和非关键词进行提取,并且对于整个语句的有效性做判断。如果分析到语法错误,会直接给客户端抛出异常:ERROR:You have an error in your SQL syntax。这个阶段也会做一些校验:比如校验当前数据库是否存在sql语句的表和字段,没有则会报错unknown column in field list。 - 优化器
主要是进行sql语句的优化,比如多个索引的时候该如何选择索引,多表查询的时候如何选择关联顺序等。 - 执行器
在执行器的阶段会调用存储引擎的API
更新语句
我们就以 InnoDB 模式下来探讨这个语句的执行流程。流程如下:
- 连接器
- 清空缓存
不同的是,写会把缓存清空,防止出现数据不一致。 - 解析器
也是会生成一个语法树,并且解析器会知道这是一个更新语句。 - 优化器
优化器决定是否用到索引,要使用哪个索引。 - 执行器
执行器负责具体执行,根据索引或者全表扫描查找到这行数据,然后修改更新数据。InnoDB 引擎把更新完的数据保存在内存中,同时记录 redo log,此时 redo log 进入 准备(prepare) 状态。
之后执行器调用引擎APi来提交事务,并且将提交 redo log 更新为提交(commit)状态。
SQL执行过程的日志问题
这里肯定有同学会问,为什么要用两个日志模块,用一个日志模块不行吗?
这是因为最开始 MySQL 并没与 InnoDB 引擎( InnoDB 引擎是其他公司以插件形式插入 MySQL 的) ,MySQL 自带的引擎是 MyISAM,但是我们知道 redo log 是 InnoDB 引擎特有的,其他存储引擎都没有,这就导致会没有 crash-safe 的能力(crash-safe 的能力即使数据库发生异常重启,之前提交的记录都不会丢失),binlog 日志只能用来归档。
为什么 redo log 要引入 prepare 预提交状态?这里我们用反证法来说明下为什么要这么做?
- 先写 redo log 直接提交,然后写 binlog,假设写完 redo log 后,机器挂了,binlog 日志没有被写入,那么机器重启后,这台机器会通过 redo log 恢复数据,但是这个时候 bingog 并没有记录该数据,后续进行机器备份的时候,就会丢失这一条数据,同时主从同步也会丢失这一条数据。
- 先写 binlog,然后写 redo log,假设写完了 binlog,机器异常重启了,由于没有 redo log,本机是无法恢复这一条记录的,但是 binlog 又有记录,那么和上面同样的道理,就会产生数据不一致的情况。
- 如果采用 redo log 两阶段提交的方式就不一样了,写完 binglog 后,然后再提交 redo log 就会防止出现上述的问题,从而保证了数据的一致性。那么问题来了,有没有一个极端的情况呢?假设 redo log 处于预提交状态,binglog 也已经写完了,这个时候发生了异常重启会怎么样呢? 这个就要依赖于 MySQL 的处理机制了,MySQL 的处理过程如下:
- 判断 redo log 是否完整,如果判断是完整的,就立即提交。
- 如果 redo log 只是预提交但不是 commit 状态,这个时候就会去判断 binlog 是否完整,如果完整就提交 redo log, 不完整就回滚事务。
这样就解决了数据一致性的问题。