MySQL性能调优实践

无论你使用什么软件,你都有可能迟早会遇到性能不尽如人意的情况。MySQL也不例外。如果你不熟悉MySQL,那么它是一个通用的关系型数据库管理系统(RDBMS)(通过MySQL文档存储支持NoSQL)。它的性质是通用的,因此允许任意复杂的查询(在语法规则和限制范围内),而且它经常被用作关键业务和面向客户的软件的数据存储,这意味着性能调整既重要又不简单。

本博客通过一些最佳实践来介绍MySQL的性能调优。如果其中一些建议听起来与其他类型的性能调优很熟悉,那么这是因为它们是--MySQL性能调优与其他类型的软件有很多共同之处。 鉴于MySQL性能调优是一个非常广泛的主题,我将只关注高层次的最佳实践,而不涉及具体建议。我的前六个最佳实践是:

  1. 对最佳做法保持警惕
  2. 监控
  3. 有条不紊地工作
  4. 考虑到整个堆栈
  5. 进行小的、渐进的改变
  6. 理解变化

让我们更详细地了解它们。

对最佳实践保持警惕

在最佳实践列表的开头,警告要对最佳实践保持警惕,这听起来可能很奇怪。然而,这一点非常重要,尤其是建议越具体越好。

我的意思是,你总是需要考虑到你的具体情况。如前所述,MySQL是一个通用的数据库,所以你的使用方式将不同于其他人的使用方式,最佳配置、环境和查询优化策略也是如此--或者换句话说 "没有两个系统是相同的"。此外,MySQL在不断发展,一个在早期版本中很好的建议可能不再那么好了。

让我举一个例子。MySQL有配置选项来指定各种缓冲区的大小;其中两个是sort_buffer_size和join_buffer_size。一个常见的最佳做法是保持这些缓冲区不大于256KiB,除非特定的查询已被证明受益于更大的值。然而,在MySQL 8.0.12及以后的版本中,MySQL使用不同的算法来分配排序缓冲区,这意味着在一般情况下增加它是没有问题的,在MySQL 8.0.17及以后的版本中,支持使用连接缓冲区的哈希连接,所以你可能需要增加它以充分利用新功能。(话虽如此,但现在还不要去开始增加缓冲区,因为缓冲区太大,还是有弊端的。)

这种 "曾经是真实的,可能不再适用 "的概念不仅限于功能和算法,而且也适用于硬件。你仍然会发现源于2005年或更早的最佳实践,当时一台拥有8GiB内存和4个CPU的服务器是相当强大的,但现在我用来写这篇博客的笔记本电脑里有更多的内存。总之,在考虑最佳做法时,你需要把这些事情考虑进去。

监控

在任何一种性能调整中,监控数据是成功的关键。这适用于两个层面:了解你所使用的产品,并拥有关于所做工作的指标。后者是通过监控获得的。虽然有些指标很适合临时收集,比如执行一个特定的查询所需的时间,或者报告的查询计划很慢,但一般来说,你需要一个专门的监控解决方案。我对监控的一些建议是。

  • 选择一个也能收集查询信息的监控解决方案
  • 花点时间了解你的监控解决方案
  • 努力消除假阳性警报
  • 将警报配置为适当的严重程度和警报机制

当执行一个比 "这个查询很慢 "更一般的性能问题时,最有用的信息来源之一是查询分析功能,你可以看到在问题发生时执行了哪些查询。

无论你的监控解决方案有多好,它的价值只在于你知道如何使用它。在危机发生前花点时间熟悉你所选择的解决方案,这样你就可以立即浏览到可以帮助你解决你所遇到的问题的数据,并确保你对它进行了配置以捕获你所需要的信息。

与监控相关的另一个有用的功能是通过电子邮件、文本信息、到聊天频道或类似的方式向你发送警报。我最讨厌的是,每当你得到一个假阳性结果,或者你在凌晨2:30被一个很容易等到早上的警报吵醒时,你最终忽略一个重要警报的风险就越高。因此,重要的是,你要努力避免误报,并确保你以适当的方式接收警报,这样你就不会被打扰到超过必要的时间。

当你需要解决一个问题时,良好的监控是必不可少的,这一点将在接下来讨论。

有条不紊地工作

当你遇到一个性能问题,并且你有来自管理层和终端用户的压力时,你可能会觉得很诱人,因为你会跟随自己的反应,做出第一个想到的改变,努力快速解决问题。然而,这样做往往会适得其反,即使它看起来很有效,你也没有办法确定是否真的是这些改变带来了变化。相反,你需要通过一系列的阶段,有条不紊地工作。

  • 核实问题。
  • 确定原因。
  • 确定解决方案。
  • 实施所选择的解决方案。

 每个阶段由一个或多个步骤组成。通过记录每个阶段,你也可以以此作为帮助,在未来快速识别和解决类似问题。你可以用一个循环来说明这些阶段,每一步都紧跟前一步。

MySQL性能调优实践_第1张图片

这些阶段在这里显示为一个循环,因为性能不是一个二分状态,所以一般来说,你会不断地努力提高性能,周期之间的延迟或长或短。一个特定的问题也可能需要几个周期才能完全解决。让我们更详细地了解一下这些阶段中的每一个。

a.核实问题
验证问题是尽可能具体的问题,并找到证明它是如此的证据。例如,一个最终用户可能会说 "查询很慢",但这并不能真正解释问题是什么。它是什么查询?是应用程序、网络、MySQL,还是其他什么东西慢了(在下一个关于考虑整个堆栈的建议中更多)?查询慢是什么意思?

你的工作是确保你有这些问题的答案,以及问题的证明。例如,证明可以是在你的监控解决方案中收集一个显示问题的基线,或者手动执行查询并验证执行时间是否像声称的那样慢。这可能看起来像不信任,但并不是这样的意思。相反,系统是复杂的,报告问题的人一般不具备所需的知识,无法准确知道根本问题是什么。所以,验证是为了避免你最终陷入盲目的追寻中。基准线也是必不可少的,可以证明当你实施解决方案时,该解决方案是否有效。

作为验证问题的一部分,你还需要确定什么是 "足够好"。如前所述,性能不是一个二进制的状态,所以如果你不定义什么是 "足够好 "的性能,你将永远无法完成。

一旦你验证了问题,你就可以进入确定原因的阶段。

b.确定原因
调查一个性能问题最困难的部分往往是确定原因。确保你思想开放,并考虑整个堆栈,因为即使问题明确显示在MySQL中,原因可能在堆栈的不同部分。具有创造性也是值得的,不要只考虑标准的解决方案。一旦你相信你已经确定了原因,记得要加上你已经找到原因的理由。
 

c.确定解决方案
下一步是确定一个解决方案。在这里,你同样需要保持选择的开放性,而不仅仅是考虑在发现原因的特定区域内的可能解决方案。有时(后面会有一个例子),你可以在堆栈的不同部分或使用不同的功能或类似的地方找到解决方案。至于原因,请注意为什么你认为每个潜在的解决方案都能成功。一旦完成,选择你认为最好的解决方案来实施。注意,你可以选择一个解决方案作为权宜之计,而选择另一个作为长期的解决方案。
 

d.实施解决方案
最后一个阶段是实施解决方案。你可以通过创建一个行动计划,并在你的测试和暂存系统上测试该解决方案。确保你在测试周期中不断更新行动计划。最理想的情况是,解决方案的实施方式将允许你自动应用它,所以你要确保在生产系统中应用的解决方案与制定行动计划时测试的解决方案相同。

实施解决方案的一个重要部分就是要验证变化的效果。否则你就不知道你是否真的解决了问题。这就是你需要验证的基线的地方,因为这可以让你比较变化前后的系统表现。

我已经提到过几次 "整个堆栈"。让我们来讨论这个问题。

考虑整个堆栈

当你使用MySQL时,你不能只关注MySQL本身来解决你所遇到的性能问题。MySQL是一个更大的堆栈的一部分,从终端用户开始,一直到最底层的硬件。一个简化的堆栈可以在下图中看到。

MySQL性能调优实践_第2张图片

注意,箭头是双向的,以说明互动是如何进行的。在这个例子中,应用程序通过网络向执行查询的MySQL发送查询。这种执行涉及使用操作系统(如I/O缓存和文件系统),而操作系统又使用主机中的硬件。主机可以是裸机,也可以是虚拟机。在虚拟机的情况下,堆栈通过管理程序继续向下到底层硬件。

一旦主机执行了它的动作,结果就被送回操作系统和MySQL。这允许MySQL完成查询,结果可以通过网络被送回应用程序。为了说明堆栈中的这些层是如何相互作用的,并可能意味着原因和解决方案并不在你最初期望的地方,值得考虑两个现实世界的例子。

在第一个案例中,数据库管理员注意到MySQL随机停顿。这是在MySQL NDB Cluster中,它有独立的数据节点来存储数据。当停滞持续超过18秒时,数据节点将被关闭,以防止拖累集群的其他部分。用top、mpstat和iostat等Linux命令进行的初步测试表明,停顿可以持续一分钟以上。这已经意味着焦点转移到了MySQL之外,因为在MySQL数据节点关闭之后,停顿还在持续。使用perf工具发现,根本原因是Linux正在进行内存压缩。这是由于MySQL试图在网络上发送TCP流量而引发的,由于TCP需要一个由连续的内存(kmalloc())组成的缓冲区,而内存是非常零散的,因此需要进行碎片整理。那么,为什么会发生这种情况呢?事实证明,服务器的内存比MySQL使用的内存要多得多,所以Linux把剩下的内存用于一个巨大的I/O缓存,这个缓存导致了内存碎片化。解决方案是在MySQL中启用直接I/O,这大大减少了对操作系统I/O缓存的使用,这就减少了内存碎片。

第二个案例是针对MySQL服务器(通常被认为是 "MySQL")。一个应用程序从使用Microsoft SQL Server迁移到使用MySQL。在迁移之后,有人报告说查询非常慢,而且MySQL不时地会阻止应用程序的连接。原因是应用程序使用的框架中的一个错误,这意味着缺少一个WHERE子句。这使得一个特定的查询扫描了大量的数据,这意味着它花费了这么长的时间,以至于应用程序重新提交了它。最终,在一个相对较小的虚拟机上,有多达50个相同的查询副本在并行执行。随着第一个查询将数据读入内存,随后的查询会变得越来越快,所以它们都会在同一时间完成。现在,50个查询都在向应用程序发送巨大的结果集,使网络过载,并使应用程序的内存耗尽,从而导致崩溃。网络的过载反过来意味着网络数据包被丢弃,导致握手错误,最终使MySQL将应用程序的主机列入黑名单。

这些例子处于问题的极端,但它们很好地说明了一个地方的症状是如何由堆栈的不同部分的行为或错误引起的。对于第一个例子,解决方案可能不在造成问题的堆栈的同一部分。

做一些小的渐进式的改变

当你遇到一个性能问题,并且你有几个你认为有助于提高性能的变化时,你很想把它们同时进行。虽然这看起来是一条捷径,但实际上它可能会引起挫折感,并让你相信这些解决方案并不奏效,即使其中一些方案确实有效。问题是,你最终可能会让两个变化相互抵消,所以看起来两个变化都不起作用。这就是原因,你的目标应该是一次只做一个改变,这样就可以清楚地知道每个改变是否有效。

同样地,对于每一个改变,都要做小的增量改变。例如,对于配置选项,可能会有一个最佳性能的甜蜜点。如果你做了大的改动,你可能会绕过这个甜蜜点,而看起来这个改动是不起作用的。通过做几个小的改变,你可以寻找到甜蜜点。
 

理解变化

我的最后一个最佳实践是确保你理解你正在做的改变。为什么我提出这一点,最好用一个例子来说明。MySQL有一个叫做二进制日志的日志,它记录了所有的改变,所以你可以在副本上或在时间点恢复期间再次应用它们。假设你验证了写入二进制日志会导致性能问题,你就会寻找解决方案。一个建议是将选项sync_binlog设置为0。你测试了一下,果然性能提高了。结案了?不,没那么快。

当你把sync_binlog设置为0时会发生什么?该选项指定了二进制日志必须被刷新到磁盘的频率。值为1意味着每次事务提交,值为100意味着每一百次提交,而值为0意味着 "从不"。

刷新写到磁盘是相对昂贵的,这也解释了为什么禁用强制刷新可以提高写性能。然而,它有两个副作用,你需要考虑。首先,一个写内容只有在被刷新后才会被持久化,在此之前,它只存在于内存中。因此,通过将sync_binlog设置为0,如果MySQL崩溃,你有可能丢失写入的数据。这也意味着,如果复制源崩溃,你将不得不重新创建所有的复制,而且在时间点恢复的情况下,你可能无法恢复所有数据。你准备好支付这个代价了吗?如果没有,这个解决方案将不适合你。

将sync_binlog设置为0的一个更微妙的副作用是,最终所有写入的数据都会在磁盘上结束。一个二进制日志文件最多只能有1吉布(加上最后一个事务的大小),而且二进制日志文件在旋转时总是被刷到磁盘上。由于服务器有几十到几百GB的内存,通常在sync_binlog = 0的情况下,整个文件最终会被缓存在内存中,而且一GB的交易事件会同时被刷新。在刷新过程中,不能进行任何提交,所以你可能会看到每次二进制日志旋转时都有几秒钟的停顿。换句话说,sync_binlog = 0会提高吞吐量,但sync_binlog = 1会给出最稳定的提交延迟。

理解这些相互作用没有捷径。它需要通过与MySQL一起工作和研究现有的信息来源来获得你自己的经验。一个可以帮助你进行MySQL性能调优(特别是与查询有关)的来源是《MySQL 8查询性能调优》。

你可能感兴趣的:(技术,mysql,数据库,nosql)