/* * -------------------------------------------------------- * 高性能MySQL-3rd-Baron Schwartz-笔记 * 第六章 查询性能优化 */ --------------------------------------------------------
======================================================mysql
6.1 为何查询速度会变慢
算法
查询有生命周期大体顺序:从客户端,到服务器,而后在服务器上进行解析,生成执行计划,执行,返回结果给客户端,其中执行是最重要的阶段。包括了大量的检索数据到存储引擎的调用,调用后的数据处理,分组和排序。sql
查询在每一个部分都会花费时间,包括网络、CPU计算、生成执行计划、锁等待(互斥等待),尤为是向存储引擎调用操做,这些调用须要在内存操做、在CPU操做、内存不足时致使I/O操做。编程
了解以上就可能知道查询速度会变慢的思考角度。缓存
======================================================性能优化
6.2 慢查询基础:优化数据访问服务器
优化数据访问,就是优化访问的数据,操做对象是要访问的数据,两方面,是否向服务器请求了大量不须要的数据,二是是否逼迫MySQL扫描额外的记录(没有必要扫描)。网络
请求不须要数据的典型案例:不加LIMIT(返回所有数据,只取10条)、多表关联Select * 返回所有列(多表关联查询时*返回多个表的所有列)、仍是Select *(可能写程序方面或代码复用方面有好处,但还要权衡)、重复查询相同数据(真须要这样,能够缓存下来,移动开发这个颇有必要本地存储)。函数
标志额外扫描的三个指标:响应时间(本身判断是否合理值)、扫描的行数、返回的行数,通常扫描行数>返回行数。性能
扫描的行数须要与一个“访问类型”概念关联,就是 Explain 中的 type,explain的type结果由差到优分别是:ALL(全表扫描)、index(索引扫描)、range(范围扫描)、ref(惟一索引查询 key_col=xx)、const(常数引用)等。从“访问类型”能够明白,索引让 MySQL 以最高效、扫描行数最少的方式找到须要的记录。
书中有个例子,说明在where中使用已经是索引的列和取消该列的索引后两种结果,type由ref变为All,预估要访问的rows从10变为5073,差别很是明显。
======================================================
6.3 重构查询的方式
第一:将一个复杂查询拆分为数个小且简单的查询,数据返回也快。
第二:切分查询,如删除10万条数据,能够切分为10次,每次删除1万条。
第三:分解关联查询:
以上作法好处是,充分利用前一步缓存,减小锁竞争,in(123, 456,...)也更高效,减小冗余记录,等等。
======================================================
6.4 查询执行的基础(知识)
MySQL执行查询执行路径,以下图,关键要解释的是 客户端发送请求,若是查询缓存有结果,则直接返回。
客户端/服务器通信协议,具体细节不关注,只知道它是“半双工”工做,要么客户端向服务器发送数据,要么服务器向客户端发送数据,两个动做不会同时发生;另外,发送数据都只有发送完成后才能动做,这就是为何要加LIMIT。
另外,注意当客户端从服务器获取数据时,看起来是从服务器获取数据,其实是从库函数的缓存中获取数据,想一想PHP的 mysql_query(),此时数据已经到了PHP的缓存中,而mysql_unbuffered_query()不会缓存结果。
MySQL中的关联(join)查询,整体来讲,MySQL认为任何一个查询都是一次关联,不光是查询两个表匹配才叫关联。因此,理解MySQL如何执行关联查询相当重要。MySQL的关联是:嵌套循环关联,举例以下:
结合书中关于多表关联的案例,参考一个实际例子 《MySQL SQL优化之 STRAIGHT_JOIN》 全面介绍优化过程。
排序优化,无论怎么样,从性能角度,应该尽量避免排序,或者尽量避免对大量数据进行排序。第三章讲了索引排序,快速,当不能直接使用索引时,MySQL就会本身进行排序,数据量小时在内存中排序,数据量大时使用到磁盘。量小于“排序缓冲区”时,MySQL使用内存进行“快速排序”。若是内存不够,MySQL先将数据分块,每块使用快速排序,而后将各块结果放在硬盘上,而后合并(merge),最后返回排序结果。
注意:MySQL排序过程统称为文件排序(filesort),概念上的,即便排序发生在内存,而不是磁盘文件中。
MySQL有两种排序算法,两次传输排序(旧版)、单次传输排序(新版)。两种各有各的最好和最差的应用场景,注意 max_length_for_sort_data 是临界值,不超过期使用单次传输,超过使用两次传输。MySQL自动判断,具体参考第八章中“文件排序优化”。
两次传输排序(旧版),读取行指针和须要排序的字段,对其进行排序,而后再根据排序结果读取所须要的数据行。显然是两次传输,特别是读取排序后的数据时(第二次)大量随机I/O,因此两次传输成本高。
单次传输排序(新版),一次读取出全部须要的或SQL查询指定的列,而后根据排序列,排序,直接返回排序后的结果。顺序I/O,缺点:若是列多,额外占用空间。
注意:MySQL排序时使用的空间比想象大不少,为何?由于MySQL要为每个排序记录分配足够长的空间存放,VARCHAR满长度(声明的完整长度),使用UTF8字符集时,为每一个字符预留3个字节。因此会很大!
结合关联查询,排序会更复杂。若是ORDER BY排序列都在第一个表(驱动表),那么在关联处理时,先对驱动表排序,Explain结果中Extra会有Using filesort;除此以外全部状况,都会在关联结束后,将结果放在临时表中进行最终排序,Extra中会有Using temporary;Using filesort。若是还有LIMIT,也会在排序后应用。能够,排序须要的空间大!
注意:MySQL5.6之后,有所优化,若是有LIMIT会只排序须要的,而不是全部,抛弃不知足条件的结果。
查询执行引擎,相对于查询优化,查询执行简单些了,MySQL只根据执行计划输出的指令逐步执行。指令都是调用存储引擎的API来完成,通常称为 handler API,实际上,MySQL优化阶段为每一个表都建立了一个 handler 实例,(相似于VC++编程中的句柄?),用 handler 实例获取表的信息(列名、索引统计信息等)。
注意:存储引擎接口不丰富,底层仅几十个,但功能丰富!如某接口实现了查询第一行,又有一个接口实现了查询下一行,有了这两个就能够全表扫描了!
返回结果给客户端,有结果集返回结果集,没结果,返回影响的行数。通常MySQL也会将这个结果缓存下来,存放到查询缓存中。
注意:MySQL返回结果是一个增量、逐步返回的过程,例如,关联操做中,当一个嵌套循环处理到最后一个关联表,并开始生成第一条结果时,MySQL就能够开始向客户端逐步返回结果集了。好处:服务器端无须存储太多结果,也不会由于返回的结果太多而消耗太多内存,也使客户端第一时间得到返回结果。结果是以TCP协议封包发送的,TCP的传输过程,可能会对封包进行缓存而后批量发送。
======================================================
6.5 MySQL查询优化器的局限性
不熟悉 JOIN USING 和 JOIN ON 的请看 红薯做品 MySQL 三种关联查询的方式: ON vs USING vs 传统风格
一个是关联子查询,没看明白,回来再读。
一个UNION限制,没法将限制条件从外层下推到内层,改造例子以下
等值传递:讲的IN列表,MySQL会将IN列表的值传到各个过滤子句,若是IN列表太大,会形成额外消耗,优化和执行都很慢。
并行执行,MySQL没法执行并行查询,不用白费力气了。
哈希关联,MySQL不支持哈希关联,全部关联都是嵌套循环关联。
松散索引扫描,MySQL不支持松散(跳跃),仍须要扫描每个条目。
最大值和最小值,MySQL对 MIN()和MAX()作得很差。看一个例子,强制使用索引来优化(use index(xx))。
在同一个表上查询和更新,MySQL不容许这样。
======================================================
6.6 查询优化器的提示(hint)
讲到了不少提示,意在若是咱们对优化器选择的执行计划不满意,使用提示来控制最终的执行计划,如上面的 USE INDEX(PRIMARY),其余还有:HIGH_PRIORITY、LOW_PRIORITY、DELAYED、STRAIGHT_JOIN(上文提到过)、SQL_SMALL_RESULT、SQL_BIG_RESULT、SQL_BUFFER_RESULT、SQL_CACHE、SQL_NO_CACHE、SQL_CALC_FOUND_ROWS、FOR UPDATE、LOCK IN SHARE MODE、USE INDEX、IGNORE INDEX、FORCE INDEX等等。
======================================================
6.7 优化特定类型的查询
6.7.1 优化 COUNT()查询
COUNT()常被误解(难道这本书里说的对的?),COUNT()有两个做用,一、统计非NULL列的列植的数量,二、统计返回数据集的行数;经常使用的是COUNT(*),*常被误解为全部列,实际上在操做时是忽略全部列,而直接统计全部行数。COUNT(*)中的*与SELECT *中的*是不一样的。若是你真想统计结果集的行数,就用 COUNT(*)而不要使用 COUNT(aCol)。
一般觉得 MyISAM执行COUNT(*)最快,其实是有条件的,只有不用 WHERE时,由于MySQL根本不用扫描数据行,也无须去计算,会直接利用存储引擎的特性去得到这个值。当带上 WHERE 上,就须要去扫描去计算了。
书中一个优化的例子,将条件反转后可大大加速,如查询 id > 5 的数量有4097行,而反转,查询 id < 5 的,只有几行,而后 用总行数(用 COUNT(*) 获取-常数不费计算)减去 id < 5的,大大优化。但这种状况貌似我提早能够知道 id > 5的数据比 id < 5 的数据多不少才能够。
能使用近似值的就没必要追求精确计算值,代价过高!
6.7.2 优化关联查询
这个话题基本整本书都在讨论(仍是很晕),注意一下:
1)确保ON或USING子句中的列上有索引,在建立索引时就要考虑到关联的顺序。