高性能MySQL阅读笔记

Scroll Down

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

查询性能低下最基本的原因是访问的数据太多。某些查询可能不可避免地需要筛选大量数据,但这并不常见。大部分性能低下的查询都可以通过减少访问的数据量的方式进行优化。对于低效的查询,我们发现通过下面两个步骤来分析总是很有效:

  • 确认应用程序是否在检索大量超过需要的数据。这通常意味着访问了太多的行,但有时候也可能是访问了太多的列。
  • 确认MySQL服务器层是否在分析大量超过需要的数据行。

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

有些查询会请求超过实际需要的数据,然后这些多余的数据会被应用程序丢弃。这会给MySQL服务器带来额外的负担,并且增加网络开销,另外也会消耗应用服务器的CPU和内存资源。
查询不需要的记录

多表关联时返回全部列

总是取出全部列
每次看到SELECT * 的时候都需要用怀疑的眼光审视,是不是真的需要全部的列,很可能不是必须的。取出全部列,会让优化器带来额外的IO、内存和CPU消耗。因此,一些DBA是严格禁止SELECT * 写法的,这样做有时候还能避免某些列被修改带来的问题。
重复查询相同的数据

MySQL是否在扫描额外的记录

在确定查询只返回需要的数据之后,接下来应该看看查询为了返回结果是否扫描了过多的数据。对于MySQL,最简单的衡量查询开销的三个指标如下:

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

响应时间
响应时间是两部分之和:服务时间和排队时间。服务时间是指数据库处理这个查询真正花费的时间。排队时间是指服务器因为等待某些资源而没有真正执行查询的时间-可能是等待IO完成,也可能是等待行锁,等等。

扫描的行数和返回的行数
分析查询时,查看该查询扫描的行数是非常有帮助的。这在一定程度上能够说明该查询找到需要的数据的效率高不高。

一般MySQL能够使用如下三种方式应用WHERE条件,从好到坏依次为:

  • 在索引中使用WHERE条件来过滤不匹配的记录。这是在存储引擎层完成的。
  • 使用索引覆盖扫描(在Extra列中出现了Using Index)来返回记录,直接从索引中过滤不需要的记录并返回命中的结果。这是在MySQL服务器层完成的,但无须再回表查询记录。
  • 从数据表中返回数据,然后过滤不满足条件的记录(在Extra列中出现Using Where)。这在MySQL服务器层完成,MySQL需要先从数据表读出记录然后过滤。

如果发现查询需要扫描大量的数据但只返回少数的行,那么通常可以尝试下面的技巧去优化它:

  • 使用索引覆盖扫描,把所有需要用的列都放到索引中,这样存储引擎无须回表获取对应行就可以返回结果了。
  • 改变库表结构。例如使用单独的汇总表
  • 重写这个复杂的查询,让MySQL优化器能够以更优化的方式执行这个查询。

查询缓存

MySQL查询缓存保存查询返回的完整结果。当查询命中该缓存,MySQL会立即返回结果,跳过了解析、优化和执行阶段。
查询缓存系统会跟踪查询中涉及的每个表,如果这些表发生变化,那么和这个表相关的所有的缓存数据都将失效。这种机制效率看起来比较低,因为数据表变化时很可能对应的查询结果并没有变更,但这种简单实现代价很小,而这点对于一个非常繁忙的系统来说非常重要。

MySQL如何判断缓存命中

MySQL判断缓存命中的方法很简单:缓存存放在一个引用表中,通过一个哈希值引用,这个哈希值包括了如下因素,即查询本身、当前要查询的数据库、客户端协议的版本等一些其他可能会影响返回的结果。
当判断缓存是否命中时,MySQL不会解析、或者参数化查询语句,而是直接使用SQL语句和客户端发送过来的其他原始信息。任何字符上的不同,都会导致缓存的不命中。
当查询语句中有一些不确定的数据时,则不会缓存。例如包含函数NOW()或者CURRENT_DATE()的查询不会缓存。类似的包含CURRENT_USER或者CONNECTION_ID()的查询语句因为会根据不同的用户返回不同的结果,所以也不会被缓存。事实上,如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、MySQL库中的系统表,或者任何包含列级别权限的表,都不会被缓存。