本文将会深入的扒一扒msyql查询的流程,
也会讲到一些mysql术语,如:缓存,语法解析器,预处理,执行计划,查询优化器,
另外还会讲一讲mysql复杂查询的基础:"关联"。
查询缓存:
在解析一个查询语句之前,如果查询缓存是打开的,那么mysql会优先检查这个查询是否命中查询缓存中的数据
查询优化处理:
查询的生命周期的下一步是将一个sql转换成一个执行计划,mysql再依照这个执行计划和存储引擎进行交互。
其中包含多个子阶段:解析sql,预处理,优化sql执行计划。
这个过程中任何错误都将终止查询。
语法解析器和预处理:
首先,mysql通过关键字将sql语句进行解析,并生产一棵对应的“解析树”。
mysql解析器将使用mysql语法规则验证和解析查询。
预处理器则根据一些mysql规则进一步检验解析树是否合法。
查询优化器:
在这一步的时候,语法树已经被认为合法了,并且由优化器将其转换为执行计划。
一条查询可以有很多中执行方式,最后都返回相同的结果。
优化器的作用就是找到这其中最好的执行计划。
mysql使用基于成本的优化器,它将尝试预测一个查询使用某种执行计划时的成本,并选择其中成本最小的一个。
可以通过查询当前对话的last_query_cost的值来得知mysql计算的当前成本。
它是根据一系列的统计信息计算得来的:每个表或者索引的页面个数,索引的数(索引中不通值的数量)
索引和数据行的长度,索引分布情况。优化器在评估成本的时候并不考虑任何层面的缓存,它假设读取任何数据都需要一次磁盘IO
有很多原因导致mysql优化器选择错误的执行计划。
1 统计信息不准确
2 执行计划中的成本估算不等同是你执行的成本。
3 mysql的最优可能和你想的最优不一样。
4 mysql从不考虑其他并发执行的查询,这可能会影响当前查询的速度。
5 mysql也并不是任何时候都基于成本的优化,有时也会基于一些固定的规则。
6 mysql不会考虑不受气控制而操作的成本。
7 优化有时候无法估算所有可能的执行计划,可能它错过实际上最优的计划。
mysql的查询优化器优化策略主要分为两种:
静态优化, 可以直接对解析树进行分析,并完成优化。又称"编译时优化"
动态优化, 动态优化则和上下文相关,也可能和很多因素有关,例如where条件中的取值,索引中条目对应的数据行数等。又称"运行时优化"
mysql能够优化的类型:
1 .重新定义关联表的顺序
2. 将外连接转换为内连接
3. 使用等价替换原则。 mysql可以使用一些等价变换来简化并规范表达式。它可以合并和减少一些比较,还可以移除一些恒成立和一些恒不成立的判断。
4. 优化count(),min(),max() 例如要找到某一列的最小值,只需要查询对应B-Tree索引最左端的纪录,mysql可以直接获取索引的第一行纪录。
如果mysql使用了这种优化,将会在explain中看到 Extra:"select tables optimized away"
5 .预估并转化常数表达式
6. 覆盖索引扫描
7. 子查询优化
8. 提前终止查询
9. 等值传播
10.列表IN()的比较
数据和索引的统计信息:
统计信息由存储引擎实现,不同的存储引擎可能会存储不同的统计信息。
因为服务器层没有任何统计信息,所以mysql查询优化器在生成查询的执行计划是,
需要向存储引擎获取相应的统计信息。存储引擎则提供给优化器对应的统计信息,包括:
每个表或者索引又多少个页面,每个表的每个索引的基数是多少,数据行和索引长度,
索引的分布信息等。优化器根据这些信息来选择一个最优的执行计划。
mysql的关联查询策略:
mysql中的”关联“术语并不是我们通常意义理解的关联,它的意义更为广泛。
总之,mysql认为不是仅仅是在两张表查询匹配行的查询才是“关联”,
而是所有的查询都是“关联”。
总之,理解mysql执行”关联“的流程是非常重要的。
我们先来看一个UNION查询的例子。对于union查询,mysql先将一系列的单个查询结果
放在一个临时表内,然后再读出来返回。在mysql的概念中,每个查询都是一次关联,
所以读取临时表也是一次关联。
当前mysql关联执行的策略很简单:mysql对任何关联都执行嵌套循环关联操作,即mysql先在
一个表中循环取出单条数据,然后再嵌套循环到下一个表中寻找匹配的行,以此类推,直到
找到所有表中匹配的行为止。然后根据各个表匹配的行,返回查询中需要的各个列。
按照这样的方式查找第一个表记录,再嵌套查询下一个关联表,然后回溯到上一个表,
在mysql中是通过嵌套循环实现---正如其名“嵌套循环关联”。请看下面的例子中的简单查询:
mysql -> SELECT tbl1.col1, tbl2.col2 -> FROM tbl1 INNER JOIN tbl2 USING(col3) -> WHERE tbl1.col1 IN(5,6);
假设mysql按照查询中表的顺序进行关联操作,我们则可以用下面的伪代码表示mysql
如何完成这个查询:
outer_iter = iterator over tbl1 where col1 IN(5,6) outer_row = outer_iter.next while outer_row inner_iter = iterator over tbl2 where col3 = outer_row.col3 inner_row = inner_iter.next while inner_row output [ outer_row.col1, inner_row.col2 ] inner_row = inner_iter.next end outer_row = outer_iter.next end
上面的执行计划对于单表查询和多表关联查询都适用,如果是一个单表查询,那么只需要
完成上面外层的基础操作。对于外连接上面的执行过程仍然适用。如:
mysql -> SELECT tbl1.col1, tbl2.col2 -> FROM tbl1 LEFT OUTER JOIN tbl2 USING(col3) -> WHERE tbl1.col1 IN(5,6);
伪代码如下:
outer_iter = iterator over tbl1 where col1 IN(5,6) outer_row = outer_iter.next while outer_row inner_iter = iterator over tbl2 where col3 = outer_row.col3 inner_row = inner_iter.next if inner_row while inner_row output [ outer_row.col1, inner_row.col2 ] inner_row = inner_iter.next end else output [ outer_row.col1, NULL ] end outer_row = outer_iter.next end
另一种可视化查询执行计划的方法是根据优化器执行的路径绘制出对应的泳道图。如图:
执行计划:
mysql会生成查询的一颗指令树,然后通过存储引擎执行完成这颗指令树并返回结果。
最终的执行计划包含重构查询的全部信息。
如果对某个查询 执行 explain extended后,再执行show warnings 就可以看到重构后的查询。例:
explain extended
select * from activity; show warnings;
任何多表查询都可以使用一棵树表示,mysql执行查询的方式,并不是一颗平衡树,
正如刚刚介绍的,mysql总是从一个表开始一直嵌套循环,回溯完成所有表关联。
所以,mysql的执行计划总是如图所示,是一颗左侧深度优先的树。
从本质上来讲,mysql对所有类型的都是以同样的方式运行。例如,mysql在FROM子句遇到子查询时,先执行子查询的并将其结果集放到一个临时表,
然后将这个表当做普通表对待。mysql在执行union查询时也适用类似的临时表,在遇到右外连接的时候会改写为等价的左外链接。简而言之,
当前版本的mysql会将所有的查询类型都转换成类似的执行计划。