《高性能MySQL》——服务器性能剖析(笔记)

三、服务器性能剖析

  • 服务器是否达到了性能最佳的状态
  • 某条语句为什么执行不够快
  • 某些间歇性疑难故障

要知道上述的问题,简单的方式是专注于测量服务器的时间花费在哪里

3.1 性能优化简介

我们将性能定义为完成某件任务所需要的时间度量,即——响应时间

通过任务和时间而不是资源来测量性能。

数据库服务器的目的是执行SQL语句,所以它关注的任务是查询或者语句,如SELECT、UPDATE、 DELETE 等。数据库服务器的性能用查询的响应时间来度量,单位是每个查询花费的时间。

对于优化,我们假设性能优化就是在一定的工作负载下尽可能地降低响应时间。

  1. 假如你认为性能优化是降低CPU利用率,那么可以减少对资源的使用。但这是一个陷阱,资源是用来消耗并用来工作的,所以有时候消耗更多的资源能够加快查询速度。

    • 很多时候将使用老版本InnoDB引擎的MySQL升级到新版本后,CPU利用率会上升得很厉害,这并不代表性能出现了问题,反而说明新版本的InnoDB对资源的利用率上升了。

    • 查询的响应时间则更能体现升级后的性能是不是变得更好。版本升级有时候会带来一些bug,比如不能利用某些索引从而导致CPU利用率上升。CPU利用率只是一种现象,而不是很好的可度量的目标。

  2. 如果把性能优化仅仅看成是提升每秒查询量,这其实只是吞吐量优化。

    • 吞吐量的提升可以看作性能优化的副产品。
    • 对查询的优化可以让服务器每秒执行更多的查询,因为每条查询执行的时间更短了(吞吐量的定义是单位时间内的查询数量,这正好是我们对性能的定义的倒数)。

所以如果目标是降低响应时间,那么就需要理解为什么服务器执行查询需要这么多时间,
然后去减少或者消除那些对获得查询结果来说不必要的工作。

也就是说,先要搞清楚时间花在哪里。这就引申出优化的第二个原则:无法测量就无法有效地优化。

所以第一步应该测量时间花在什么地方

我们将花费非常多,甚至90%的时间来测量响应时间花在哪里。

如果通过测量没有找到答案,那要么是测量的方式错了,要么是测量得不够完整。如果测量了系统中完整而且正确的数据,性能问题一般都能暴露出来,对症下药的解决方案也就比较明了。

测量是一项很有挑战性的工作,并且分析结果也同样有挑战性,测出时间花在哪里,和知道为什么花在那里,是两码事。

前面提到需要合适的测量范围,这是什么意思呢?合适的测量范围是说只测量需要优化的活动。有两种比较常见的情况会导致不合适的测量:

  • 在错误的时间启动和停止测量。
  • 测量的是聚合后的信息,而不是目标活动本身。

例如,一个常见的错误是先查看慢查询,然后又去排查整个服务器的情况来判断问题在
哪里。如果确认有慢查询,那么就应该测量慢查询,而不是测量整个服务器。测量的应
该是从慢查询的开始到结束的时间,而不是查询之前或查询之后的时间。


完成一项任务所需要的时间可以分成两部分:

  • 执行时间
  • 等待时间。

如果要优化任务的执行时间,最好的办法是通过测量定位不同的子任务花费的时间,然后优化去掉一些子任务、降低子任务的执行频率或者提升子任务的效率。

而优化任务的等待时间则相对要复杂一些,因为等待有可能是由其他系统间接影响导致,任务之间也可能由于争用磁盘或者CPU资源而相互影响。根据时间是花在执行还是等待上的不同,诊断也需要不同的工具和技术。

3.1.1 通过性能剖析进行优化

一旦掌握并实践面向响应时间的优化方法,就会发现需要不断地对系统进行性能 剖析(profiling)

性能剖析是测量和分析时间花费在哪里的主要方法。性能剖析一般有两个步骤:

  1. 测量任务所花费的时间
  2. 然后对结果进行统计和排序,将重要的任务排到前面。

在任务开始时启动计时器,在任务结束时停止计时器,然后用结束时间减去启动时间得到响应时间。

这些结果数据可以用来绘制调用关系图,但对于我们的目标来说更重要的是,可以将相似的任务分组并进行汇总。

对相似的任务分组并进行汇总可以帮助对那些分到一组的任务做更复杂的统计分析,但至少需要知道每一组有多少任务,并计算出总的响应时间。

通过性能 剖析报告(profile report) 可以获得需要的结果。性能剖析报告会列出所有任务列表。每行记录一个任务,包括任务名、任务的执行时间、任务的消耗时间、任务的平均执行时间,以及该任务执行时间占全部时间的百分比。性能剖析报告会按照任务的消耗时间进行降序排序。

为了更好地说明,这里举一个对整个数据库服务器工作负载的性能剖析的例子,主要输出的是各种类型的查询和执行查询的时间。

这是从整体的角度来分析响应时间。下面的输出是用Percona Toolkit中的pt-query-digest分析得到的结果。为了显示方便,对结果做了一些微调,并且只截取了前面几行结果:

在这里插入图片描述
上面只是性能剖析结果的前几行,根据总响应时间进行排名,只包括剖析所需要的最小列组合。每一行都包括了查询的响应时间和占总时间的百分比、查询的执行次数、单次执行的平均响应时间,以及该查询的摘要。通过这个性能剖析可以很清楚地看到每个查询相互之间的成本比较,以及每个查询占总成本的比较。

在这个例子中,任务指的就是查询,实际上在分析MySQL的时候经常都指的是查询。

我们将实际地讨论两种类型的性能剖析:

  • 基于执行时间的分析:研究的是什么任务的执行时间最长
  • 基于等待的分析:判断任务在什么地方被阻塞的时间最长

如果任务执行时间长是因为消耗了太多的资源且大部分时间花费在执行上,等待的时间不多,这种情况下基于等待的分析作用就不大。

反之亦然,如果任务一直在等待, 没有消耗什么资源,去分析执行时间就不会有什么结果。如果不能确认问题是出在执行还是等待上,那么两种方式都需要试试。

事实上,当基于执行时间的分析发现一个任务需要花费太多时间的时候,应该深入去分析一下,可能会发现某些“执行时间”实际上是在等待。


在对系统进行性能剖析前,必须先要能够进行测量,这需要系统可测量化的支持。可测量的系统一般会有多个测量点可以捕获并收集数据,但实际系统很少可以做到可测量化。

大部分系统都没有多少可测量点,即使有也只提供一些活动的计数,而没有活动花费的时间统计。

MySQL就是一个典型的例子,直到版本5.5才第一次提供了Performance Schema,其中有一些基于时间的测量点,而版本5.1及之前的版本没有任何基于时间的测量点。

MySQL默认数据库information_schema,mysql,performance_schema内容
MYSQL performance schema详解
MYSQL performance_schema 招招毙命

能够从MySQL收集到的服务器操作的数据大多是show status计数器的形式,这些计数器统计的是某种活动发生的次数。

这也是我们最终决定创建Percona Server的主要原因,Percona Server从版本5.0开始提供很多更详细的查询级别的测量点。

顺便一提,schema和database的区别其实依据不同厂商
MYSQL中认为是一个东西
在这里插入图片描述Oracle中则有数据库对象、模式对象、非模式对象的区分在这里插入图片描述

Percona server介绍及与mysql对比及安装

虽然理想的性能优化技术依赖于更多的测量点,但幸运的是,即使系统没有提供测量点,也还有其他办法可以展开优化工作。

因为还可以从外部去测量系统,如果测量失败,也可以根据对系统的了解做出一些靠谱的猜测。但这么做的时候一定 要记住,不管是外部测量还是猜测,数据都不是百分之百准确的,这是系统不透明所带来的风险。

举个例子,在Percona Server 5.0中,慢查询日志揭露了一些性能低下的原因,如磁盘I/O等待或者行级锁等待。如果日志中显示一条查询花费10秒,其中9.6秒在等待磁盘I/O,那么追究其他4%的时间花费在哪里就没有意义,磁盘I/O才是最重要的原因。

3.1.2 理解性能剖析

MySQL的性能 剖析(profile) 将最重要的任务展示在前面,但有时候没显示出来的信息也很重要。

可以参考一下前面提到过的性能剖析的例子。不幸的是,尽管性能剖析输出了排名、总计和平均值,但还是有很多需要的信息是缺失的,如下所示。

值得优化的查询(worthwhile query)

性能剖析不会自动给出哪些查询值得花时间去优化。这里我们要再次强调两点:

  1. 一些只占总响应时间比重很小的查询是不值得优化的。根据 阿姆达尔定律(Amdahl’s Law),对一个占总响应时间不超过5%的查询进行优化,无论如何努力,收益也不会超过5%。

  2. 如果花费了1000美元去优化一个任务,但业务的收入没有任何增加,那么可以说反而导致业务被逆优化了1000 美元。如果优化的成本大于收益,就应当停止优化。

异常情况

某些任务即使没有出现在性能剖析输出的前面也需要优化。

比如某些任务执行次数很少,但每次执行都非常慢,严重影响用户体验。因为其执行频率低,所以总的响应时间占比并不突出。

未知的未知

一款好的性能剖析工具会显示可能的“丢失的时间”。丟失的时间指的是任务的总时间和实际测量到的时间之间的差。

例如,如果处理器的CPU时间是10秒,而剖析到的任务总时间是9.7秒,那么就有300亳秒的丟失时间。这可能是有些任务没有测量到,也可能是由于测量的误差和精度问题的缘故。如果工具发现了这类问题,则要引起重视,因为有可能错过了某些重要的事情。

即使性能剖析没有发现丢失时间,也需要注意考虑这类问题存在的可能性,这样才不会错过重要的信息。我们的例子中没有显示丢失的时间,这是我们所使用工具的一个局限性。

被掩藏的细节

性能剖析无法显示所有响应时间的分布。只相信平均值是非常危险的,它会隐藏很多信息,而且无法表达全部情况。

要做出最好的决策,需要为性能剖析里输出的这一行中包含的12773次查询提供更多的信息,尤其是更多响应时间的信息,比如直方图、百分比、标准差、偏差指数等。

3.2 对应用程序进行性能剖析

对任何需要消耗时间的任务都可以做性能剖析,当然也包括应用程序。实际上,剖析应用程序一般比剖析数据库服务器容易,而且回报更多。

虽然前面的演示例子都是针对MySQL服务器的剖析,但对系统进行性能剖析还是建议自上而下地进行,这样可以追踪自用户发起到服务器响应的整个流程。

虽然性能问题大多数情况下都和数据库有关,但应用导致的性能问题也不少。

性能瓶颈可能有很多影响因素:

  • 外部资源,比如调用了外部的Web服务或者搜索引擎。
  • 应用需要 处理大量的数据,比如分析一个超大的XML文件。
  • 在循环中执行昂贵的操作,比如滥用正则表达式。
  • 使用了低效的算法,比如使用 暴力搜索算法(naive search algorithm) 来查找列表中的项。

幸运的是,确定MySQL的问题没有这么复杂,只需要一款应用程序的剖析工具即可(作为回报,一旦拥有这样的工具,就可以从一开始就写出高效的代码)。

建议在所有的新项目中都考虑包含性能剖析的代码。往已有的项目中加入性能剖析代码也许很困难,新项目就简单一些。

性能剖析本身会导致服务器变慢吗?

  • 说“是的”,是因为性能剖析确实会导致应用慢一点。
  • 说“不是”,是因为性能剖析可以帮助应用运行得更快。

性能剖析和定期检测都会带来额外开销。问题在于这部分的开销有多少,并且由此获得的收益是否能够抵消这些开销。

大多数设计和构建过高性能应用程序的人相信,应该尽可能地测量一切可以测量的地方,并且接受这些测量带来的额外开销,这些开销应该被当成应用程序的一部分。另外,大多数应用并不需要每天都运行详细的性能测量。即使不同意这个观点,为应用构建一些可以永久使用的轻量级的性能剖析也是有意义的。如果系统没有每天变化的性能统计,则碰到无法提前预知的性能瓶颈就是一件头痛的事情。发现问题的时候,如果有历史数据,则这些历史数据价值是无限的。而且性能数据还可以帮助规划好硬件采购、资源分配,以及预测周期性的性能尖峰。

那么何谓“轻量级”的性能剖析?

比如可以为所有SQL语句计时,加上脚本总时间统计,这样做的代价不高,而且不需要在每次 页面查看(page view) 时都执行。

如果流量趋势比较稳定,随机采样也可以,随机采样可以通过在应用程序中设置实现:

<?php
$profiling_ enabled = rand(0, 100) > 99;
?>

这样只有1%的会话会执行性能采样,来帮助定位一些严重的问题。这种策略在生产环境中尤其有用,可以发现一些其他方法无法发现的问题。

(测量php应用程序略)

3.3 剖析MySQL查询

对查询进行性能剖析有两种方式,每种方式都有各自的问题。

可以剖析整个数据库服务器,这样可以分析出哪些查询是主要的压力来源(如果已经在最上面的应用层做过剖析,则可能已经知道哪些查询需要特别留意)。

定位到具体需要优化的查询后,也可以钻取下去对这些查询进行单独的剖析,分析哪些子任务是响应时间的主要消耗者。

3.3.1 剖析服务器负载

服务器端的剖析很有价值,因为在服务器端可以有效地审计效率低下的查询。

定位和优化“坏”查询能够显著地提升应用的性能,也能解决某些特定的难题。还可以降低服务器的整体压力,这样所有的查询都将因为减少了对共享资源的争用而受益(“间接的好处”)。降低服务器的负载也可以推迟或者避免升级更昂贵硬件的需求,还可以发现和定位糟糕的用户体验,比如某些极端情况。

MySQL的每一个新版本中都增加了更多的可测量点。如果当前的趋势可靠的话,那么在性能方面比较重要的测量需求很快能够在全球范围内得到支持。但如果只是需要剖析并找出代价高的查询,就不需要如此复杂。

有一个工具很早之前就能帮到我们了,这就是 慢查询日志

捕获MySQL的查询到日志文件中

在MySQL中,慢查询日志最初只是捕获比较“慢”的查询,而性能剖析却需要针对所有的查询。而且在MySQL 5.0及之前的版本中,慢查询日志的响应时间的单位是秒,粒度太粗了。幸运的是,这些限制都已经成为历史了。

在MySQL 5.1 及更新的版本中,慢日志的功能已经被加强,可以通过设置long_query_time 为0来捕获所有的查询,而且查询的响应时间单位已经可以做到微秒级。

如果使用的是Percona Server, 那么5.0版本就具备了这些特性,而且Percona Server提供了对日志内容和查询捕获的更多控制能力。

在MySQL的当前版本中,慢查询日志是开销最低、精度最高的测量查询时间的工具。

如果还在担心开启慢查询日志会带来额外的I/O开销,那大可以放心。更需要担心的是日志可能消耗大量的磁盘空间。

  • 如果长期开启慢查询日志,注意要部署 日志轮转(log rotation) 工具。

使用logrotate完成日志自动切分并轮转

  • 或者不要长期启用慢查询日志,只在需要收集负载样本的期间开启即可。

MySQL还有另外一种查询日志,被称之为 “通用日志”,但很少用于分析和剖析服务器性能。通用日志在查询请求到服务器时进行记录,所以不包含响应时间和执行计划等重要信息。

MySQL 5.1之后支持将日志记录到数据库的表中,但多数情况下这样做没什么必要。这不但对性能有较大影响,而且MySQL 5.1在将慢查询记录到文件中时已经支持微秒级别的信息,然而将慢查询记录到表中会导致时间粒度退化为只能到秒级。而秒级别的慢查询日志没有太大的意义。

Percona Server的慢查询日志比MySQL官方版本记录了更多细节且有价值的信息,如查询执行计划、锁、I/O活动等。另外在可管理性上也进行了增强。比如全局修改针对每个连接的long_query_ time 的阈值,这样当应用使用连接池或者持久连接的时候,可以不用重置会话级别的变量而启动或者停止连接的查询日志。

总的来说,慢查询日志是一种轻量而且功能全面的性能剖析工具,是优化服务器查询的利器。


有时因为某些原因如权限不足等,无法在服务器上记录查询。

这样的限制我们也常常碰到,所以我们开发了两种替代的技术,都集成到了Percona Toolkit 中的pt-query-digest中。

第一种是通过–processlist选项不断查看SHOW FULL PROCESSLIST 的输出,记录查询第一次出现的时间和消失的时间。某些情况下这样的精度也足够发现问题,但却无法捕获所有的查询。一些执行较快的查询可能在两次执行的间隙就执行完成了,从而无法捕获到。

第二种技术是通过抓取TCP网络包,然后根据MySQL的客户端/服务端通信协议进行解析。可以先通过tcpdump将网络包数据保存到磁盘,然后使用pt-query-digest的–type=tcpdump选项来解析并分析查询。此方法的精度比较高,并且可以捕获所有查询。还可以解析更高级的协议特性,比如可以解析二进制协议,从而创建并执行服务端 预解析的语句(prepared statement) 及,压缩协议。

另外还有一种方法,就是通过MySQLProxy代理层的脚本来记录所有查询,但在实践中我们很少这样做。

分析查询日志

不要直接打开整个慢查询日志进行分析,这样做只会浪费时间和金钱。

首先应该生成一个剖析报告,如果需要,则可以再查看日志中需要特别关注的部分。

自顶向下是比较好的方式,否则有可能像前面提到的,反而导致业务的逆优化。

从慢查询日志中生成剖析报告需要有一款好工具,这里建议使用pt-query-digest,这毫无疑问是分析MySQL查询日志最有力的工具。

技术分享 | 使用 pt-query-digest 分析慢日志

该工具功能强大,包括可以将查询报告保存到数据库中,以及追踪工作负载随时间的变化。

一般情况下,只需要将慢查询日志文件作为参数传递给pt-query-digest,就可以正确地工作了。它会将查询的剖析报告打印出来,并且能够选择将“重要”的查询逐条打印出更详细的信息。

该工具还在持续的开发中,因此要了解最新的功能请阅读最新版本的文档。

pt-query-digest

这里给出一份pt-query-digest输出的报告的例子,作为进行性能剖析的开始。这是前面提到过的一个未修改过的剖析报告:

在这里插入图片描述
首先,每个查询都有一个ID,这是对查询语句计算出的哈希值指纹,计算时去掉了查询条件中的文本值和所有空格,并且全部转化为小写字母。

Item下,语句里面的表名(比如InvitesNew)后面的问号意味着这是一个 分片(shard) 的表,表名后面的分片标识被问号替代,这样就可以将同一组分片表作为一个整体做汇总统计。

报告中的V/M列提供了 方差均值比(variance-to-mean ratio) 的详细数据,方差均值比也就是常说的 离差指数(index of dispersion) 。离差指数高的查询对应的执行时间的变化较大,而这类查询通常都值得去优化。

如果pt-query-digest指定了–explain 选项,输出结果中会增加一列简要描述查询的执行计划,执行计划是查询背后的“极客代码”。通过联合观察执行计划列和V/M列,可以更容易识别出性能低下需要优化的查询。

最后,在尾部也增加了一行输出,显示了其他17个占比较低而不值得单独显示的查询的统计数据。可以通过–limit和–outliers选项指定工具显示更多查询的详细信息,而不是将一些不重要的查询汇总在最后一行。

默认只会打印时间消耗前10位的查询,或者执行时间超过1秒阈值很多倍的查询,这两个限制都是可配置的。

剖析报告的后面包含了每种查询的详细报告。可以通过查询的ID或者排名来匹配前面的剖析统计和查询的详细报告。

3.3.2 剖析单条查询

在实际应用中,多用一下三种方法:

  • SHOW STATUS
  • SHOW PROFILE
  • 检查慢查询日志的条目(这还要求必须是Percona Server,官方MySQL版本的慢查询日志缺失了很多附加信息)

使用SHOW PROFILE

SHOW PROFILE 命令是在MySQL 5.1以后的版本中引入的,来源于开源社区中的Jeremy Cole的贡献。这是在书中写作之际唯一一个在GA版本中包含的真正的查询剖析工具。

默认是禁用的,但可以通过服务器变量在会话(连接)级别动态地修改。

 mysql> SET profiling = 1;

然后,在服务器上执行的所有语句,都会测量其耗费的时间和其他一些查询执行状态变更相关的数据。

当一条查询提交给服务器时,此工具会记录剖析信息到一张临时表,并且给查询赋予一个从1开始的整数标识符。

下面是对 Sakila样本数据库的一个视图的剖析结果 :

mysql> SELECT * FROM sakila.nicer_but_slower_film_list;
[query results omitted]
997 rows in set (0.17 sec)

该查询返回了997行记录,花费了大概1/6秒。

在这里插入图片描述
查询详情

mysql> SHOW PROFILE FOR QUERY 1;

在这里插入图片描述
可以看到有相当多的信息,他们是按照执行步骤来记录时间的。
同时它是存在一张表里面的,我们可以直接查询。

mysql> SET @query_id = 1;
Query OK,0 rows affected (0.00 sec)
mysql> SELECT STATE, SUM(DURATION) AS Total R, 
-> ROUND(
-> 100 * SUM(DURATION) /
->       (SELECT SUM(DURATION)
->        FROM INFORMATION_ SCHEMA. PROFILING
->        WHERE QUERY_ ID = @query. id
->       ), 2) AS Pct_ R,
->   COUNT(*) AS Calls,
->   SUM(DURATION) / COUNT(*) AS "R/Ca11"
->   FROM INFORMATION_ SCHEMA. PROFILING
->    WHERE QUERY_ID = @query_id
-> 		GROUP BY STATE
-> 		ORDER BY Total_R DESC;

在这里插入图片描述
通过上述排序后的结果,我们很容易发现:

  1. 减少临时表的使用
  2. 发送数据这里也需要优化,但是可能的原因很多:不同服务器活动、关联时匹配的记录行,排查比较麻烦。
  3. 结果排序这个地方不需要优化,因为占比相对较小(Total_R)。

使用SHOW STATUS

MySQL的SHOW STATUS命令返回了一些计数器。既有服务器级别的全局计数器,也有基于某个连接的会话级别的计数器。

例如其中的Queries在会话开始时为0,每提交一条查询增加1。

如果执行SHOW GLOBAL STATUS (注意到新加的GLOBAL关键字),则可
以查看服务器级别的从服务器启动时开始计算的查询次数统计。

不同计数器的可见范围不一样,不过全局的计数器也会出现在SHOW STATUS 的结果中,容易被误认为是会话级别的,千万不要搞迷糊了。

如果打算优化从某些特定连接观察到的东西,测量的却是全局级别的数据,就会导致混乱。MySQL官方手册中对所有的变量是会话级还是全局级做了详细的说明。

SHOW STATUS 是一个有用的工具,但并 不是一款剖析工具

SHOW STATUS的大部分结果都只是一个计数器,可以显示某些活动如读索引的频繁程度,但无法给出消耗了多少时间。

SHOW STATUS 的结果中只有一条指的是操作的时间(Innodb_ row_ lock_ time), 而且只能是全局级的,所以还是无法测量会话级别的工作。

尽管SHOW STATUS 无法提供基于时间的统计,但对于在执行完查询后观察某些计数器的值还是有帮助的。

有时候可以猜测哪些操作代价较高或者消耗的时间较多。最有用的计数器包括 句柄计数器(handler counter) 、临时文件和表计数器等。在

下面的例子演示了如何将会话级别的计数器 重置为0 ,然后查询前面提到的视图,再检查计数器的结果:

mysql> FLUSH STATUS;
mysql> SELECT * FROM sakila.nicer_but_slower_film_list;
mysql> SHOW STATUS WHERE Variable_name LIKE 'Handler%' OR Variable name LIKE ' Created% ' ;

在这里插入图片描述

从结果可以看到该查询使用了三个临时表,其中两个是磁盘临时表,并且有很多的没有用到索引的读操作(Handler_ read_ rnd_ next)。

假设我们不知道这个视图的具体定义,仅从结果来推测,这个查询有可能是做了多表 关联(join) 查询,并且没有合适的索引,可能是其中一个子查询创建了临时表,然后和其他表做联合查询。而用于保存子查询结果的临时表没有索引,如此大致可以解释这样的结果。

使用这个技术的时候,要注意SHOW STATUS 本身也会创建一个临时表 ,而且也会通过句柄操作访问此临时表,这会影响到SHOW STATUS结果中对应的数字,而且不同的版本可能行为也不尽相同。比较前面通过SHOW PROFILES 获得的查询的执行计划的结果来看,至少临时表的计数器多加了1。

你可能会注意到通过EXPLAIN查看查询的执行计划也可以获得大部分相同的信息,但EXPLAIN是通过估计得到的结果,而通过计数器则是实际的测量结果。例如,EXPLAIN 无法告诉你临时表是否是磁盘表,这和内存临时表的性能差别是很大的。

使用慢查询日志

Percona Server 对官方的慢查询日志做了一定的修改。

下面是用相同的查询执行后抓取到的结果:
在这里插入图片描述

从这里看到查询确实一共创建了三个临时表,其中两个是磁盘临时表。

这里为了方便阅读,对结果做了简化。但最后对该查询执行SHOW PROFILE的数据也会写入到日志中,所以在Percona Server中甚至可以记录SHOW PROFILE 的细节信息。

另外也可以看到,慢查询日志中详细记录的条目包含了SHOWPROFILE和SHOWSTATUS所有的输出,并且还有更多的信息。所以通过pt-query-digest发现“坏”查询后,在慢查询日志中可以获得足够有用的信息。

查看pt-query-digest的报告时,其标题部分一般会有如下输出:

在这里插入图片描述
通过3214这个偏移值,我们可以查看对应的日志
在这里插入图片描述

使用Performance Schema

下面的查询显示了系统中等待的主要原因:

mysql> SELECT event_name, count_ star, sum_timer_wait
-> FROM events_ waits_sumary_g1obal_by_ event_name
-> ORDER BY sum_timer_wait DESC LIMIT 5;

在这里插入图片描述
对大多数用户来说,直接通过Performance Schema的裸数据获得有用的结果相对来说过于复杂和底层。到目前为止实现的这个特性,主要是为了测量当为提升服务器性能而修改MySQL源代码时使用,包括等待和互斥锁。

3.3.3 使用性能剖析

当获得服务器或者查询的剖析报告后,怎么使用?

好的剖析报告能够将潜在的问题显示出来,但最终的解决方案还需要用户来决定(尽管报告可能会给出建议)。

优化查询时,用户需要对服务器如何执行查询有较深的了解。剖析报告能够尽可能多地收集需要的信息、给出诊断问题的正确方向,以及为其他诸如EXPLAIN等工具提供基础信息。

尽管一个拥有完整测量信息的剖析报告可以让事情变得简单,但现有系统通常都没有完美的测量支持。

3.4 诊断间歇性问题

间歇性的问题(比如系统偶尔停顿或者慢查询)很难诊断。

有些幻影问题只在没有注意到的时候才发生,而且无法确认如何重现,诊断这样的问题往往要花费很多时间,有时候甚至需要好几个月。

在这个过程中,有些人会尝试以不断试错的方式来诊断,有时候甚至会想要通过随机地改变一些服务器的设置来侥幸地找到问题。

尽量不要使用试错的方式来解决问题。 这种方式有很大的风险,因为结果可能变得更坏。这也是一种令人沮丧且低效的方式。

如果一时无法定位问题,可能是测量的方式不正确,或者测量的点选择有误,或者使用的工具不合适。

为了演示为什么要尽量避免试错的诊断方式,下面列举了我们认为已经解决的一些间歇性数据库性能问题的实际案例:

  • 应用通过curl从一个运行得很慢的外部服务来获取汇率报价的数据。

curl 是常用的命令行工具,用来请求 Web 服务器。它的名字就是客户端(client)的 URL 工具的意思

  • memcached缓存中的一些重要条目过期,导致大量请求落到MySQL以重新生成缓存条目。

  • DNS查询偶尔会有超时现象。

  • 可能是由于互斥锁争用,或者内部删除查询缓存的算法效率太低的缘故,MySQL的查询缓存有时候会导致服务有短暂的停顿。

  • 当并发度超过某个阈值时,InnoDB的扩展性限制导致查询计划的优化需要很长的时间。

从上面可以看到,有些问题确实是数据库的原因,也有些不是。只有在问题发生的地方通过观察资源的使用情况,并尽可能地测量出数据,才能避免在没有问题的地方耗费精力。

3.4.1 单条查询问题还是服务器问题.

发现问题的蛛丝马迹了吗?如果有,则首先要确认这是单条查询的问题,还是服务器的问题。

  • 如果服务器上所有的程序都突然变慢,又突然都变好,每一条查询也都变慢了,那么慢查询可能就不一定是原因,而是由于其他问题导致的结果。

  • 反过来说,如果服务器整体运行没有问题,只有某条查询偶尔变慢,就
    需要将注意力放到这条特定的查询上面。

服务器的问题非常常见

在过去几年,硬件的能力越来越强,配置16核或者更多CPU的服务器成了标配,MySQL在SMP架构的机器上的可扩展性限制也就越来越显露出来。

尤其是较老的版本,其问题更加严重,而目前生产环境中的老版本还非常多。

新版本MySQL依然也还有一些扩展性限制,但相比老版本已经没有那么严重,而且出现的频率相对小很多,只是偶尔能碰到。

这是好消息,也是坏消息:

  • 好消息是很少会碰到这个问题
  • 坏消息则是一旦碰到,则需要对MySQL内部机制更加了解才能诊断出来。

当然,这也意味着很多问题可以通过升级到MySQL新版本来解决。

如何判断是单条查询问题还是服务器问题

如果问题不停地周期性出现,那么可以在某次活动中观察到

或者整夜运行脚本收集数据,第二天来分析结果

大多数情况下都可以通过三种技术来解决。

1. 使用SHOW GLOBAL STATUS

这个方法实际上就是以较高的频率比如一秒执行一次SHOWGLOBALSTATUS命令捕获数据,问题出现时,则可以通过某些计数器的“尖刺”或者“凹陷”来发现。

这个方法比较简单,所有人都可以使用(不需要特殊的权限),对服务器的影响也很小,所以是一个花费时间不多却能很好地了解问题的好方法。

下面是示例命令及其输出:

$ mysqladmin ext -i1| awk’
/Queries/{q=$4-qp;qp=$4}
/Threads_connected/{tc=$4}
/Threads_running/{printf "%5d %5d %5d\n", q, tc, $4}'

每秒查询数目、连接数目、当前执行查询的线程总数
在这里插入图片描述
这个命令每秒捕获一次SHOWGL0BALSTATUS的数据,输出给awk计算并输出每秒的查询数、Threads_ connected 和Threads_ running (表示当前正在执行查询的线程数)。

这三个数据的趋势对于服务器级别偶尔停顿的敏感性很高。


一般发生此类问题时,根据原因的不同和应用连接数据库方式的不同,每秒的查询数一般会下跌,而其他两个则至少有一个会出现尖刺。

在这个例子中,应用使用了连接池,所以Threads_ connected 没有变化。

但正在执行查询的线程数明显上升,同时每秒的查询数相比正常数据有严重的下跌。


凭猜测有一定的风险。但在实践中有两个原因的可能性比较大。

  • 其中之一般是服务器内部碰到了某种瓶颈,导致新查询在开始执行前因为需要获取老查询正在等待的锁而造成堆积。这一类的锁一般也会对应用服务器造成后端压力,使得应用服务器也出现排队问题。

  • 另外一个常见的原因是服务区突然遇到了大量查询请求的冲击,比如前端的memcached突然失效导致的查询风暴。

这个命令每秒输出一行数据,可以运行几个小时或者几天,然后将结果绘制成图形,这样就可以方便地发现是否有趋势的突变。

如果问题确实是间歇性的,发生的频率又较低也可以根据需要尽可能长时间地运行此命令,直到发现问题再回头来看输出结果。

大多数情况下,通过输出结果都可以更明确地定位问题。

2. 使用 SHOW PROCESSLIST

这个方法是通过不停地捕获SHOW PROCESSLIST 的输出,来观察是否有大量线程处于不正常的状态或者有其他不正常的特征。

例如查询很少会长时间处于“statistics"状态,这个状态一般是指服务器在查询优化阶段如何确定表关联的顺序一通常都是非常快的。

另外,也很少会见到大量线程报告当前连接用户是 未经验证的用户(Unauthenticated user),这只是在连接握手的中间过程中的状态,当客户端等待输入用于登录的用户信息的时候才会出现。

使用SHOW PROCESSLIST 命令时,在尾部加上\G可以垂直的方式输出结果,这很有用,因为这样会将每一行记录的每一列都单独输出为一行,这样可以方便地使用 sort|uniq|sort 一类的命令来计算某个列值出现的次数:

$ mysql -e 'SHOW PROCESSLIST\G' | grep State: | sort | uniq -c | sort -rn

在这里插入图片描述
如果要查看不同的列,只需要修改grep的模式即可。在大多数案例中,State 列都非常有用。

从这个例子的输出中可以看到,有很多线程处于查询执行的结束部分的状态,包括“freeing items”、“end" 、“cleaning up”和“logging slow query"。事实上,在案例中的这台服务器上,同样模式或类似的输出采样出现了很多次。大量的线程处于“freeing items”状态是出现了大量有问题查询的很明显的特征和指示。

上面演示的这个例子是由于InnoDB内部的争用和脏块刷新所导致,但有时候原因可能比这个要简单得多。

一个经典的例子是很多查询处于"Locked” 状态,这是MyISAM的一个典型问题,它的表级别锁定,在写请求较多时,可能迅速导致服务器级别的线程堆积。

3. 使用查询日志

如果要通过查询日志发现问题,需要开启慢查询日志并在全局级别设置long_query_time为0,并且要确认所有的连接都采用了新的设置。

这可能需要重置所有连接以使新的全局设置生效

或者使用Percona Server的一个特性,可以在不断开现有连接的情况下动态地使设置强制生效。

如果因为某些原因,不能设置慢查询日志记录所有的查询,也可以通过tcpdump和pt-query- digest工具来模拟替代。


要注意找到吞吐量 突然下降时间段的日志 。查询是在完成阶段才写入到慢查询日志的,所以堆积会造成大量查询处于完成阶段,直到阻塞其他查询的资源占用者释放资源后,其他的查询才能执行完成。

这种行为特征的一个好处是,当遇到吞吐量突然下降时,可以归咎于吞吐量下降后完成的第一个查询(有时候也不一定是第一个查询。当某些查询被阻塞时,其他查询可以不受影响继续运行,所以不能完全依赖这个经验)。

再重申一次,好的工具可以帮助诊断这类问题,否则要人工去几百GB的查询日志中找原因。

下面的例子只有一行代码,却可以根据MySQL每秒将当前时间写入日志中的模式统计每秒的查询数量:

$ awk '/^# Time:/{print $3, $4, c;c=0}/^# User/{c++}' slow-query.log

在这里插入图片描述
从上面的输出可以看到有吞吐量突然下降的情况发生,而且在下降之前还有一个突然的高峰,仅从这个输出而不去查询当时的详细信息很难确定发生了什么,但应该可以说这个突然的高峰和随后的下降一定有关联。

不管怎么说,这种现象都很奇怪,值得去日志中挖掘该时间段的详细信息(实际上通过日志的详细信息,可以发现突然的高峰时段有很多连接被断开的现象,可能是有一台应用服务器重启导致的。所以不是所有的问题都是MySQL的问题)。

3.4.2 捕获诊断数据

当出现间歇性问题时,需要尽可能多地收集所有数据,而不只是问题出现时的数据。虽然这样会收集大量的诊断数据,但总比真正能够诊断问题的数据没有被收集到的情况要好。

在开始之前,需要搞清楚两件事:

  1. 一个可靠且实时的“触发器”,也就是能区分什么时候问题出现的方法。
  2. 一个收集诊断数据的工具。

诊断触发器

触发器非常重要。这是在问题出现时能够捕获数据的基础。有两个常见的问题可能导致无法达到预期的结果:

  • 误报(false positive):指收集了很多诊断数据,但期间其实没有发生问题,这可能浪费时间
  • 漏检(false negative):在问题出现时没有捕获到数据,错失了机会,一样地浪费时间

所以在开始收集数据前多花一点时间来确认触发器能够真正地识别问题是划算的。

通常情况下这是一个计数,比如正在运行的线程的数量、处于“ freeing items”状态的线程的数量等。当要计算线程某个状态的数量时,grep 的-c选项非常有用:

$ mysq1 -e 'SHOW PROCESSLIST\G'I grep -c "State: freeing items"
36

选择一个合适的阈值很重要,既要足够高,以确保在正常时不会被触发; 又不能太高,要确保问题发生时不会错过。

另外要注意,要在问题开始时就捕获数据,就更不能将阈值设置得太高。

问题持续上升的趋势一般会导致更多的问题发生,如果在问题导致系统快要崩溃时才开始捕获数据,就很难诊断到最初的根本原因。

我们需要利用一种工具来监控服务器,当达到触发条件时能收集数据。当然可以自己编写脚本来实现,不过不用那么麻烦,Percona Toolkit中的pt-stalk就是为这种情况设计的。

需要收集什么样的数据

现在已经确定了诊断触发器,可以开始启动一些进程来收集数据了。

尽可能收集所有能收集的数据,但只在需要的时间段内收集。

当一个未知问题发生时,一般来说有两种可能:

  • 服务器需要做大量的工作,从而导致大量消耗CPU
  • 或者在等待某些资源被释放

所以需要用不同的方法收集诊断数据,来确认是何种原因:

  • 剖析报告用于确认是否有太多工作
  • 等待分析则用于确认是否存在大量等待。

对于未知的问题,只能两种数据都尽量收集。

在GNU/Linux平台,可用于服务器内部诊断的一个重要工具是oprofile。也可以使用strace剖析服务器的系统调用,但在生产环境中使用它有一定的风险。

如果要剖析查询,可以使用tcpdump大多数MySQL版本无法方便地打开和关闭慢查询日志,此时可以通过监听TCP流量来模拟。另外,网络流量在其他一些分析中也非常有用。

对于等待分析,常用的方法是GDB的堆栈跟踪。MySQL内的线程如果卡在一个特定的地方很长时间,往往都有相同的堆栈跟踪信息。跟踪的过程是先启动gdb,然后 附加(attach) 到mysqld进程,将所有线程的堆栈都转储出来。然后可以利用一些简短的脚本将类似的堆栈跟踪信息做汇总,再利用sort|uniq|sort的“魔法”排序出总计最多的堆栈信息。

也可以使用SHOW PROCESSLIST 和SHOW INNODB STATUS 的快照信息观察线程和事务的状态来进行等待分析。

这些方法都不完美,但实践证明还是非常有帮助的。

收集所有的数据听起来工作量很大。或许读者之前已经做过类似的事情,但我们提供的工具可以提供一些帮助。

这个工具名为pt-collect,也是Percona Toolkit 中的一员。pt-collect一般通过pt-stalk来调用。因为涉及很多重要数据的收集,所以需要用root权限来运行。默认情况下,启动后会收集30秒的数据,然后退出。对于大多数问题的诊断来说,这已经足够,但如果有 误报(false positive) 的问题出现,则可能收集的信息就不够。

这个工具很容易下载到,并且不需要任何配置,配置都是通过pt-stalk进行的。系统中最好安装gdb和oprofile,然后在pt-stalk中配置使用。另外mysqld也需要有调试符号信息。当触发条件满足时,pt-collect 会很好地收集完整的数据。它也会在目录中创建时间戳文件。

解释结果数据

如果已经正确地设置好触发条件,并且长时间运行pt-stalk,则只需要等待足够长的时间来捕获几次问题,就能够得到大量的数据来进行筛选。

我们建议先根据两个目的来查看一些东西。

  • 检查问题是否真的发生了
  • 是否有非常明显的跳跃性变化

查看异常的查询或事务的行为,以及异常的服务器内部行为通常都是最有收获的。

查询或事务的行为可以显示是否是由于使用服务器的方式导致的问题:

  • 性能低下的SQL查询、使用不当的索引、设计糟糕的数据库逻辑架构等。

通过抓取TCP流量或者SHOW PROCESSLIST输出,可以获得查询和事务出现的地方,从而知道用户对数据库进行了什么操作。

通过服务器的内部行为则可以清楚服务器是否有bug,或者内部的性能和扩展
性是否有问题。这些信息在类似的地方都可以看到,包括在oprofile或者gdb的输出中,但要理解则需要更多的经验。

如果遇到无法解释的错误,则最好将收集到的所有数据打包,提交给技术支持人员进行分析。

MySQL的技术支持专家应该能够从数据中分析出原因,详细的数据对于支持人员来说非常重要。另外也可以将Percona Toolkit中另外两款工具pt-mysql-summary和pt-summary的输出结果打包,这两个工具会输出MySQL的状态和配置信息,以及操作系统和硬件的信息。

Percona Tolkit还提供了一款快速检查收集到的样本数据的工具: pt-sift。

这个工具会轮流导航到所有的样本数据,得到每个样本的汇总信息。如果需要,也可以钻取到详细信息。使用此工具至少可以少打很多字,少敲很多次键盘。

下面是一个问题服务器上的oprofile输出,你能找到问题吗?

在这里插入图片描述

一个重要的关于等待分析的性能瓶颈分析工具是gdb的堆栈跟踪。

下面是对一个线程的堆栈跟踪的输出结果,为了便于印刷做了一些格式化:
在这里插入图片描述
堆栈需要自下而上来看。

也就是说,线程当前正在执行的是pthread_cond_wait函数,这是由oS_event_wait_low 调用的。

继续往下,看起来是线程试图进入到InnoDB内核(srv_conc_enter_innodb),但被放入了一个内部队列中(os_event_wait_low), 原因应该是内核中的线程数已经超过innodb_thread_concurrency 的限制。

当然,要真正地发挥堆栈跟踪的价值需要将很多的信息聚合在一起来看。

附录

《高性能MySQL》
Baron Schwartz, Peter Zaitsev, Vadim Tkachenko 著
宁海元 周振兴 彭立勋 翟卫祥 刘辉 译

猜你喜欢

转载自blog.csdn.net/weixin_46949627/article/details/129251964
今日推荐