天天看点

高性能mysql学习笔记--查询优化

高性能mysql

六:查询优化

为什么查询会变慢

慢查询基础:优化数据访问

1,是否向数据库请求了不想要的数据

查询不需要的数据

多表关联返回全部列

总是取出全部列

重复查询相同的数据

2,mysql是否在扫描额外的记录

查询开销的三个指标:响应时间,扫描行数,返回行数,访问类型(全表扫描,范围扫描,索引扫描)

3,重构查询方式

3.1一个复杂查询还是多个简单查询

mysql在连接和断开链接都很轻量级,在返回一个小的查询结果方面很高效,现在网络速度快,所以有时候一个复杂查询拆成多个简单查询是有必要的。

3.2切分查询

有时候对于一个大查询我们需要分而治之,将大查询切分成小查询,每个查询功能完全一样,只是完成一小部分,每次只返回一小部分查询结果。

3.3分解关联查询

对关联查询进行分解,拆成单表的简单查询,然后将查询结果在程序中进行关联。

优势:

让缓存的效率更高;

分解后,执行单个查询可以减少锁的竞争;

在程序中做关联更容易做到高性能和可扩展;

查询本身效率也会有所提升,例如用in(id)代替关联,可以让mysql按照id顺序进行查询;

减少冗余记录的查询;

更进一步,在程序中可以实现哈希关联,并不是使用mysql的嵌套循环关联,

3.4 查询执行的基础

高性能mysql学习笔记--查询优化

mysql会做缓存

3.4.1 通信协议

mysql客户端和服务器之间的通信协议是“半双共”的,这意味着在任何一个时刻,要么由服务器向客户端发送数据,要么由客户端向服务器发送数据,这两个动作不能同事发生。所以一次查询的数据包不能太大, 不然会报错,所以limit很重要,参数:max_allowed_packet配置最大数据包。

3.4.2查询状态

使用show full processlist命令(返回结果中的command列表示当前状态)

sleep:县城正在等待客户端发送新的请求

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

locked:在mysql服务层,该线程正在等待表锁,在存储引擎级别实现的锁

analyzing and statistics:线程正在收集储存引擎的统计信息,并生成查询的执行计划。

copying to tmp table[on disk]:线程正在执行查询,将结果复制到一个临时表

sorting result:线程正在对结果集进行排序

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

3.4.2查询缓存

在解析一个查询语句之前,如果缓存打开的,mysql会优先检查缓存。精确匹配缓存(区分大小写)

3.5 查询优化处理

语法解析器和预处理

查询优化器(最优执行计划)

优化类型:

重新定义关联表的顺序,

将外链接转成内链接,

使用等价变换规则,

优化count()/min()/max(),

预估并转化为常数表达式,

覆盖索引扫描,

子查询优化,

提前终止查询,

等值传播,

列表in()的比较:mysql将in()列表中的数据先进行排序,然后二分法判断,in比or快

数据和索引的统计信息:统计信息由存储引擎实现

mysql如何执行关联查询

union:先将一系列的单个查询结果放在一个临时表中,然后再重新读取临时表数据来完成union查询。

mysql关联执行的策略很简单:mysql对任何关联都执行嵌套循环关联操作,既mysql先在一个表中循环取出单条数据,然后再嵌套到下一个表中寻找匹配的行,依次下去,直到结束。然后根据各个表匹配的行,返回查询中需要的各个列,mysql会尝试在最后一个关联表中找到所有匹配的行,如果最后一个关联表无法找到更多的行以后,mysql返回到上一层关联表,看是否能够找到更多的匹配记录,依次类推。

在from遇到子查询时,先限制子查询并将其结果放在一个临时表中。

执行计划

mysql生成查询的一颗指令树,然后通过存储引擎执行完成这颗指令树并返回结果,最终的执行计划包含了重构查询的全部信息。

关联查询优化器

innor join会优先查询表中数据少的那张表,然后再关联嵌套其他表,并不是按照sql的顺序

不过有时候优化器给出的并不是最优解,可以通过straight_join来重写查询,

例子:select straight_join a,b,from table

当需要关联的表超过optimizer_search_depth(指定大小)的限制时,就会选择贪婪搜索模式,

排序优化

当有索引时用索引排序,没有的话mysql会自己排序,数据量小在内存,大在磁盘,统称为文件排序。

两种排序:

两次传输排序(旧版使用):读取行指针和需要排序的字段,对其进行排序,需要从表中读取两次数据,两次传输成本很高

单次传输排序(新版):先查询所需要的所有列,那后根据给定列进行排序

mysql在进行文件排序的时候需要使用的临时存储空间可能会比想象的要大的多,原因是在于mysql在排序时,对每一个排序记录都会分配一个足够长的定长空间来存放

在关联查询时如果需要排序,mysql会分两种情况来处理排序,如果order by字句中的所有列都来自关联的第一个表,那么mysql在关联处理第一个表的时候就进行文件排序,如果是这样,那么在mysql的explain结果中可以看到extra字段会有“using,filesort”,除次之外的所有情况,mysql都会先将关联的结果存放在一个临时表中,然后在所有关联都结束后,再进行文件排序,,在这种情况下,在expain结果的extra字段可以看到using temporary;using filsort。如果在查询中有limit的话,limit也会在排序之后应用,所以即使需要返回较少的数据,临时表盒需要排序的数据量仍然会非常大。

自己总结:所以在关联时尽量少排序,即便用了limit。

查询执行引擎

高性能mysql学习笔记--查询优化

返回结果给客户端

查询执行的最后一个阶段是将结果返回给客户端,如果查询可以被缓存,那么会将结果缓存,mysql将结果集返回客户端是一个增量,逐步返回的过程,好处:服务器无须储存太多的结果,也就不会因为返回太多结果而消耗太多内存,另外,也让mysql客户端第一时间获得返回结果,结果集中的每一行都会以一个满足通信协议的封装包发送,再通过tcp协议进行传输,在tcp传输过程中,可能对mysql的封装包进行缓存然后批量传输。

查询优化器的局限性

关联子查询

mysql的子查询实现的不好,最不好的事where条件中包含in的子查询。所以建议使用exists()等效的改写查询来获取更好的效率,

如何用好关联子查询

有时候left join 和exists效率差不多,在链表查询时由于有重复数据需要group by和distinct时,子查询exists会比链表更好,

优化关联查询

1,确保on或者using字句中的列有索引

2,确保任何的group by 和order by中的表达式只涉及一个表的列

3,当升级mysql的时候需要注意:关联语法,运算符优先级等可能会变化的地方。

优化子查询

方法是尽可能使用关联查询代替

union的限制

如果希望union的各个字句都能够根据limit只取部分结果集,或者希望能够先排好序再合并结果集的话,就需要在union的各个字句中分别使用这些字句,如果想在union之后全局进行排序取部分集时,那就要在全局加上order by 和limit

union优化

除非确实需要服务器消除重复行,否则就一定使用union all,如果没有all,mysql会给临时表加上distinct 这会导致对整个临时表数据做唯一检查,

等值传递

高性能mysql学习笔记--查询优化

总结,in列表不能太多

mysql不能并行执行和哈希关联松散索引扫描

最大值最小值优化

mysql对于最大值最小值,如果不是索引列的话,那么会进行全表扫描,办法是曲线救国,用limit来代替max()和min()

高性能mysql学习笔记--查询优化
高性能mysql学习笔记--查询优化

mysql不允许在同一个表上查询和更新

优化特定类型的查询

1,优化count()查询

1.1统计列时要求列值是非空的(不统计null)

1.2count(*)并不会像我们想象的那样扩展成所有列,实际上,他会忽略列直接统计行数,所以如果想知道结果集的行数时,最好使用count(*)

1.3 myisam的count(*)在没有where条件时是最快的,

简单的优化

高性能mysql学习笔记--查询优化

使用近似值的优化,有时候不需要精确值,所有减少where条件来count

2,优化group by 和distinct

这两个关键字字句中列最好是有索引的。

在mysql中,当无法使用索引的时候,group by使用两种策略来完成:使用临时表或者文件排序来做分组。

优化group by with rollup

分组查询的一个变种就是要求mysql对返回的分组结果再做一次超级聚合,可以使用with rollup字句来实现这种逻辑,但是效率不好,建议还是在程序中做超级聚合更好。

3,优化limit分页

延迟关联

高性能mysql学习笔记--查询优化
高性能mysql学习笔记--查询优化

使用id来进行分页

继续阅读