读《高性能MySQL》---(三)

查询性能优化

理解查询执行的过程

  1. 客户端发送一条查询给服务器
  2. 服务器先检查查询缓存,如果命中了缓存,则立刻返回存储在缓存中的结果。否则进入下一个阶段
  3. 服务器进行SQL解析、预处理,再由优化器生成对应的执行计划
  4. MySQL根据优化计划,调用存储引擎的API来执行查询
  5. 将结果返回给客户端

优化数据访问

① 确认应用程序是否在检索大量超过需要的数据;

② 确认MySQL服务器层是否在分析大量超过需要的数据行。

是否向数据库请求了不需要的数据

  1. 查询不需要的记录: 有时候我们查询数据并不需要查询所有的数据,这个时候可以在查询后加上LIMIT,以限制查询结果的数目。
  2. 查询所有列: 取出所有列,会让优化器无法完成索引覆盖扫描这类优化,还会带来额外的I/O、内存和CPU消耗。应避免SELECT *的写法,只取出需要使用到的列
  3. 重复查询相同的数据: 当需要重复查询相同数据时,应将相同的数据缓存起来,这样可以提高系统性能。

MySQL是否在扫描额外的记录

判断MySQL查询开销的三个重要指标:

  • 响应时间
  • 扫描的行数
  • 返回的行数

它们大致反映了MySQL在内部执行查询时需要访问多少数据,并大概推算出查询运行的时间。

需要注意,响应时间是服务时间和等待时间之和。

重构查询

  • 合适地选择一次重复的查询还是多个简单的查询
  • 将较大的语句切分为小的语句多次执行,例如,删除很多的数据记录,可以拆分为多次小的删除(两次删除相隔一段时间,也可以降低服务器压力)
  • 将关联查询分解为多个单表查询。可以提高缓存效率、减少锁的竞争等,很多时候将关联查询放到应用程序中将会更加高效

特定类型查询的优化

优化COUNT()

  • COUNT()用于统计某一个列值的数量。并且要求列值是非空的(不统计NULL)。使用COUNT()统计行数时,最好使用COUNT(*),性能比较好,意义清晰。
  • MyISAM存储引擎的COUNT(*)并不总是非常快的,只有当没有任何where条件时,才会非常快,因为此时不需要实际地去计算结果的行数。有时可以简单地优化一下,例如:需要查询所有ID大于5的城市数,可以用全部行数减去ID<=5的城市数,从而缩小需要扫描的记录行数。

优化关联查询

  • 确保ON或者USING字句中的列上有索引。
  • 确保任何的GROUP BY和ORDER BY中的表达式只涉及到一个表中的列。

优化子查询

  • 子查询尽量使用关联查询代替

优化LIMIT分页

当LIMIT的偏移量很大时,例如LIMIT 1000, 20,这时MySQL需要查询1020条记录然后只返回20条数据,这样代价很高。

  • 解决方法是:尽可能地使用索引覆盖扫描,而不是查询所有的列。然后根据需要做一次关联操作,再返回所需要的列
  • 另一个解决方法是:使用书签保存上次取记录的位置,下次从这个位置开始即可,例如:SELECT * FROM user LIMIT 20,如果前面语句返回的是1~20,下一页的查询则为:SELECT * FROM user WHERE user_id>20 LIMIT 20,该做法可行是因为主键user_id是单调递增的。

优化UNION查询

  • 除非确实需要消除重复行,否则一定要使用UNION ALL。因为单独使用UNION,MySQL会对整个临时表的数据做唯一性检查,造成不必要的性能消耗。