mysql执行sql的过程

当希望MySQL能够以更高的性能运行查询时,最好的办法就是弄清楚MySQL是如何优化和执行查询的。

image.png

 客户端发送了一条查询给服务器

 服务器先检查缓存,如果命中了缓存,则立刻返回存储在换种的结果,否则就进入下一阶段

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

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

 缓存结果然后返回给客户端

1  客户端/服务端通信协议

每个客户端连接都会在服务器中拥有一个线程,这个连接的查询只会在这个单独的线程中执行,该线程只能轮流在某个CPU核心或者CPU中运行

 

MySQL客户端/服务端通信协议是“半双工”的:在任一时刻,要么是服务器向客户端发送数据,要么是客户端向服务器发送数据,这两个动作不能同时发生。一旦一端开始发送消息,另一端要接收完整个消息才能响应它,所以我们无法也无须将一个消息切成小块独立发送,也没有办法进行流量控制。

客户端用一个单独的数据包将查询请求发送给服务器,所以当查询语句很长的时候,需要设置max_allowed_packet=4194304(4MB) 参数。

但是需要注意的是,如果查询实在是太大,服务端会拒绝接收更多数据并抛出异常.

而服务器响应应用给用户的数据通常会很多,有多个数据包组成。但是当服务器响应客户端请求时,客户端必须完整的接收整个返回结果,而不能简单的只取前面几条结果,然后让服务器停止发送。因而在实际开发中,尽量保持查询简单且只返回必需的数据,减小通信间数据包的大小和数量是一个非常好的习惯,这也是查询中尽量避免使用SELECT *以及加上LIMIT限制的原因之一。

其实在客户端发出请求后,是由服务器端推送数据过来的。客户端 像是从消防水管中喝水。。。

  查询状态:

 一个线程,任何时刻都有一个状态;用show full processlist命令可以查看当前的状态。

Sleep 线程正在等待客户端发送新的请求。

Query 线程正在执行查询或者正在将结果发送给客户端。

Locked 在mysql服务器层,该线程正在等待表锁。在存储引擎级别实现的锁,不会体现在线程状态中。

Analyzing and statistics

线程正在收集村粗引擎的统计信息,并生成查询的执行计划

Coping to tmp table [on disk]

线程正在执行查询,并且将其结果集都复制到一个临时表中,这种状态一般要么是在做Group by 操作,要么是文件排序操作,或者是UNION操作。如果这个状态后面还有on disck,那么表示mysql正在将一个内存临时表放到磁盘上。

Sorting result

线程正在对结果集进行排序。

Sending data

线程可能在多个状态之间传送数据,或者在生成结果集,或者在向客户端返回数据。

that you know who hold the ball...

2 查询缓存

在解析一个查询语句前,如果查询缓存是打开的,那么 MySQL 会检查这个查询语句是否命中查询缓存中的数据。这个检查是通过一个对大小写敏感的哈希查找实现的。即使只有一个字符不一样,也利用不到缓存。

如果当前查询恰好命中查询缓存,在检查一次用户权限后直接返回缓存中的结果。这种情况下,查询不会被解析,也不会生成执行计划,更不会执行。

如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、MySQL 库中的系统表,其查询结果都不会被缓存。

比如函数 NOW() 或者 CURRENT_DATE() 会因为不同的查询时间,返回不同的查询结果。

再比如包含 CURRENT_USER 或者 CONNECION_ID() 的查询语句会因为不同的用户而返回不同的结果,将这样的查询结果缓存起来没有任何的意义。

既然是缓存,就会失效,那查询缓存何时失效呢?MySQL 的查询缓存系统会跟踪查询中涉及的每个表,如果这些表(数据或结构)发生变化,那么和这张表相关的所有缓存数据都将失效。

正因为如此,在任何的写操作时,MySQL 必须将对应表的所有缓存都设置为失效。

如果查询缓存非常大或者碎片很多,这个操作就可能带来很大的系统消耗,甚至导致系统僵死一会儿。

而且查询缓存对系统的额外消耗也不仅仅在写操作,读操作也不例外:

任何的查询语句在开始之前都必须经过检查,即使这条 SQL 语句永远不会命中缓存。

如果查询结果可以被缓存,那么执行完成后,会将结果存入缓存,也会带来额外的系统消耗。

基于此,我们要知道并不是什么情况下查询缓存都会提高系统性能,缓存和失效都会带来额外消耗,只有当缓存带来的资源节约大于其本身消耗的资源时,才会给系统带来性能提升

3   语法解析和预处理

MySQL 通过关键字将 SQL 语句进行解析,并生成一棵对应的解析树。这个过程解析器主要通过语法规则来验证和解析。比如 SQL 中是否使用了错误的关键字或者关键字的顺序是否正确,验证引号是否能前后正确匹配。

预处理则会根据 MySQL 规则进一步检查解析树是否合法。比如检查要查询的数据表和数据列是否存在等等。

 查询优化:

经过前面的步骤生成的语法树被认为是合法的了,并且由优化器将其转化成查询计划。

多数情况下,一条查询可以有很多种执行方式,最后都返回相应的结果。优化器的作用就是找到这其中最好的执行计划。

MySQL 使用基于成本的优化器,它尝试预测一个查询使用某种执行计划时的成本,并选择其中成本最小的一个。

SELECT

mnt.user_id,

mnt.items_name,

mem.user_name,

mem.mobile,

mbl.company_name

FROM

`nvip_tags` AS mnt

INNER JOIN member AS mem ON mnt.user_id = mem.user_id

LEFT JOIN member_bind_list AS mbl ON mnt.user_id = mbl.user_id;

show status like 'Last_query_cost';

Last_query_cost 4.399000

表示mysql优化器认为大概需要做4个数据页的随机查找才能完成上面的查询;

这个结果是根据一些列的统计信息计算得来的,这些统计信息包括:每张表或者索引的页面个数、索引的基数、索引和数据行的长度、索引的分布情况等等。

有非常多的原因会导致 MySQL 选择错误的执行计划,比如统计信息不准确、不会考虑不受其控制的操作成本(用户自定义函数、存储过程)。

MySQL 认为的最优跟我们想的不一样(我们希望执行时间尽可能短,但 MySQL 值选择它认为成本小的,但成本小并不意味着执行时间短)等等

提前终止查询:

添加上limit N

列表IN()的比较:

很多数据库中,In完全等同于多个OR条件的字句,因为这两者是完全等价的。

在mysql中这点不成立,mysql将in()列表中的数据先进行排序,然后通过二分查找的方式来确定列表中的值是否满足条件,这是一个O(log n) 复杂度的操作,等价的转成Or查询复杂度为O(n) ,对于IN()列表中有大量的取值的时候,MYSQL的处理速度将会更快。

在完成解析和优化阶段以后,MySQL 会生成对应的执行计划,查询执行引擎根据执行计划给出的指令逐步执行得出结果。

查询执行引擎

在完成解析和优化阶段以后,mysql会生成对应的执行计划,查询执行引擎根据执行计划来完成整个查询。这里的执行计划是一个数据结构。

整个执行过程的大部分操作均是通过调用存储引擎实现的接口来完成,这些接口被称为 handler API

查询过程中的每一张表由一个 handler 实例表示。实际上,MySQL 在查询优化阶段就为每一张表创建了一个 handler 实例,优化器可以根据这些实例的接口来获取表的相关信息,包括表的所有列名、索引统计信息等

返回结果给客户端

查询执行的最后一个阶段就是将结果返回给客户端。即使查询不到数据,MySQL 仍然会返回这个查询的相关信息,比如该查询影响到的行数以及执行时间等等

如果查询缓存被打开且这个查询可以被缓存,MySQL 也会将结果存放到缓存中

结果集返回客户端是一个增量、逐步返回的过程。有可能 MySQL 在生成第一条结果时,就开始向客户端逐步返回结果集了。

这样服务端就无须存储太多结果而消耗过多内存,也可以让客户端第一时间获得返回结果。

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

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注