欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  数据库

MySQL查询执行过程

程序员文章站 2022-05-08 10:09:30
...
MySQL查询执行路径

1. 客户端发送一条查询给服务器;

2. 服务器先会检查查询缓存,如果命中了缓存,则立即返回存储在缓存中的结果。否则进入下一阶段;

3. 服务器端进行SQL解析、预处理,再由优化器生成对应的执行计划;

4. MySQL根据优化器生成的执行计划,调用存储引擎的API来执行查询;

5. 将结果返回给客户端。

MySQL查询执行过程

查询缓存(query cache)

在解析一个查询语句之前,如果查询缓存是打开的,那么MySQL会优先检查这个查询是否命中查询缓存中的数据。这个检查是通过一个对大小写敏感的哈希查找实现的。查询和缓存中的查询即使只有一个字节不同,那也不会匹配缓存结果,这种情况查询会进入下一个阶段的处理。

如果当前的查询恰好命中了查询缓存,那么在返回查询结果之前MySQL会检查一次用户权限。这仍然是无须解析查询SQL语句的,因为在查询缓存中已经存放了当前查询需要访问的表信息。如果权限没有问题,MySQL会跳过所有其他阶段,直接从缓存中拿到结果并返回给客户端。这种情况下,查询不会被解析,不用生成执行计划,不会被执行。

缓存配置参数:

mysql> show variables like 'query_cache%';

+------------------------------+---------+

| Variable_name | Value |

+------------------------------+---------+

| query_cache_limit | 1048576 |

| query_cache_min_res_unit | 4096 |

| query_cache_size | 0 |

| query_cache_type | ON |

| query_cache_wlock_invalidate | OFF |

+------------------------------+---------+

5 rows in set (0.01 sec)

query_cache_limit: MySQL能够缓存的最大结果,如果超出,则增加 Qcache_not_cached的值,并删除查询结果

query_cache_min_res_unit: 分配内存块时的最小单位大小

query_cache_size: 缓存使用的总内存空间大小,单位是字节,这个值必须是1024的整数倍,否则MySQL实际分配可能跟这个数值不同(感觉这个应该跟文件系统的blcok大小有关)

query_cache_type: 是否打开缓存 OFF: 关闭 ON: 总是打开

query_cache_wlock_invalidate: 如果某个数据表被锁住,是否仍然从缓存中返回数据,默认是OFF,表示仍然可以返回

语法解析器和预处理器

首先,MySQL通过关键字将SQL语句进行解析,并生成一棵对应的“解析树”。MySQL解析器将使用MySQL语法规则验证和解析查询。例如,它将验证是否使用错误的关键字,或者使用关键字的顺序是否正确等,再或者它还会验证引号是否能前后正确的匹配。

预处理器则根据一些MySQL规则进一步检查解析树是否合法,例如,这里讲检查数据表和数据列是否存在,还会解析名字和别名,看看它们是否有歧义。

下一步预处理器会验证权限,这通常很快,除非服务器上有非常多的权限设置。

查询优化器

现在语法树被认为合法的了,并且由优化器将其转化为执行计划。一条查询可以由很多种执行方式,最后都返回相同的结果。优化器的作用就是找到这其中最好的执行计划。

MySQL使用基于成本的优化器,它将尝试预测一个查询使用某种执行计划的成本,并选择其中成本最小的一个。最初,成本的最小单位是随机读取一个4K数据页的成本,后来成本计算公式变得更加复杂,并且引入了一些“因子”来估算某些操作的代价,如当执行一次where条件比较的成本。可以通过查询当前会话的last_query_cost的值来得知MySQL计算的当前查询的成本。

有很多种原因会导致MySQL优化器选择错误的执行计划,比如:

1. 统计信息不准确。

2. 执行计划中的成本估算不等同于实际的执行计划的成本。

3. MySQL的最优可能与你想的最优不一样。

4. MySQL从不考虑其他并发的查询,这可能会影响当前查询的速度。

5. MySQL也不是任何时候都是基于成本的优化,有时候也会基于一些固定的规则。

6. MySQL不会考虑不受其控制的成本,例如执行存储过程或者用户自定义的函数的成本。

MySQL的查询优化使用了很多优化策略来生成一个最优的执行的计划。优化策略可以分为两种,静态优化和动态优化。静态优化可以直接对解析树进行分析,并完成优化。例如优化器可以通过一些简单的代数变换将where条件转换成另一种等价形式。静态优化不依赖于特别的数值,如where条件中带入的一些常数等。静态优化在第一次完成后就一直有效,即使使用不同的参数重复查询也不会变化,可以认为是一种“编译时优化”。

相反,动态优化则和查询的上下文有关。也可能和很多其他因素有关,例如where条件中的取值、索引中条目对应的数据行数等,这些需要每次查询的时候重新评估,可以认为是“运行时优化”。

下面是一些MySQL能够处理的优化类型:

1. 重新定于关联表的顺序

2. 将外连接转化成内连接

3. 使用等价变换规则

4. 优化count()、min()和max()

5. 预估并转化为常数表达式

6. 覆盖索引扫描

7. 子查询优化

8. 提前终止查询

9. 等值传播

10. 列表in()的比较

查询执行引擎

在解析和优化阶段,MySQL将生成查询对应的执行计划,MySQL的查询执行引擎则根据这个执行计划来完成整个查询。这里执行计划是一个数据结构,而不是和很多其他的关系型数据库那样会生成对应的字节码。

相对于查询优化阶段,查询执行阶段不是那么复杂:MySQL只是简单的根据执行计划给出的指令逐步执行。在根据执行计划逐步执行的过程中,有大量的操作需要通过调用存储引擎实现的接口来完成,这些接口就是我们称为“handler API”的接口。实际上,MySQL在优化阶段就为每个表创建了一个handler实例,优化器根据这些实例的接口可以获取表的相关信息,包括表的所有列名、索引统计信息等。

返回结果给客户端

查询执行的最后一个阶段是将结果返回给客户端。即使查询不需要返回结果给客户端,MySQL仍然会返回这个查询的一些信息,如查询影响到的行数。

如果查询可以被缓存,那么MySQL在这个阶段,会将结果存放到查询缓存中。

MySQL将结果返回客户端是一个增量、逐步返回的过程。例如,在关联表操作时,一旦服务器处理完最后一个关联表,开始生成第一条结果时,MySQL就可以开始向客户端逐步返回结果集了。

这样处理有两个好处:服务器无需存储太多的结果,也就不会因为要返回太多的结果而消耗太多的内存。另外,这样的处理也让MySQL客户端第一时间获得返回的结果。

结果集中的每一行都会以一个满足MySQL客户端/服务器通信协议的封包发送,再通过TCP协议进行传输,在TCP传输过程中,可能对MySQL的封包进行缓存然后批量传输。