6. 确定问题并采取相应的优化措施

经过以上步骤,基本就可以确认问题出现的原因。此时用户可以根据情况采取相应的措施,进行优化以提高执行的效率。

对于低效的查询,可以通过下面两个步骤来分析:

  1. 确认应用程序是否在检索大量且不必要的数据。这通常意味着访问了太多的行,但有时候也可能是访问了太多的列

  2. 确认 MySQL 服务器层是否在扫描额外的记录

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

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

当然,查询返回超过需要的数据也不总是坏事。在我们研究过的许多案例中,人们会告诉我们,这种有点浪费数据库资源的方式可以简化开发,因为能提高相同代码片段的复用性,如果清楚这样做对性能的影响,那么这种做法也是值得考虑的。如果应用程序使用了某种缓存机制,或者有其他考虑,获取超过需要的数据也可能有其好处,但不要忘记这样做的代价是什么。获取并缓存所有的列的查询,相比多个独立的只获取部分列的查询可能更有好处。

是否在扫描额外的记录

对于 MySQL,最简单的衡量查询开销的三个指标如下:

  • 响应时间

  • 扫描的行数

  • 返回的行数

这三个指标都会被记录到 MySQL 的慢日志中,所以检查慢日志记录是找出扫描行数过多的查询的好办法。

响应时间

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

在不同类型的应用压力下,响应时间并没有一致的规律或者公式。诸如存储引擎的锁(表锁、行锁)、高并发资源竞争、硬件响应等诸多因素都会影响响应时间。所以,响应时间既可能是一个问题的结果也可能是一个问题的原因,不同案例情况不同。

当你看到一个查询的响应时间时,首先需要问问自己,这个响应时间是否是一个合理的值。实际上可以使用“快速上限估计”法来估算查询的响应时间:概括地说,了解这个查询需要哪些索引以及它的执行计划是什么,然后计算大概需要多少个顺序和随机 I/O,再用其乘以在具体硬件条件下一次 I/O 的消耗时间。最后把这些消耗都加起来,就可以获得一个大概参考值来判断当前响应时间是不是一个合理的值。

扫描的行数返回的行数

理想情况下扫描的行数和返回的行数应该是相同的,但实际中这种“美事”并不多。扫描的行数与返回的行数的比率通常很低,一般在 1:1 到 10:1 之间,不过有时候这个值也可能非常非常大。

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

  • 索引中使用 WHERE 条件来过滤不匹配的记录。这是在存储引擎层完成的。

  • 使用索引覆盖扫描(在 Extra 列中出现了 Using index)来返回记录,直接从索引中过滤不需要的记录并返回命中的结果。这是在 MySQL 服务器层完成的,但无须再回表查询记录。

  • 数据表中返回数据,然后过滤不满足条件的记录(在 Extra 列中出现 Using where)。这在 MySQL 服务器层完成,MySQL 需要先从数据表中读出记录然后过滤。

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

  • 使用索引覆盖扫描,把所有需要用的列都放到索引中,这样存储引擎无须回表获取对应行就可以返回结果了

  • 改变库表结构。例如,使用单独的汇总表

  • 重写这个复杂的查询,让 MySQL 优化器能够以更优化的方式执行这个查询

最后更新于