一、sql查询执行过程概括
下面给出的mysql基本架构示意图,从中你可以清楚的mysql的各个模块和执行过程。
大体来说可以分为两部分Server层和储存引擎层。
- Server层包括连接器、查询缓存、分析器、优化器、执行器等,覆盖了mysql的大部分核心服务功能,以及所有的内置函数(如日期、时间、数字和加密函数等)
- 储存引擎层负责数据的储存和提取。其架构模式是插件式的,支持Innodb、MyISAM等多个储存引擎。现在最常用的储存引擎是Innodb,从Mysql5.5.5开始为其默认引擎。
从图中不难看出所有的引擎都是公用server层。不知道每个组件干什么用?先对图中的组件有一个大概印象,接下来将介绍server层的一些组件的作用。
![图片[1]-sql语句的执行经历了哪些-中间件专区论坛-技术-SpringForAll社区](https://pic4.zhimg.com/80/v2-38669f2d8fedc64da920e60c1dd33e2f_1440w.webp)
二、连接器
我们会先要连接到数据库,这时候接待你的就是连接器,连接器负责客户端和数据库建立连接、获取权限、维持和管理连接。客户端连接的命令一般会这样写:
mysql -h$ip -u$name -p$pwd
在完成经典的tcp三次握手之后建立连接、一旦建立连接和认证通过,连接器会从权限表中查询你拥有的权限,此连接的权限都会依赖此权限(修改权限后也不会改变,除非重新启动)
连接完成后,如果没有动作,则会处于空闲状态,可以用show processlist
, 下面是命令的返回结果,其中Sleep
表示有一个空闲连接
![图片[2]-sql语句的执行经历了哪些-中间件专区论坛-技术-SpringForAll社区](https://pic3.zhimg.com/80/v2-a21eadece292c031e77751f9845cdb6e_1440w.webp)
当客户端太长时间没有动静,连接器就会自动断开。这个时间参数可以根据wait_timeout
控制,默认是8小时。
在数据库里面,长连接是指连接成功之后,客户端有请求,则会一直用这个连接。短链接是指每一次执行很少的查询后就会断开连接,之后查询则需要重新建立连接。
建立连接的过程比较复杂,占用的时间也比较长 ,所以笔者建议最好使用长连接。
但是长连接也不是十全十美,当你建立长连接之后,你可能会发现mysql占用的内存涨的非常快,因为mysql在执行的过程中所产生的临时对象是管理在该连接对象里面的。这些资源在连接断开的时候才会释放,当长连接持续过久,可能内存占用过大,就会被系统强行杀掉,从表面上看是mysql异常重启了。
所以我们一般会怎么解决这种问题呢。我们一般会考虑下面两种方案:
- 定期断开长连接。在使用一段时间后或者执行一次占内存较大的查询后会断开连接,之后查询再连接。
- 如果是mysql5.7后的版本,可以通过执行
mysql_reset_connection
来初始化资源,该过程不会去重新连接和验证权限
三、查询缓存
当建立连接完成后,就可以执行查询语句了,此时会执行查询逻辑的第二步:查询缓存
可以参考上图,mysql拿到一个查询后,会先到查询缓存去看看之前是否执行过此条语句。之前执行的结果会以key-value的形式被直接存在内存里面。如果语句不在缓存中,则会继续执行下面的步骤,最后将结果缓存到查询缓存中。
但是一般情况下都不会建议用查询缓存,因为一旦对一张表进行更新插入等更改表的操作,关于这个表的缓存就是被清空。由此导致查询缓存的命中率就会很低。所以除非你有一张静态表,很长时间才会更新一次,否则建议不用查询缓存。
mysql中可以把query_cache_type
设置成DEMAND,此时默认的sql就不会执行缓存,需要缓存时需要显示指定:
select SQL_CACHE * from T where ID=10
值得注意的是,再mysql8.0版本中查询缓存的功能被完全删掉了。
四、分析器
如果没有命中缓存,就开始真正的执行语句了。首先,mysql需要知道你要做什么,所以需要对sql进行解析。
分析器首先会对sql进行词法分析,mysql需要识别sql语句的字符串分别是什么,代表什么。词法分析会把例如“select”这些关键字识别出来,也会把字符串“T”识别成表名“T”。
做完词法分析后,就要做语法分析。语法分析会根据语法规则判断输入的sql语句是否满足mysql语法。比如你的“select”少了一个s,收到“You have an error in your SQL syntax”的错误提醒。
关于分析器的词法分析和语法分析具体过程和实现原理可以单独写一篇文章,在之后的文章中会给出。
五、优化器
在分析器之后,mysql就知道你要做什么了。开始执行之前,还需要优化器处理。
优化器的主要作用是在表中有多个索引的时候,决定用哪个索引;或者在一个语句有多表关联(join)的时候,决定各表的连接顺序。以下面的sql语句为例子:
select * from t1 join t2 on ID where t1.c=10 and t2.d=20;
既可以从t1中取出c等于10的记录,然后再关联到t2,然后再判断t2表中d是否等于20的行;也可以从t2中取出d等于20的记录,然后再关联到t1,然后再判断t1表中的c是否等于10 。优化器会更具上两种哪个的执行效率更高而决定用哪种方案。
对于优化器一些具体的实现方案,比如怎么选择索引等,后面会再文章中解释。
六、执行器
优化器完成以后,就要到执行器的阶段了。执行器开始执行的时候会判断你对这个表是否有查询权限(如果在之前的查询缓存返回结果,会在返回结果之前也做一次权限验证)。下面是没有权限认证的一个示例:
mysql> select * from T where ID=10;
ERROR 1142 (42000): SELECT command denied to user 'b'@'localhost' for table 'T'
当验证权限之后,就会打开表继续执行,执行器会根据表的引擎定义,去使用这个引擎提供的接口。
还是以上面例子分析,假如ID字段中没有索引,则执行的流程是这样的:
- 调用InnoDB引擎接口取这个表的第一行,判断ID值是否为10,如果不是则跳过,如果是则将 这行存在结果集中。
- 调用引擎接口,取下一行,判断相同的逻辑,直到取到最后一行。
- 执行器将上面所有的结果集返回客户端。
对于有索引的的表,执行逻辑也差不多。第一次就会执行”满足条件的第一行“接口,之后循环取“满足条件的下一行”这个接口。
在慢查询日志中,你会看见一个rows_examined
的字段,该字段会表示语句执行过程中扫描了多少行。这个值是在执行器在扫描每一行的时候累加的。
在有些场景下,执行器调用一次,在引擎内部则扫描了多行,因此引擎扫描行数rows_examined 并不是完全相同的。之后准备写一篇文章来讲存储引擎的内部机制,里面会有详细的说明。
七、总结
这篇文章介绍了mysql的逻辑架构,希望可以帮助大家对mysql的完整执行流程的各个阶段有一个初步的印象。对于每个阶段细节问题,将会在之后的篇幅中展开。
没有回复内容