MySQL太慢?试试这些诊断思路和工具

1,190 阅读18分钟
原文链接: mp.weixin.qq.com
作者 | 黄炎 原文|http://www.sohu.com/a/249994941_683048 如果遇到 MySQL 慢的话,你的第一印象是什么,如果MySQL 数据库性能不行,你是如何处理的? MySQL 慢怎么办

如果遇到 MySQL 慢的话,你的第一印象是什么,MySQL 数据库如果性能不行,你是如何处理的?

我咨询了一些同行, 得到了以下反馈:

  • 第一反应是再试一次

  • 第二个反应是优化一下 SQL

  • 第三个反应是调大 buffer pool,然后开始换硬件了,换一下 SSD

  • 最后实在不行了找个搜索引擎搜索一下“MySQL 慢怎么办”。

如果大家用的是国内的搜索引擎的话,搜索引擎会推荐某某知道或者某某乎, 推荐一些 MySQL 调优经验, 调大参数 A, 调低参数 B, 诸如此类,类似的网站能告诉你 MySQL 慢怎么办。

我们来分析一下这些现象背后隐藏的意义:

  • 如果再试一次能够成功的话, 意味着你可能碰到了不可复现的外界因素的影响,导致 MySQL 会慢。

  • 如果优化 SQL 能解决,就意味着 SQL 的执行复杂度远远大于它的需求复杂度。

  • 如果调大 buffer pool 能解决,就意味着 MySQL 碰到了自身的某些限制。

  • 如果换 SSD 能解决,那么意味着服务器资源受到了一定的限制。

  • 如果需要搜索引擎,意味着调优这事已经变成了玄学。

本文向大家分享我对 MySQL 慢的诊断思路,以及向大家介绍系统观测工具。

MySQL 慢的诊断思路

MySQL 慢的诊断思路,一般会从三个方向来做:

  1. MySQL 内部的观测

  2. 外部资源的观测

  3. 外部需求的改造

下面依次看一下这几个思路。

MySQL 内部观测

常用的 MySQL 内部观测手段是这样的:

  • 第一步是 Processlist,看一下哪个 SQL 压力不太正常;

  • 第二步是 explain,解释一下它的执行计划;

  • 第三步要做 Profilling,如果这个 SQL 能再执行一次的话, 就做一个 Profilling;

  • 高级的 DBA 会直接动用 performance_schema ,MySQL 5.7 以后直接动用 sys_schema,sys_schema 是一个视图,里面有便捷的各类信息,帮助大家来诊断性能;

  • 再高级一点,会动用 innodb_metrics 进行一个对引擎的诊断。

除了这些手段以外,还有一些乱七八糟的手段就不列在这了,这些是常规的 MySQL 内部状态观测的思路。

外部资源观测

这里引用国外一个大神写的文章,标题是《60 秒的快速巡检》(参考链接在文末)。我们来看一下它在 60 秒之内对服务器到底做了一个什么样的巡检。一共十条命令,下面一条一条来看一下。

  1. uptime,uptime 告诉我们这个机器活了多久,以及它的平均负载是多少。

  2. dmesg -T | tail,告诉我们系统日志里边有没有什么报错。

  3. vmstat 1,告诉我们虚拟内存的状态,页的换进换出有没有问题,swap 有没有使用。

  4. mpstat -P ALL 1,告诉我们 CPU 压力在各个核上是不是均匀的。

  5. pidstat 1,告诉我们各个进程的对资源的占用大概是什么样子。

  6. iostat-xz 1,查看 IO 的问题。

  7. free-m 内存使用率;

  8. sar-n DVE 1,

  9. sar-n TCP, ETCP 1,8 和 9 两条按设备网卡设备的维度,看一下网络的消耗状态,以及总体看 TCP 的使用率和错误率是多少。

  10. top,看一下大概的进程和线程的问题。

这个就是对于外部资源的诊断,这十条命令揭示了应该去诊断哪些外部资源。

外部需求改造

第三个诊断思路是外部的需求改造,在这里引用了 MySQL 官方文档中的一章,《Examples of Common Queries 》( https://dev.mysql.com/doc/mysql-tutorial-excerpt/5.5/en/examples.html),文档中介绍了常规的 SQL 怎么写, 给出了一些例子。

下面看一下它其中提到的一个例子。

这张表有三列,article、dealer、price。它做的事情是从这个表里选取每个作者最贵的商品列在结果集中,这是它最原始的 SQL,非常符合业务的写法,但是它是个关联子查询。

关联子查询成本是很贵的,所以上面的文档会教你快速地把它转成一个非关联子查询,大家可以看到中间的子查询和外边的查询之间是没有关联性的。

第三步,会教大家直接把子查询拿掉,然后转成这样一个 SQL,这个就叫业务改造,前后三个 SQL 的成本都不一样,把关联子查询拆掉的成本,拆掉以后 SQL 会跑得非常好,但这个 SQL 已经不能良好表义了,只有在诊断到 SQL 成本比较高的情况下才建议大家使用这种方式。

为什么它能够把一个关联子查询拆掉?

这背后的原理是关系代数,所有的 SQL 都可以被表达成等价的关系代数式,关系代数式之间有等价关系,这个等价关系通过变换可以把关联子查询拆掉。

总结一下,对于 MySQL 慢的诊断思路如下:

第一,MySQL 本身提供了很多命令来观察 MySQL 自身的各类状态,从上往下检一般能检到 SQL 的问题或者服务器的问题。

第二,从服务器的角度,我们从巡检的脚本角度入手,服务器的资源就这几种,观测手法也就那么几种,把服务器的资源全部都观察一圈就可以了。

第三,如果实在搞不定,需求方一定要按照数据库容易接受的方式去写 SQL,这个成本会下降的非常快,这个是常规的 MySQL 慢的诊断思路。

下面重点介绍为大家介绍系统观测工具。

系统观测工具介绍

先从诊断思路的讨论切换到系统的观测工具,首先了解什么叫系统观测工具并且看一下它的举例,然后再回到诊断思路上,看看新的工具的引入能为我们的思路到底带来怎样的改变。

什么叫系统观测工具

这里也参考了一篇外国人写的文档:

https://jvns.ca/blog/2017/07/05/linux-tracing-systems/

把这个文档拆开,中间描述了三件事情:

  • 系统观测工具的数据源来自于哪里;

  • 数据采集过程,因为采集的是系统的运行状况,所以到底如何采集这是一个难点;

  • 应该怎么看数据,是用图来看,还是用表来看,它就叫数据处理前端。

第一步,我们来看一下数据源,Linux 给我们提供的数据源包括操作系统内核态提供的观测点和用户态提供的观测点,MySQL 很早之前就提供了用户态的观测点。

第二步,如何把数据抽出来。以下这些工具中大家最熟悉的应该是 perf 和 ftrace,sysdig 也有人在用,其它的可能有所耳闻,这是从操作系统里抽取数据的方法。

第三步,数据处理前端,前端常用的也是 perf 和 ftrace。如果大家对 perf 很熟悉的话会知道 perf 出来的数据是一个树形的数据,并可以跟这棵树进行交互,比如说: 查看某个函数运行了多久,哪一个函数的时间最长,这个是数据处理前端。

我们来对比一下常规的四类系统观测工具:ftrace, perf_events,eBPF 和 Systemtap,这四个工具到底有什么不同,看看 Linux 为什么提供这么多观测工具。

ftrace:ftrace 是 sysfs 中的一个桩,通过这个桩内核提供了一种观测的形式——把想观测的函数签名打到这个桩里,然后操作系统就会提供这个函数运行的状况。ftrace 的结构如左图, 数据处理前端和采集端是 ftrace, 数据源是下面这一堆。

perf:常用的 perf 的原理是操作系统提供了一个系统调用可以将数据写到一个缓存中, 然后客户端把这些数据端抽取出来然后呈现在显示器上。

eBPF:这是本文想重点推荐的。以上两种方案一种是操作系统提供的文件系统上的桩,一种是操作系统提供的系统调用,而 eBPF 是将一段代码直接插到操作系统内核某一个位置上的机制

Systemtap:它的原理是将一段 C 的代码编译成一个内核模块,然后将这个模块嵌到内核里边去,它不是由内核提供的一个机制,而是由内核的模块机制提供的一种功能。

介绍了这四种观测工具的不同,大家在选取观测工具的时候就知道应该怎么选。

这四种观测工具对系统伤害最轻的是谁?

对系统伤害最轻的是系统调用,这是系统承诺出来的服务。然后是 ftrace,这是系统在文件系统层面提供的一个口,告诉你可以通过这个口跟系统交互。

对系统侵入性最强的是谁?

对系统侵入性最强的应该是 eBPF,因为它直接将一根代码嵌入到系统里边去做,最不稳定的应该是 System Tap,因为它是系统的一个模块, 又提供了非常复杂的功能。

上图是 eBPF 的架构图,eBPF 先将一段程序编译成二进制代码,然后插入到操作系统里,操作系统运行这段代码的时候,将采集到的数据吐到操作系统本身的空间里,然后再做统一返回。

eBPF 结构最核心的部分在于把代码插入到操作系统中运行,它需要做各种安全保护才能完成这一点,所以这也是这个机制复杂的地方。

下面引用一个开源的 eBPF 脚本集 bcc, 快速看一下 eBPF 能做什么, 这些功能都是开箱即用的。

bcc (eBPF 脚本集) 使用举例

MySQL 的请求延迟分析

一个 MySQL 承担了很多业务,上千个并发˙中,哪一个 SQL 最慢,到底有哪些 SQL 在一秒以上,除了 slow log 以外,还可以用这种方法来看。

这个命令的结果分为三列,它的第一列是请求的延迟,指数级递增,单位是微秒,中间一列是它的命中数,如果有一个请求命中了 64-127 微秒这个区间,命中数会加一,最后一列是它的分布图,它在同一个报告里提供了数值的方式和图的方式,可以很容易看到结果。

对于这台服务器来说,我下了一个 select 的性能压力,它大部分的请求集中于 64 到 127 微秒之间。这个数据库的性能可能还不错。

再来看另外一种压力,我下了一个 select+insert 的混合压力在一个数据库里,它的图又变了,它呈现了一个非常好的双峰图,我将两个峰值用另外一种颜色标明,这两个峰值的意思是很有可能有混合压力在一个数据库里,或者是上面的这部分压力是命中了某些缓存,而下面的某些压力是由于没有命中缓存,导致这部分请求更慢一些, 形成另一个峰值,所以通过这种峰值分析可以看到数据库大概的一个运行状态。

如果能做得更好,你可以抽检自己的数据库然后做环比图,比如今天和昨天同样的时间,同样的业务压力下对数据库的延迟进行分析,如果数据库的延迟峰一直在往后延,就意味着数据库的状态在变得更糟糕一些。这是 bcc 第一个能做的事情,需要再次强调的是它开箱即用直接下载过来就可以使用。

MySQL 的慢查询

MySQL 本身提供很好的慢查询,为什么还要用另外一个机制来获取 MySQL 的慢查询呢?

MySQL 的慢查询可能很难做,与 MySQL 的慢日志相比, 它可以低成本地完成:

  1. 获取少量慢查询

  2. 获取某种模式的慢查询

  3. 获取某个用户的慢查询

比如说获取少量的慢查询,为什么是少量呢?因为不确定现在的线上延迟是多少,慢查询只开一秒可能日志瞬间就被堆上去,性能就会下来,但是如果慢查询开个十秒左右,没有请求在这个区间命中,所以要一点一点的去调这个值,比如说线上 1% 的最慢的查询能够命中,但是在这个脚本里面,可以取一定区间的最大的几个查询把它拎出来。

通过脚本还可以命中某种模式的慢查询, 比如说我们只关心 update 的慢查询, 那么获取 select 的结果就没有太大的意义,或者是我一定要获取某一些特定表的相关的查询,我都可以通过脚本来做。

第三种情况,想获取某个用户的慢查询,这个一般对于多租户系统,因为多租户系统只想针对某一个用户进行慢查询分析的时候,这种脚本就比较好用。

VFS 延迟分析

对 VFS 做延迟分析,这是对数据库进行了一个写压力,可以明显看到一个双峰图,这是写的两个峰,是数据库对于内核的写压力的反馈。

这个意味着什么呢?这个可能意味着因为这部分的写是命中了操作系统文件系统的缓存,而下面这部分写是真正的写穿到设备的,所以他们俩的延迟不一样,这是一个典型的双峰图,大家需要把两个峰拆开来去行这样的分析。

换一个说法,如果写压力都集中在这里,而没有第二个峰的情况下,需不需要去更换物理设备?有可能不需要,因为所有的东西都命中了操作系统的缓存。

短生命周期的临时文件检测

这个不一定常见,MySQL 会在某些情况下动用临时表, 如果 SQL 没写好就会创建临时表,这些临时表的生命周期很短,但是量很大,所以一定要写文件而不能内存里。

在这种情况下会对操作系统造成一些压力,而这个压力又不太好诊断,因为临时文件的生存周期短,所以这个脚本可以帮大家提供一个方案,这个方案的结果是这样子。

我做了一个临时表,这个临时表活了 5.3 秒左右,于是它展现在了脚本的结果里。如果扫描自己的线上 MySQL 发现这里有大量的东西说明在大量的使用临时表,如果 IO 压力在此时比较大, 就可能受了临时表的影响。

短连接分析

好一点的应用都会用连接池,但是我们很多的时候没有那么好的运气,老碰到那么好的应用,所以经常业务会扔过来大量的短连接。

这个例子中, sysbench 上了一个大并发,但是只活了 300 多毫秒,这些连接都只活了 300 多毫秒,反复运行这个 sysbench 就可以将数据库打死,建立一千个连接,300 毫秒以后也会销毁,再建立一千个连接,你的业务就会忽上忽下,通过这个脚本就可以抓到这个压力从哪个服务器来的,哪个端口来的,然后把它搞定就可以了。

长连接分析

除了短连接分析,还有长连接分析,哪一个业务端老在搞我的数据,老在往里写,总在往里读,搞的网络特别慢。

可以帮大家提供这样一个视角,它有读有写。

以上几个 bcc 相关的例子都是现成的脚本。bcc 可以观测操作系统的各个方面,比如说如果有东西被 OOM kill 掉了,内存有泄露的也可以看,它基本上是我们这几年发现的一个宝库,大家直接调用这些脚本就可以完成很多的别人完成不了的分析,它的技术用的是 eBPF,直接在 github 上直接搜就行了。

eBPF 使用方法 / 限制

如果这里边脚本满足不了要求, 那可以自己写。这里介绍一下脚本的写法以及 eBPF 的限制。

拿上面提到过的 MySQL 延迟分析举例,一个 MySQL 上面有一千个 query,这些 query 大概都落在哪个延迟时间里面那张图,为了完成这个需求, 我需要写两段程序,其中第一段程序是运行在内核里边的程序。

这段程序的逻辑是这样的:

  • 在 query 开始的时候截获一下,让它记录一个时间戳;

  • 请求结束的时候再截获一下记录一个时间戳;

  • 把两个时间戳相减获得一个延迟;

  • 把这个延迟扔到结果集里边去,程序就完成了。

我用结束时间减开始时间,减一下得到一个延迟,然后把延迟扔到一个统计容器里面,这个事就结束了。这是我要写的第一个程序,是嵌到内核里的程序,但是需要一个外壳的程序负责嵌入。

这个外壳程序的逻辑也非常简单,把刚才那段内核的程序嵌到 MySQL 的观测点上,嵌到内核里面去,然后把结果集拿出来,打印出来就结束了,这是如何写一个 eBPF 的脚本,大家唯一需要做的事情就是这两个程序,然后运行一下。

这个程序的核心只有 45 行,中间忽略了负责差错处理的一部分。只需要把现在的脚本拿下来抄一抄,改一改就可以完成很多的功能了。

这么好的方法为什么很多人不知道呢?

  1. 操作系统内核的限制,这个功能是 Linux 4.4 引进来的,但是在 Linux 4.4 上存在统计的 bug,我们推荐的是 Linux 4.9+,部分好用的功能是在 4.13+ 上才开放,这个是 eBPF 最大的限制。怎么办呢?只能祝大家长寿吧!活到 Linux 4.x 内核能在生产环境上使用的那一天。

  2. 它的第二个最大的限制是 MySQL 的编译参数,MySQL 虽然在很早的时候已经提供了 dtrace 的观测点,这些观测点是公用的,但是它在默认的编译出来的官方发布的包里边是不带观测点编译的,所以在直接官方发布的二进制的包里边是用不了这个功能的,大家需要自己编译一下。编译的时候需要带这个参数,这个可能也是属于一个比较大的限制。

所以如果大家受到限制,再推荐换一个工具:systemtap。

Linux 2.6 就已经有了,但是它的机制是写一个内核模块,这种机制其实不是特别稳定,它为了解决不是特别稳定的问题增加了若干限制,比如说能在内核中使用的内存大小有限制,采集频率也有限制,对整个内核性能的影响百分比也有限制,在这些限制参数都开起来的情况下,它还是比较安全的。

但是很多观测功能就必须要把这些限制关掉,一旦关掉内核就不是很稳定,所以这个工具,我没有敢把它的缺点写在上面因为确实是个好的工具,我们也很难说它的这个缺点是个致命的缺陷,但是不太推荐在生产环境上使用,但是在测试环境上确实是非常好玩的一个工具,如果大家用不了 eBPF 的话可以用 systemtap 来做一些诊断。

还有很多其他的工具:

至于如何选择,大家直接谷歌一下有专门的文章教大家怎么选择这些观测工具。但是总的来说没有一个科学的思路,只有尝试,不停的尝试。

参考链接:

60 秒的快速巡检:

https://medium.com/netflix-techblog/linux-performance-analysis-in-60-000-milliseconds-accc10403c55

本文 PPT 下载链接:github.com/actiontech/slides