6 查询性能优化
6.1为什么查询会变慢
这里说明了的查询执行周期,从客户端到服务器端,服务器端解析,优化器生成执行计划,执行(可以细分,大体过程可以通过show profile查看),从服务器端返回客户端结果。
而执行部分作为最重要的一环,需要做的事情比较多,而不合适的query往往让执行过程做了不必要的操作,或者不能使用更优秀的底层数据结构,从而用时更久。
6.2慢查询基础:优化数据访问
访问数据量多大,超过实际所需是慢查询的一个原因。导致这种情况的原因大致有两个
1.应用程序向mysql服务要求的了超过需要的,或重复的数据,比如select * 。
2.mysql服务器执行过程中访问了超过需要的数据。
我们先来看第一种原因
6.2.1向数据库请求了超过需要的数据
1.请求了超过需要的行,如没有使用limit,请求了100行数据,但是只使用了10行。
2.使用select * 返回了不需要的列数据,尤其是多表关联查询是情况更突出,应该做的是只返回我们需要的列。
3.客户端如果使用了缓存,并且该部分缓存被接下来的数据需求命中,那么就没有必要再向服务器发送数据请求。
6.2.2mysql扫描了额外的纪录
慢查询日志中的三个衡量查询开销的指标:1.响应时间,2。扫描行数,3.返回行数
1.响应时间
响应时间包括服务时间与排队时间。
响应时间在不同的服务器环境,应用环境下都会不同。文中提到使用‘快速上限法’(中文版译名):通过query的执行计划,计算需要的顺序io与随机io次数,再结合当前环境下一次io的时间。最后做一次加总可以获得一个‘参考值‘。(可以看出来,这样的一种方法实操性并不强,因为并不是每一个query我们都能相对准确的估算io次数,不同服务器环境下,甚至统一服务器的不同时间,io的效率也会不同。 不过这里我们可以看出来,io是执行过程中相对比较耗时的一步)
2.扫描行数与返回行数
扫描行数与返回行数单独都是不能体现出任何query的效率的,只有两者的比值才能说明该query筛选数据的能力。
3.扫描行数与访问类型
explain 中的type列列出了访问类型:全表扫;索引扫描;范围扫描;单值查询
6.3重构查询的方式
6.3.1一个复杂的查询还是多个简单的查询
由于mysql的网络协议是‘半双工’的,连接断开的花费并不大,所以很多时候分解查询是优化的sql的一个有效方式。
6.3.2切分查询
切分查询是指对大的查询采取分而治之的策略,分时段分但服务器端的压力。但次执行分段的sql都是一样的,只是通过do_query()返回的数据与limit实现对数据条目的分批处理。
6.3.3分解关联查询
将关联查询分解成多个sql步骤。
这样做是减小事务的粒度,能够缓解锁的争用,也能让缓存的数据更加模块化,易于使用缓存。
6.4查询的执行步骤
客户端请求数据-->服务器端查询缓存,命中返回,未命中-->mysql解析sql,预编译,生成执行计划-->调用数据库引擎api执行sql-->返回结果到客户端
6.4.1mysql通信协议
‘半双工’。即同一时间,只有一方在发送数据,只有在接受完对方发送的数据,才可以向对方发出响应。
这里提到了客户端获取数据的两种方式,在c#一种是dateset,即一次性返回数据,在客户端保存数据快照(缓存),一种dateread是保持客户端与服务器端的连接,客户端随时查询数据。--不同的语言提供了不同的调用方式。
查询状态
指的是查询的进程的执行状态:需要注意的是sort result对结果集排序;sanding date表示自生成结果集或者在向客户端返回数据。
6.4.2查询缓存
就是查询步骤中的第二步查询的结构。第七章中有介绍。
6.4.3查询优化
即是查询步骤中的第三步
1.语言解析器与预处理器
解析器对语法进行检查,预处理器检查语义并检查用户是否有权限。
2.查询优化器
优化器对预处理器生成的预处理树进行执行计划的测算,计算各种执行计划的花费(有前面的知识我们知道,这里的花费往往指的是查询需要访问的数据页的多少)
可以明白:优化器对成本的测算并不是啊完全准确的,但是对于大多数sql,io都是它的执行瓶颈所在。
注意优化器并不会考虑缓存(有缓存的话不应该第二步就命中,返回给客户端了吗。
优化有静态优化与动态优化的区分,考虑到第七章查询缓存部分与存储过程的描述,我在考虑mysql是否保存执行计划呢?如何保存sql的执行计划的呢?静态动态优化时二选一还是并存呢?
书中列出了10多中优化器能够优化的查询,我不在一一列举。
- 数据索引的统计信息
Myisam引擎保存了count,而innodb只能提供估算值(innodb的数据与索引是纠缠的,插入删除修改还可能导致页分裂,页合并等问题,所以保存一个count之类的统计值维护不易)
4.关联查询的执行
Mysql的关联查询可以看做一个双循环或者多循环,只有两个表的关联查询,外侧循环的表叫外表,内侧循环的表叫内表。可以知道内侧循环的查询次数是远多于外侧的,所以内表非常需要一个合适的索引。
5.关联查询优化器
但是内外表常常不是我们可以决定的,mysql优化器会在其中起作用,优化器会建立一颗深度优先树,通过不同的组合得出花费最少的执行计划。不过在关联的表n过多时执行计划树的颗数会指数上升,n超过optimizer_search_depth就不再使用穷举的方式了,而是使用别的搜索方式获取最优执行计划。
上一段中mysql对关联查询的执行方式是嵌套循环,执行计划表现为一颗深度优先树。
- 排序优化
第三章中介绍了索引排序,这里介绍filesort。
Filesort文件排序,filesort并不总是要用到磁盘,当数据量较小时可以再内存中进行。
数据量是否大于文件缓冲区是两者的分水岭。当使用磁盘排序是相当于外排序。
Mysql filesort排序策略
两次读取数据(旧版):只使用行指针与排序字段做排序,这样排序结果需要通过行指针才能读取到全部数据,由于是随机io,io效率低。
单次读取数据(新版):先读取所需要的列,然后在根据给定列进行排序。
两个各有优劣,这个讨论我们放在第8章中
5.6版本在这里做出了改进,当使用limit时,mysql不再对所有结果排序,而是仅对需要的数据排序。
6.4.4查询执行引擎
Mysql的执行计划是一个数据结构,执行该执行计划时,需要调用存储引擎是响应的‘handler api’实现。如果是所有存储过程共有的特性,一般是服务器层实现的,如日期函数,视图,触发器。
6.4.5返回数据
Mysql执行sql过程中,开始生成第一条结果时就开始返回数据,这样是的服务器不需要保存大量的结果。(往后看可以发现union会到这个需要新建一个临时表来保存数据,前部union段执行完成才能返回数据)