MySQL查询过程 :mysql

客户端/服务端通讯协议
MySQL客户端/服务端通讯协议是“半双工”的:在任一时刻,要么是服务器向客户端发送数据,要么是客户端向服务器发送数据,这两个动做不能同时发生。一旦一端开始发送消息,另外一端要接收完整个消息才能响应它,因此咱们没法也无须将一个消息切成小块独立发送,也没有办法进行流量控制。
客户端用一个单独的数据包将查询请求发送给服务器,因此当查询语句很长的时候,须要设置max_allowed_packet参数。可是须要注意的是,若是查询实在是太大,服务端会拒绝接收更多数据并抛出异常。
与之相反的是,服务器响应给用户的数据一般会不少,由多个数据包组成。可是当服务器响应客户端请求时,客户端必须完整的接收整个返回结果,而不能简单的只取前面几条结果,而后让服务器中止发送。于是在实际开发中,尽可能保持查询简单且只返回必需的数据,减少通讯间数据包的大小和数量是一个很是好的习惯,这也是查询中尽可能避免使用SELECT *以及加上LIMIT限制的缘由之一。
查询缓存sql
在解析一个查询语句前,若是查询缓存是打开的,那么MySQL会检查这个查询语句是否命中查询缓存中的数据。若是当前查询刚好命中查询缓存,在检查一次用户权限后直接返回缓存中的结果。这种状况下,查询不会被解析,也不会生成执行计划,更不会执行。
MySQL将缓存存放在一个引用表(不要理解成table,能够认为是相似于HashMap的数据结构),经过一个哈希值索引,这个哈希值经过查询自己、当前要查询的数据库、客户端协议版本号等一些可能影响结果的信息计算得来。因此两个查询在任何字符上的不一样(例如:空格、注释),都会致使缓存不会命中。
若是查询中包含任何用户自定义函数、存储函数、用户变量、临时表、mysql库中的系统表,其查询结果
都不会被缓存。好比函数NOW()或者CURRENT_DATE()会由于不一样的查询时间,返回不一样的查询结果,再好比包含CURRENT_USER或者CONNECION_ID()的查询语句会由于不一样的用户而返回不一样的结果,将这样的查询结果缓存起来没有任何的意义。
既然是缓存,就会失效,那查询缓存什么时候失效呢?MySQL的查询缓存系统会跟踪查询中涉及的每一个表,若是这些表(数据或结构)发生变化,那么和这张表相关的全部缓存数据都将失效。正由于如此,在任何的写操做时,MySQL必须将对应表的全部缓存都设置为失效。若是查询缓存很是大或者碎片不少,这个操做就可能带来很大的系统消耗,甚至致使系统僵死一下子。并且查询缓存对系统的额外消耗也不只仅在写操做,读操做也不例外:
- 任何的查询语句在开始以前都必须通过检查,即便这条SQL语句永远不会命中缓存
- 若是查询结果能够被缓存,那么执行完成后,会将结果存入缓存,也会带来额外的系统消耗
基于此,咱们要知道并非什么状况下查询缓存都会提升系统性能,缓存和失效都会带来额外消耗,只有当缓存带来的资源节约大于其自己消耗的资源时,才会给系统带来性能提高。但要如何评估打开缓存是否可以带来性能提高是一件很是困难的事情,也不在本文讨论的范畴内。若是系统确实存在一些性能问题,能够尝试打开查询缓存,并在数据库设计上作一些优化,好比:
- 用多个小表代替一个大表,注意不要过分设计
- 批量插入代替循环单条插入
- 合理控制缓存空间大小,通常来讲其大小设置为几十兆比较合适
- 能够经过SQL_CACHE和SQL_NO_CACHE来控制某个查询语句是否须要进行缓存
最后的忠告是不要轻易打开查询缓存,特别是写密集型应用。若是你实在是忍不住,能够将query_cache_type设置为DEMAND,这时只有加入SQL_CACHE的查询才会走缓存,其余查询则不会,这样能够很是自由地控制哪些查询须要被缓存。
语法解析和预处理数据库
MySQL经过关键字将SQL语句进行解析,并生成一颗对应的解析树。这个过程解析器主要经过语法规则来验证和解析。好比SQL中是否使用了错误的关键字或者关键字的顺序是否正确等等。预处理则会根据MySQL规则进一步检查解析树是否合法。好比检查要查询的数据表和数据列是否存在等等。
查询优化缓存
通过前面的步骤生成的语法树被认为是合法的了,而且由优化器将其转化成查询计划。多数状况下,一条查询能够有不少种执行方式,最后都返回相应的结果。优化器的做用就是找到这其中最好的执行计划。
MySQL使用基于成本的优化器,它尝试预测一个查询使用某种执行计划时的成本,并选择其中成本最小的一个。在MySQL能够经过查询当前会话的last_query_cost的值来获得其计算当前查询的成本。
mysql> select * from t_message limit 10; ...省略结果集 mysql> show status like 'last_query_cost'; +-----------------+-------------+ | Variable_name | Value | +-----------------+-------------+ | Last_query_cost | 6391.799000 | +-----------------+-------------+
示例中的结果表示优化器认为大概须要作6391个数据页的随机查找才能完成上面的查询。这个结果是根据一些列的统计信息计算得来的,这些统计信息包括:每张表或者索引的页面个数、索引的基数、索引和数据行的长度、索引的分布状况等等。
有很是多的缘由会致使MySQL选择错误的执行计划,好比统计信息不许确、不会考虑不受其控制的操做成本(用户自定义函数、存储过程)、MySQL认为的最优跟咱们想的不同(咱们但愿执行时间尽量短,但MySQL值选择它认为成本小的,但成本小并不意味着执行时间短)等等。
MySQL的查询优化器是一个很是复杂的部件,它使用了很是多的优化策略来生成一个最优的执行计划:
- 从新定义表的关联顺序(多张表关联查询时,并不必定按照SQL中指定的顺序进行,但有一些技巧能够指定关联顺序)
- 优化MIN()和MAX()函数(找某列的最小值,若是该列有索引,只须要查找B+Tree索引最左端,反之则能够找到最大值,具体原理见下文)
- 提早终止查询(好比:使用Limit时,查找到知足数量的结果集后会当即终止查询)
- 优化排序(在老版本MySQL会使用两次传输排序,即先读取行指针和须要排序的字段在内存中对其排序,而后再根据排序结果去读取数据行,而新版本采用的是单次传输排序,也就是一次读取全部的数据行,而后根据给定的列排序。对于I/O密集型应用,效率会高不少)
查询执行引擎服务器
在完成解析和优化阶段之后,MySQL会生成对应的执行计划,查询执行引擎根据执行计划给出的指令逐步执行得出结果。整个执行过程的大部分操做均是经过调用存储引擎实现的接口来完成,这些接口被称为handler API。查询过程当中的每一张表由一个handler实例表示。实际上,MySQL在查询优化阶段就为每一张表建立了一个handler实例,优化器能够根据这些实例的接口来获取表的相关信息,包括表的全部列名、索引统计信息等。存储引擎接口提供了很是丰富的功能,但其底层仅有几十个接口,这些接口像搭积木同样完成了一次查询的大部分操做。
返回结果给客户端
查询执行的最后一个阶段就是将结果返回给客户端。即便查询不到数据,MySQL仍然会返回这个查询的相关信息,好比该查询影响到的行数以及执行时间等等。
若是查询缓存被打开且这个查询能够被缓存,MySQL也会将结果存放到缓存中。
结果集返回客户端是一个增量且逐步返回的过程。有可能MySQL在生成第一条结果时,就开始向客户端逐步返回结果集了。这样服务端就无须存储太多结果而消耗过多内存,也可让客户端第一时间得到返回结果。须要注意的是,结果集中的每一行都会以一个知足①中所描述的通讯协议的数据包发送,再经过TCP协议进行传输,在传输过程当中,可能对MySQL的数据包进行缓存而后批量发送。
回头总结一下MySQL整个查询执行过程,总的来讲分为6个步骤:
- 客户端向MySQL服务器发送一条查询请求
- 服务器首先检查查询缓存,若是命中缓存,则马上返回存储在缓存中的结果。不然进入下一阶段
- 服务器进行SQL解析、预处理、再由优化器生成对应的执行计划
- MySQL根据执行计划,调用存储引擎的API来执行查询
- 将结果返回给客户端,同时缓存查询结果