高性能 MySQL 第四版(GPT 重译)(一)(1)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 高性能 MySQL 第四版(GPT 重译)(一)

前言

Oracle 维护的官方文档为您提供了安装、配置和与 MySQL 交互所需的知识。本书作为该文档的伴侣,帮助您了解如何最好地利用 MySQL 作为强大的数据平台来满足您的用例需求。

本版还扩展了合规性和安全性在操作数据库足迹中的日益重要作用。隐私法律和数据主权等新现实已经改变了公司产品构建的方式,这自然地引入了技术架构如何演变的新复杂性。

本书适合对象

本书首先面向希望提升在运行 MySQL 方面的专业知识的工程师。本版假设读者熟悉为什么要使用关系数据库管理系统(RDBMS)的基本原则。我们还假设读者具有一些一般系统管理、网络和操作系统的经验。

我们将为您提供在现代架构和更为更新的工具和实践下运行 MySQL 的经验丰富的策略。

最终,我们希望您从本书中对 MySQL 内部和扩展策略的知识中获益,帮助您在组织中扩展数据存储层。我们希望您新获得的见解将帮助您学习和实践一种系统化的方法来设计、维护和故障排除基于 MySQL 构建的架构。

这个版本有何不同

高性能 MySQL已经成为数据库工程社区多年的一部分,之前的版本分别在 2004 年、2008 年和 2012 年发布。在这些先前的版本中,目标始终是通过专注于深度内部设计,解释各种调整设置的含义,并为用户提供改变这些设置的知识,教导开发人员和管理员如何优化 MySQL 以获得最佳性能。本版保持了相同的目标,但侧重点不同。

自第三版以来,MySQL 生态系统发生了许多变化。发布了三个新的主要版本。工具景观大大扩展,超越了 Perl 和 Bash 脚本,进入了完整的工具解决方案。全新的开源项目已经建立,改变了组织如何管理扩展 MySQL 的方式。

甚至传统的数据库管理员(DBA)角色也发生了变化。行业中有一个老笑话说 DBA 代表“别烦问”。DBA 因为数据库没有像周围的软件开发生命周期(SDLC)一样快速发展而被认为是软件开发生命周期中的绊脚石,这并不是因为他们有什么脾气暴躁的态度,而只是因为数据库的发展速度没有跟上周围 SDLC 的步伐。

像 Laine Campbell 和 Charity Majors(O’Reilly)合著的数据库可靠性工程:设计和运营弹性数据库系统这样的书籍,已经成为技术组织将数据库工程师视为业务增长的推动者而不是所有数据库的唯一运营者的新现实。曾经 DBA 的主要日常工作涉及模式设计和查询优化,现在他们负责教导开发人员这些技能,并管理允许开发人员快速安全地部署自己模式更改的系统。

通过这些变化,重点不再是优化 MySQL 以获得几个百分点的速度。我们认为高性能 MySQL现在是为人们提供他们需要的信息,以便就如何最好地使用 MySQL 做出明智决策。这始于理解 MySQL 的设计,然后理解 MySQL 擅长和不擅长的地方。¹ 现代版本的 MySQL 提供合理的默认设置,除非您遇到非常具体的扩展问题,否则几乎不需要进行调整。现代团队现在正在处理模式更改、合规问题和分片。我们希望高性能 MySQL成为现代公司如何大规模运行 MySQL 的全面指南。

本书使用的约定

本书使用以下排版约定:

斜体

表示新术语、URL、电子邮件地址、文件名和文件扩展名。

常量宽度

用于程序清单,以及段落内引用程序元素,如变量或函数名称、数据库、数据类型、环境变量、语句和关键字。

常量宽度粗体

显示用户应按照字面意义输入的命令或其他文本。

常量宽度斜体

显示应替换为用户提供的值或由上下文确定的值的文本。

提示

此图标表示提示或建议。

注意

此图标表示一般说明。

警告

此图标表示警告或注意事项。

第四版致谢

来自 Silvia

首先,我要感谢我的家人。我的父母为了把我和我的兄弟带到美国,牺牲了在埃及的稳定工作和生活。我的丈夫 Armea,在我接受挑战的过去几年中一直支持我,最终实现了这一成就。

我作为一个移民从中东的大学时代离开,实现了移居美国的梦想。在加利福尼亚州的一所州立大学获得学位后,我在纽约市找到了一份工作,我记得这本书的第二版是我用自己的钱买的第一本不是教科书的技术书籍。我要感谢前几版的作者教给我许多基本的经验,为我在职业生涯中管理数据库做好准备。

我感激我职业生涯中与之合作过的许多人的支持。他们的鼓励让我写下了这本书的这一版,这本书在我职业生涯的早期教会了我很多。我要感谢 Tim Jenkins,SendGrid 的前首席技术官,他雇佣了我这份终身职位,尽管我在面试中告诉他他错误地使用了 MySQL 复制,但他还是信任我,结果证明这是一艘火箭。

我要感谢所有在科技领域的了不起的女性,她们是我的支持网络和啦啦队。特别感谢 Camille Fournier 和 Nicole Forsgren 博士,因为她们写的两本书影响了我过去几年的职业生涯,改变了我对日常工作的看法。

感谢我在 Twilio 团队的同事们。感谢 Sean Kilgore 让我成为一个更优秀的工程师,关心的不仅仅是数据库。感谢 John Martin 是我曾经合作过的最乐观的人。感谢 Laine Campbell 及其 PalominoDB 团队(后来被 Pythian 收购)在我最艰难的岁月中给予的支持和教导,以及 Baron Schwartz 鼓励我写下我的经历。

最后,感谢 Virginia Wilson 是一位出色的编辑,帮助我将我的思绪转化为通顺的句子,并在整个过程中给予我如此多的支持和优雅。

来自 Jeremy

当 Silvia 找我帮忙写这本书时,正值大多数人生活中异常紧张的时期——全球大流行,始于 2020 年。我不确定是否想要给自己的生活增加更多压力。我的妻子 Selena 告诉我,如果我不接受,我会后悔的,而我知道不应该和她争论。她一直支持我,鼓励我成为我能成为的最好的人。我将永远爱她,感激她为我所做的一切。

致我的家人、同事和社区朋友们:没有你们,我绝对不可能走到今天这一步。你们教会了我如何成为今天的自己。我的职业生涯是与你们共同经历的总和。你们教会了我如何接受批评,如何以身作则领导,如何失败并重新站起,最重要的是,团队的力量胜过个人的能力。

最后,我要感谢 Silvia,她信任我为这本书带来共同的理解但不同的视角。我希望我达到了你的期望。

致技术审阅者

作者还要感谢帮助将这本书推向今天这一地步的技术审阅者们:Aisha Imran、Andrew Regner、Baron Schwartz、Daniel Nichter、Hayley Anderson、Ivan Mora Perez、Jam Leoni、Jaryd Remillard、Jennifer Davis、Jeremy Cole、Keith Wells、Kris Hamoud、Nick Vyzas、Shubheksha Jalan、Tom Krouper 和 Will Gunty。感谢你们的时间和努力。

¹ 众所周知,人们经常将 MySQL 用作队列,然后才发现这样做是错误的。最常见的原因是轮询新队列操作的开销、锁定记录以进行处理的管理,以及随着数据增长而变得笨重的队列表格。

第一章:MySQL 架构

MySQL 的架构特点使其适用于各种用途。虽然它并非完美,但足够灵活,可以在小型和大型环境中都能很好地运行。从个人网站到大型企业应用程序都适用。要充分利用 MySQL,您需要了解其设计,以便与之合作,而不是对抗它。

本章概述了 MySQL 服务器架构的高层概述,存储引擎之间的主要区别以及这些区别的重要性。我们试图通过简化细节并展示示例来解释 MySQL。这个讨论对于那些对数据库服务器新手以及对其他数据库服务器是专家的读者都将有用。

MySQL 的逻辑架构

对 MySQL 组件如何协同工作有一个清晰的心理图像将有助于您理解服务器。图 1-1 展示了 MySQL 架构的逻辑视图。

最顶层的层级是客户端,包含的服务并非 MySQL 独有。这些服务是大多数基于网络的客户端/服务器工具或服务器所需的服务:连接处理、身份验证、安全等。

第二层是事情变得有趣的地方。MySQL 的大部分智慧都在这里,包括查询解析、分析、优化以及所有内置功能的代码(例如日期、时间、数学和加密)。在这个层面提供的任何功能都跨存储引擎:存储过程、触发器和视图,例如。

第三层包含存储引擎。它们负责存储和检索 MySQL 中存储的所有数据。就像 GNU/Linux 中提供的各种文件系统一样,每个存储引擎都有其自己的优点和缺点。服务器通过存储引擎 API 与它们通信。该 API 隐藏了存储引擎之间的差异,并在查询层面上使它们基本透明。它还包含几十个低级函数,执行诸如“开始事务”或“获取具有此主键的行”等操作。存储引擎不解析 SQL¹,也不相互通信;它们只是响应服务器的请求。

图 1-1 MySQL 服务器架构的逻辑视图

连接管理和安全

默认情况下,每个客户端连接在服务器进程内部都有自己的线程。连接的查询在该单个线程内执行,该线程又位于一个核心或 CPU 上。服务器维护一个准备好使用的线程缓存,因此它们不需要为每个新连接创建和销毁。²

当客户端(应用程序)连接到 MySQL 服务器时,服务器需要对其进行身份验证。身份验证基于用户名、来源主机和密码。也可以在传输层安全(TLS)连接中使用 X.509 证书。一旦客户端连接,服务器会验证客户端是否对其发出的每个查询具有权限(例如,客户端是否被允许发出访问 world 数据库中 Country 表的 SELECT 语句)。

优化和执行

MySQL 解析查询以创建内部结构(解析树),然后应用各种优化。这些优化包括重写查询、确定读取表的顺序、选择使用哪些索引等。您可以通过查询中的特殊关键字向优化器传递提示,影响其决策过程。您还可以要求服务器解释优化的各个方面。这让您了解服务器正在做出的决策,并为重新调整查询、模式和设置提供参考,使一切尽可能高效地运行。在第八章中有更详细的内容。

优化器并不真正关心特定表使用的存储引擎是什么,但存储引擎确实会影响服务器优化查询的方式。优化器向存储引擎询问一些能力以及某些操作的成本,还会请求表数据的统计信息。例如,某些存储引擎支持对某些查询有帮助的索引类型。您可以在第六章和第七章中了解更多关于模式优化和索引的内容。

在旧版本中,MySQL 利用内部查询缓存来查看是否可以从中提供结果。然而,随着并发性的增加,查询缓存成为一个臭名昭著的瓶颈。截至 MySQL 5.7.20,查询缓存正式被废弃为 MySQL 的一个特性,并在 8.0 版本中,查询缓存被完全移除。尽管查询缓存不再是 MySQL 服务器的核心部分,但缓存频繁提供的结果集是一个好的实践。虽然超出了本书的范围,但一个流行的设计模式是在 memcached 或 Redis 中缓存数据。

并发控制

每当多个查询需要同时更改数据时,就会出现并发控制问题。对于本章的目的,MySQL 必须在两个级别进行并发控制:服务器级别和存储引擎级别。我们将为您简要介绍 MySQL 如何处理并发读取和写入,以便您在本章的其余部分中获得所需的背景知识。

为了说明 MySQL 如何处理对同一组数据的并发工作,我们将以传统的电子表格文件为例。电子表格由行和列组成,就像数据库表一样。假设文件在您的笔记本电脑上,并且只有您可以访问它。没有潜在的冲突;只有您可以对文件进行更改。现在,想象您需要与同事共同使用该电子表格。它现在位于您和同事都可以访问的共享服务器上。当您和同事同时需要对此文件进行更改时会发生什么?如果我们有一个整个团队的人正在积极尝试编辑、添加和删除此电子表格中的单元格,会发生什么?我们可以说他们应该轮流进行更改,但这并不高效。我们需要一种允许高容量电子表格并发访问的方法。

读/写锁

从电子表格中读取并不那么麻烦。多个客户端同时读取同一文件没有问题;因为他们没有进行更改,所以不太可能出错。如果有人尝试在其他人正在读取电子表格时删除A25单元格会发生什么?这取决于情况,但读者可能会得到损坏或不一致的数据视图。因此,即使从电子表格中读取也需要特别小心。

如果您将电子表格视为数据库表,很容易看出在这种情况下问题是相同的。在许多方面,电子表格实际上只是一个简单的数据库表。修改数据库表中的行与删除或更改电子表格文件中的单元格内容非常相似。

解决这个经典的并发控制问题相当简单。处理并发读/写访问的系统通常实现由两种锁类型组成的锁定系统。这些锁通常被称为共享锁排他锁,或读锁和写锁。

不用担心实际的锁定机制,我们可以描述概念如下。对于资源的读锁是共享的,或者说是相互非阻塞的:许多客户端可以同时从资源中读取,而不会相互干扰。另一方面,写锁是排他的——也就是说,它们会阻止读锁和其他写锁——因为唯一安全的策略是在给定时间内只允许单个客户端向资源写入,并在客户端写入时阻止所有读取。

在数据库世界中,锁定一直在发生:MySQL 必须防止一个客户端在另一个客户端更改数据时读取数据。如果数据库服务器表现得符合要求,那么锁定的管理速度足够快,以至于客户端几乎察觉不到。我们将在第八章中讨论如何调整查询以避免由锁定引起的性能问题。

锁定粒度

提高共享资源并发性的一种方法是更加选择性地锁定你要锁定的内容。而不是锁定整个资源,只锁定包含你需要更改的数据的部分。更好的是,只锁定你计划更改的确切数据片段。在任何给定时间最小化你锁定的数据量,让对给定资源的更改可以同时发生,只要它们不相互冲突。

不幸的是,锁并不是免费的——它们会消耗资源。每个锁操作——获取锁、检查锁是否空闲、释放锁等——都有开销。如果系统花费太多时间管理锁而不是存储和检索数据,性能可能会受到影响。

锁定策略是锁定开销和数据安全之间的一种折衷,这种折衷会影响性能。大多数商用数据库服务器并不给你太多选择:在你的表中,你得到的是所谓的行级锁定,有各种复杂的方式来提供许多锁的良好性能。锁是数据库如何实现一致性保证的方式。一个数据库的专家操作员必须深入阅读源代码,以确定最适合的一组调整配置,以优化速度与数据安全之间的这种权衡。

另一方面,MySQL 提供了选择。它的存储引擎可以实现自己的锁定策略和锁定粒度。锁管理是存储引擎设计中非常重要的决定;将粒度固定在某个级别可以提高某些用途的性能,但使该引擎不太适合其他用途。因为 MySQL 提供了多个存储引擎,它不需要一个单一的通用解决方案。让我们看看两种最重要的锁定策略。

表锁

MySQL 中最基本的锁定策略,也是开销最低的策略是表锁。表锁 类似于前面描述的电子表格锁:它锁定整个表。当客户端希望写入表(插入、删除、更新等)时,它会获取写锁。这会阻止所有其他读取和写入操作。当没有人在写入时,读者可以获取读锁,这些读锁不会与其他读锁冲突。

表锁在特定情况下有改进性能的变体。例如,READ LOCAL 表锁允许某些类型的并发写操作。写锁和读锁队列是分开的,写队列完全比读队列的优先级高。³

行锁

提供最大并发性(并带来最大开销)的锁定风格是使用行锁。回到电子表格的类比,行锁 就像只锁定电子表格中的行一样。这种策略允许多人同时编辑不同的行,而不会相互阻塞。这使服务器能够进行更多并发写入,但代价是需要跟踪谁拥有每个行锁,它们已经打开多久,以及它们是什么类型的行锁,以及在不再需要时清理锁。

行锁是在存储引擎中实现的,而不是在服务器中。服务器大部分时间⁴ 对在存储引擎中实现的锁定是不知情的,正如你将在本章和整本书中看到的,存储引擎都以自己的方式实现锁定。

事务

在深入研究数据库系统的更高级功能之前,你会发现事务的存在。事务是一组 SQL 语句,被视为一个原子单元的工作。如果数据库引擎可以将整组语句应用到数据库中,它会这样做,但如果由于崩溃或其他原因无法完成其中任何一个,那么所有语句都不会被应用。要么全部成功,要么全部失败。

这一部分与 MySQL 无关。如果你已经熟悉 ACID 事务,请随时跳到“MySQL 中的事务”。

银行应用程序是为什么需要事务的经典例子。想象一个银行的数据库有两个表:支票和储蓄。要将$200 从简的支票账户转移到她的储蓄账户,你需要至少执行三个步骤:

  1. 确保她的支票账户余额大于$200。
  2. 从她的支票账户余额中减去$200。
  3. 给她的储蓄账户余额加上$200。

整个操作应该包裹在一个事务中,这样如果任何一个步骤失败,已完成的步骤可以被回滚。

你可以使用START TRANSACTION语句开始一个事务,然后使用COMMIT使其更改永久化,或者使用ROLLBACK放弃更改。因此,我们示例事务的 SQL 可能如下所示:

1  START  TRANSACTION;
2  SELECT balance FROM checking WHERE customer_id = 10233276;
3  UPDATE checking SET balance = balance - 200.00 WHERE customer_id = 10233276;
4  UPDATE savings SET balance = balance + 200.00 WHERE customer_id = 10233276;
5  COMMIT;

事务本身并不是全部。如果数据库服务器在执行第 4 行时崩溃会发生什么?谁知道呢?客户可能刚刚损失了200。如果另一个进程在第3行和第4行之间出现并移除整个支票账户余额会发生什么?银行已经给客户提供了200。如果另一个进程在第 3 行和第 4 行之间出现并移除整个支票账户余额会发生什么?银行已经给客户提供了200 的信用,甚至自己都不知道。

在这个操作序列中还有很多失败的可能性。你可能会遇到连接中断、超时,甚至在操作中途数据库服务器崩溃。这通常是为什么高度复杂和缓慢的两阶段提交系统存在的原因:以减轻各种故障场景。

事务并不足够,除非系统通过 ACID 测试。ACID 代表原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。这些是数据安全事务处理系统必须满足的紧密相关标准:

原子性

事务必须作为一个单一不可分割的工作单元运行,以便整个事务要么被应用,要么永远不被提交。当事务是原子的时,不存在部分完成的事务:要么全部成功,要么全部失败。

一致性

数据库应该始终从一个一致的状态转移到下一个一致的状态。在我们的示例中,一致性确保在第 3 行和第 4 行之间发生崩溃时,支票账户中不会消失$200。如果事务从未提交,事务的任何更改都不会反映在数据库中。

隔离性

事务的结果通常对其他事务是不可见的,直到事务完成。这确保如果在我们的示例中的第 3 行和第 4 行之间运行银行账户摘要,它仍然会看到支票账户中的$200。当我们在本章后面讨论隔离级别时,你会明白为什么我们说“通常不可见”。

持久性

一旦提交,事务的更改就是永久的。这意味着更改必须被记录,以防止在系统崩溃时丢失数据。然而,持久性是一个稍微模糊的概念,因为实际上有许多级别。一些持久性策略提供比其他更强的安全保证,而且没有什么是 100%持久的(如果数据库本身真的是持久的,那么备份如何增加持久性呢?)。

ACID 事务和 InnoDB 引擎特别提供的保证是 MySQL 中最强大和最成熟的功能之一。虽然它们会带来一定的吞吐量折衷,但当适当应用时,它们可以避免在应用层实现大量复杂逻辑。

隔离级别

隔离性比看起来更复杂。ANSI SQL 标准定义了四个隔离级别。如果您是数据库领域的新手,我们强烈建议您在阅读有关具体 MySQL 实现之前熟悉 ANSI SQL 的一般标准⁶。该标准的目标是定义更改在事务内外何时可见和何时不可见的规则。较低的隔离级别通常允许更高的并发性并具有较低的开销。

注意

每个存储引擎对隔离级别的实现略有不同,并且不一定与您习惯于其他数据库产品时所期望的相匹配(因此,在本节中我们不会详细介绍)。您应该阅读您决定使用的任何存储引擎的手册。

让我们快速看一下四个隔离级别:

READ UNCOMMITTED

READ UNCOMMITTED隔离级别中,事务可以查看未提交事务的结果。在这个级别,除非您真的非常了解自己在做什么并且有充分的理由这样做,否则可能会发生许多问题。这个级别在实践中很少使用,因为其性能并不比其他级别好多少,而其他级别有许多优势。读取未提交的数据也被称为脏读

READ COMMITTED

大多数数据库系统(但不包括 MySQL!)的默认隔离级别是READ COMMITTED。它满足先前使用的隔离的简单定义:事务将继续看到在其开始后提交的事务所做的更改,并且其更改在提交之前对其他人不可见。这个级别仍然允许所谓的不可重复读。这意味着您可以两次运行相同的语句并看到不同的数据。

REPEATABLE READ

REPEATABLE READ解决了READ UNCOMMITTED允许的问题。它保证事务读取的任何行在同一事务内的后续读取中“看起来相同”,但理论上仍允许另一个棘手的问题:幻读。简而言之,当您选择某些行的范围时,另一个事务将新行插入到该范围中,然后再次选择相同范围时,您将看到新的“幻影”行。InnoDB 和 XtraDB 通过多版本并发控制解决了幻读问题,我们稍后在本章中解释。

REPEATABLE READ是 MySQL 的默认事务隔离级别。

SERIALIZABLE

最高级别的隔离是SERIALIZABLE,通过强制事务按顺序排列以避免可能发生冲突来解决幻读问题。简而言之,SERIALIZABLE在读取每一行时都会放置一个锁。在这个级别,可能会发生很多超时和锁争用。我们很少看到人们使用这种隔离级别,但您的应用程序需求可能迫使您接受降低的并发性以换取数据安全性。

表 1-1 总结了各种隔离级别及与每个级别相关的缺点。

表 1-1. ANSI SQL 隔离级别

隔离级别 是否可能出现脏读 是否可能出现不可重复读 是否可能出现幻读 锁定读取
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE

死锁

死锁是指两个或多个事务相互持有并请求相同资源上的锁,从而创建了依赖循环。当事务尝试以不同顺序锁定资源时,就会发生死锁。无论何时多个事务锁定相同资源,都可能发生死锁。例如,考虑这两个针对StockPrice表运行的事务,该表具有主键(stock_id, date)

事务 1

START TRANSACTION;
UPDATE StockPrice SET close = 45.50 WHERE stock_id = 4 and date = ‘2020-05-01’;
UPDATE StockPrice SET close = 19.80 WHERE stock_id = 3 and date = ‘2020-05-02’;
COMMIT;

事务 2

START TRANSACTION;
UPDATE StockPrice SET high = 20.12 WHERE stock_id = 3 and date = ‘2020-05-02’;
UPDATE StockPrice SET high = 47.20 WHERE stock_id = 4 and date = ‘2020-05-01’;
COMMIT;

每个事务将执行其第一个查询并更新一行数据,将该行在主键索引中锁定,并在此过程中锁定其所属的任何其他唯一索引。然后,每个事务将尝试更新其第二行,只能发现它已被锁定。除非有某种干预来打破死锁,否则这两个事务将永远等待对方完成。我们在第七章中进一步介绍索引如何在架构演变过程中影响查询的性能。

为了解决这个问题,数据库系统实现了各种形式的死锁检测和超时。更复杂的系统,如 InnoDB 存储引擎,将注意到循环依赖关系并立即返回错误。这可能是一件好事——否则,死锁将表现为非常慢的查询。其他系统在查询超过锁等待超时后会放弃,这并不总是好事。InnoDB 目前处理死锁的方式是回滚具有最少独占行锁的事务(这是一个近似指标,哪个事务最容易回滚)。

锁行为和顺序是存储引擎特定的,因此一些存储引擎可能会在某些语句序列上发生死锁,即使其他存储引擎不会。死锁具有双重性质:一些是由于真实数据冲突而不可避免的,一些是由存储引擎的工作方式引起的。⁷

一旦发生死锁,就无法在不部分或完全回滚其中一个事务的情况下解除死锁。在事务系统中,死锁是生活中的一个事实,您的应用程序应设计为处理它们。许多应用程序可以简单地从头开始重试它们的事务,除非遇到另一个死锁,否则它们应该成功。

事务日志

事务日志有助于使事务更高效。存储引擎可以在每次更改发生时更新磁盘上的表之前更改其内存中的数据副本。这是非常快的。然后,存储引擎可以将更改记录写入事务日志,该日志位于磁盘上,因此是持久的。这也是一个相对快速的操作,因为追加日志事件涉及磁盘上一个小区域的顺序 I/O,而不是在许多地方进行随机 I/O。然后,在以后的某个时间,一个进程可以更新磁盘上的表。因此,大多数使用这种技术(称为预写式日志记录)的存储引擎最终会将更改写入磁盘两次。

如果在更新写入事务日志后但在更改数据本身之前发生崩溃,则存储引擎仍然可以在重新启动时恢复更改。恢复方法因存储引擎而异。

MySQL 中的事务

存储引擎是驱动数据如何存储和从磁盘检索的软件。虽然 MySQL 传统上提供了许多支持事务的存储引擎,但 InnoDB 现在是金标准和推荐使用的引擎。这里描述的事务基元将基于 InnoDB 引擎中的事务。

理解 AUTOCOMMIT

默认情况下,单个INSERTUPDATEDELETE语句会隐式包装在一个事务中并立即提交。这被称为AUTOCOMMIT模式。通过禁用此模式,您可以在事务中执行一系列语句,并在结束时COMMITROLLBACK

你可以通过使用SET命令为当前连接启用或禁用AUTOCOMMIT变量。值1ON是等效的,0OFF也是如此。当你运行时AUTOCOMMIT=0,你总是处于一个事务中,直到你发出COMMITROLLBACK。然后 MySQL 立即开始一个新的事务。此外,启用AUTOCOMMIT后,你可以使用关键字BEGINSTART TRANSACTION开始一个多语句事务。改变AUTOCOMMIT的值对非事务表没有影响,这些表没有提交或回滚更改的概念。

在打开事务期间发出某些命令会导致 MySQL 在执行之前提交事务。这些通常是进行重大更改的 DDL 命令,如ALTER TABLE,但LOCK TABLES和其他一些语句也有这种效果。查看你版本的文档以获取自动提交事务的完整命令列表。

MySQL 允许你使用SET TRANSACTION ISOLATION LEVEL命令设置隔离级别,该命令在下一个事务开始时生效。你可以在配置文件中为整个服务器设置隔离级别,也可以仅为你的会话设置:

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

最好在服务器级别设置你最常用的隔离级别,并仅在明确的情况下更改它。MySQL 识别所有四个 ANSI 标准隔离级别,而 InnoDB 支持它们全部。

在事务中混合存储引擎

MySQL 不会在服务器级别管理事务。相反,底层存储引擎自己实现事务。这意味着你不能可靠地在单个事务中混合不同的引擎。

如果你在一个事务中混合使用事务表和非事务表(例如,InnoDB 和 MyISAM 表),如果一切顺利,事务将正常工作。然而,如果需要回滚,对非事务表的更改无法撤消。这将使数据库处于一个不一致的状态,可能很难恢复,并使事务的整个目的变得无意义。这就是为什么非常重要为每个表选择正确的存储引擎,并尽量避免在应用逻辑中混合存储引擎。

如果你在非事务表上执行事务操作,MySQL 通常不会警告你或引发错误。有时回滚事务会生成警告:“一些非事务更改的表无法回滚”,但大多数情况下,你不会得到任何指示你正在使用非事务表。

警告

最好的做法是不要在应用程序中混合存储引擎。失败的事务可能导致不一致的结果,因为某些部分可以回滚,而其他部分则无法回滚。

隐式和显式锁定

InnoDB 使用两阶段锁定协议。它可以在事务期间的任何时候获取锁,但直到COMMITROLLBACK才会释放锁。它同时释放所有锁。前面描述的锁定机制都是隐式的。InnoDB 根据你的隔离级别自动处理锁。

然而,InnoDB 也支持显式锁定,SQL 标准根本没有提到:⁸^,⁹

SELECT ... FOR SHARE
SELECT ... FOR UPDATE

MySQL 还支持LOCK TABLESUNLOCK TABLES命令,这些命令在服务器中实现,而不是在存储引擎中。如果你需要事务,请使用事务性存储引擎。LOCK TABLES是不必要的,因为 InnoDB 支持行级锁定。

提示

LOCK TABLES和事务之间的交互是复杂的,在某些服务器版本中存在意外行为。因此,我们建议无论使用哪种存储引擎,都不要在事务中使用LOCK TABLES

多版本并发控制

MySQL 的大多数事务性存储引擎不使用简单的行级锁定机制。相反,它们与一种称为*多版本并发控制(MVCC)*的增加并发性技术结合使用行级锁定。MVCC 并不是 MySQL 独有的:Oracle、PostgreSQL 和一些其他数据库系统也使用它,尽管存在重大差异,因为 MVCC 应如何工作没有标准。

您可以将 MVCC 视为对行级锁定的一种变通方法;在许多情况下,它避免了锁定的需要,并且开销要低得多。根据实现方式,它可以允许非锁定读取,同时仅在写入操作期间锁定必要的行。

MVCC 通过使用数据在某个时间点存在的快照来工作。这意味着事务可以看到数据的一致视图,无论它们运行多长时间。这也意味着不同的事务可以同时在相同的表中看到不同的数据!如果您以前从未经历过这种情况,可能会感到困惑,但随着熟悉度的增加,您会更容易理解。

每个存储引擎都以不同方式实现 MVCC。一些变体包括乐观和悲观并发控制。我们通过解释 InnoDB 的行为来说明 MVCC 的一种工作方式,形式为图 1-2 中的序列图。

InnoDB 通过为每个启动的事务分配事务 ID 来实现 MVCC。该 ID 是在事务第一次读取任何数据时分配的。当在该事务内修改记录时,将向撤销日志写入解释如何撤消该更改的撤销记录,并且事务的回滚指针指向该撤销日志记录。这就是事务可以找到回滚的方法的方式。

图 1-2。处理不同事务中一行的多个版本的序列图

当不同会话读取群集键索引记录时,InnoDB 会比较记录的事务 ID 与该会话的读取视图。如果记录在当前状态下不应可见(更改它的事务尚未提交),则会跟随并应用撤销日志记录,直到会话达到可以可见的事务 ID。这个过程可以一直循环到一个完全删除此行的撤销记录,向读取视图发出此行不存在的信号。

通过在记录的“信息标志”中设置“删除”位来删除事务中的记录。这也在撤销日志中跟踪为“删除标记”。

值得注意的是,所有撤销日志写入也都会被重做记录,因为撤销日志写入是服务器崩溃恢复过程的一部分,并且是事务性的。[¹¹] 这些重做和撤销日志的大小也在高并发事务执行中扮演着重要角色。我们将在第五章中更详细地介绍它们的配置。

所有这些额外的记录保留的结果是,大多数读取查询从不获取锁。它们只是尽可能快地读取数据,确保只选择符合条件的行。缺点是存储引擎必须在每行存储更多数据,在检查行时做更多工作,并处理一些额外的管理操作。

MVCC 仅适用于REPEATABLE READREAD COMMITTED隔离级别。READ UNCOMMITTED不兼容 MVCC,因为查询不会读取适合其事务版本的行版本;无论如何,它们都会读取最新版本。SERIALIZABLE不兼容 MVCC,因为读取会锁定它们返回的每一行。

复制

MySQL 设计用于在任何给定时间接受一个节点上的写入。这在管理一致性方面具有优势,但在需要将数据写入多个服务器或多个位置时会产生折衷。MySQL 提供了一种本地方法来将一个节点接受的写入分发到其他节点。这被称为复制。在 MySQL 中,源节点每个副本都有一个线程作为复制客户端登录,当发生写入时会唤醒,发送新数据。在图 1-3 中,我们展示了这种设置的简单示例,通常称为源和副本设置中的多个 MySQL 服务器的拓扑树

图 1-3. MySQL 服务器复制拓扑的简化视图

对于在生产环境中运行的任何数据,您应该使用复制,并至少有三个以上的副本,最好分布在不同位置(在云托管环境中称为区域)以进行灾难恢复规划。

多年来,MySQL 中的复制变得更加复杂。全局事务标识符、多源复制、副本上的并行复制和半同步复制是一些主要更新。我们在第九章中详细介绍了复制。

数据文件结构

在 8.0 版本中,MySQL 将表元数据重新设计为包含在表的*.ibd文件中的数据字典。这使得关于表结构的信息支持事务和原子数据定义更改。在操作期间检索表定义和元数据不再仅依赖于information_schema,我们引入了字典对象缓存,这是一个基于最近最少使用(LRU)的内存缓存,其中包含分区定义、表定义、存储程序定义、字符集和校对信息。服务器访问表的元数据的这一重大变化减少了 I/O,尤其是如果一部分表是最活跃的并且因此最常见于缓存中的话,这是有效的。.ibd* 和 .frm 文件被替换为每个表的序列化字典信息(.sdi)。

InnoDB 引擎

InnoDB 是 MySQL 的默认事务存储引擎,也是最重要和最广泛使用的引擎。它设计用于处理许多短暂事务,这些事务通常会完成而不是回滚。其性能和自动崩溃恢复使其在非事务性存储需求中也很受欢迎。如果您想研究存储引擎,深入学习 InnoDB 是值得的,以尽可能多地了解它,而不是平等地研究所有存储引擎。

注意

最佳实践是将 InnoDB 存储引擎作为任何应用程序的默认引擎。MySQL 通过几个主要版本之前将 InnoDB 设为默认引擎,使这一点变得容易。

InnoDB 是默认的 MySQL 通用存储引擎。默认情况下,InnoDB 将其数据存储在一系列数据文件中,这些文件统称为表空间。表空间本质上是 InnoDB 自行管理的一个黑盒。

InnoDB 使用 MVCC 实现高并发,并实现了所有四个 SQL 标准隔离级别。它默认使用REPEATABLE READ隔离级别,并具有防止在此隔离级别中出现幻读的 next-key 锁定策略:InnoDB 不仅锁定您在查询中触及的行,还锁定索引结��中的间隙,防止插入幻影。

InnoDB 表是建立在聚簇索引上的,我们将在第八章中详细讨论架构设计时进行介绍。InnoDB 的索引结构与大多数其他 MySQL 存储引擎非常不同。因此,它提供非常快速的主键查找。但是,次要索引(非主键的索引)包含主键列,因此如果您的主键很大,其他索引也会很大。如果您将在表上有许多索引,应该努力使主键尽可能小。

InnoDB 具有各种内部优化。这些包括从磁盘读取数据的预测性读取,自适应哈希索引自动在内存中构建哈希索引以进行非常快速的查找,以及插入缓冲区以加快插入速度。我们将在本书的第四章中介绍这些内容。

InnoDB 的行为非常复杂,如果你正在使用 InnoDB,我们强烈建议阅读 MySQL 手册中的“InnoDB 锁定和事务模型”部分。由于其 MVCC 架构,建议在使用 InnoDB 构建应用程序之前,您应该了解许多微妙之处。

作为事务性存储引擎,InnoDB 通过各种机制支持真正的“热”在线备份,包括 Oracle 的专有 MySQL 企业备份和开源 Percona XtraBackup。我们将在第十章中详细讨论备份和恢复。

从 MySQL 5.6 开始,InnoDB 引入了在线 DDL,在最初的版本中有限的用例在 5.7 和 8.0 版本中得到扩展。原地模式更改允许进行特定表更改而无需完全锁定表,也无需使用外部工具,这极大地提高了 MySQL InnoDB 表的操作性。我们将在第六章中涵盖在线模式更改的选项,包括本机和外部工具。

JSON 文档支持

JSON 类型是作为 5.7 版本的一部分首次引入 InnoDB 的,它具有 JSON 文档的自动验证以及优化的存储,可以快速读取访问,这对于旧式二进制大对象(BLOB)存储工程师过去常常使用的权衡来说是一个重大改进。除了新的数据类型支持外,InnoDB 还引入了支持 JSON 文档的 SQL 函数。MySQL 8.0.7 中的进一步改进增加了在 JSON 数组上定义多值索引的能力。这个功能可以通过将常见访问模式与能够映射 JSON 文档值的函数匹配,进一步加快对 JSON 类型的读取访问查询。我们将在第六章中的“JSON 数据”中讨论 JSON 数据类型的使用和性能影响。

数据字典更改

MySQL 8.0 的另一个重大变化是删除基于文件的表元数据存储,并转而使用 InnoDB 表存储的数据字典。这一变化将 InnoDB 的所有崩溃恢复事务性优势带到了表更改等操作中。这一变化虽然极大地改进了 MySQL 中数据定义的管理,但也需要对 MySQL 服务器的操作进行重大更改。特别值得注意的是,以前依赖表元数据文件的备份过程现在必须查询新数据字典以提取表定义。

原子 DDL

最后,MySQL 8.0 引入了原子数据定义更改。这意味着数据定义语句现在要么完全成功完成,要么完全回滚。这通过创建一个专门用于 DDL 的撤销和重做日志成为可能,InnoDB 依赖于此来跟踪变化——这是 InnoDB 成熟设计被扩展到 MySQL 服务器操作的另一个地方。

摘要

MySQL 有分层架构,顶部是服务器范围的服务和查询执行,底部是存储引擎。尽管有许多不同的插件 API,但存储引擎 API 是最重要的。如果您理解 MySQL 通过在存储引擎 API 上来回传递行来执行查询,那么您已经掌握了服务器架构的基本原理。

在过去几个主要版本中,MySQL 已将 InnoDB 定为其主要开发重点,并在多年后将其内部账务处理、身份验证和授权移至 MyISAM。Oracle 对 InnoDB 引擎的增加投资导致了诸如原子 DDL、更强大的在线 DDL、更好的抗崩溃能力以及更适合安全部署的操作性等重大改进。

InnoDB 是默认存储引擎,几乎可以覆盖所有用例。因此,在谈论功能、性能和限制时,以下章节将重点关注 InnoDB 存储引擎,很少会涉及其他存储引擎。

¹ 唯一的例外是 InnoDB,因为 MySQL 服务器尚未实现外键定义,所以 InnoDB 解析外键定义。

² MySQL 5.5 及更新版本支持一个可以接受线程池插件的 API,尽管并不常用。线程池的常见做法是在访问层完成的,我们在第五章中讨论过。

³ 我们强烈建议阅读关于独占锁与共享锁、意向锁和记录锁的文档

⁴ 在处理表名更改或模式更改时会使用元数据锁,而在 8.0 中我们引入了“应用级锁定功能”。在日常数据更改过程中,内部锁定留给了 InnoDB 引擎。

⁵ 尽管这是一个常见的学术练习,但大多��银行实际上依赖每日对账,而不是在白天依赖严格的事务操作。

⁶ 欲了解更多信息,请阅读 Adrian Coyler 撰写的 ANSI SQL 摘要和 Kyle Kingsbury 撰写的一篇关于一致性模型的解释

⁷ 正如您将在本章后面看到的,一些存储引擎锁定整个表,而其他一些实现更复杂的基于行的锁定。所有这些逻辑在很大程度上存在于存储引擎层。

⁸ 这些锁定提示经常被滥用,通常应该避免使用。

SELECTFOR SHARE 是 MySQL 8.0 的一个新特性,取代了之前版本中的 SELECTLOCK IN SHARE MODE

¹⁰ 我们建议阅读 Jeremy Cole 的这篇博文,以更深入地了解 InnoDB 中的记录结构。

¹¹ 想要了解 InnoDB 如何处理其记录的多个版本,建议阅读 Jeremy Cole 的这篇博文

¹² 没有正式的标准定义 MVCC,因此不同的引擎和数据库实现方式大不相同,没有人能说其中任何一种是错误的。

第二章:在可靠性工程世界中进行监控

监控系统是一个广泛的主题,在过去几年中受到了《站点可靠性工程:谷歌如何运行生产系统》(O’Reilly)及其后续作品*《站点可靠性工作手册:实施 SRE 的实用方法》*(O’Reilly)的重要工作的��响。自这两本书出版以来,站点可靠性工程(SRE)已成为开放职位招聘中的热门趋势。一些公司甚至已经将现有员工的职称更改为某种“可靠性工程”。

站点可靠性工程改变了团队对运营工作的看法。这是因为它包含一组原则,使我们更容易回答诸如以下问题:

  • 我们是否提供了可接受的客户体验?
  • 我们应该专注于可靠性和弹性工作吗?
  • 我们如何在新功能和琐事之间取得平衡?

本章希望读者了解这些原则是什么。如果您没有阅读上述任何一本书,我们建议从*《站点可靠性工作手册》*中的这些章节作为速成课程:

  • 第一章提供了更深入理解如何朝着在生产中实现服务水平性能管理的哲学的方向发展。
  • 第二章涵盖了如何实施服务水平目标(SLO)。
  • 第五章涵盖了对 SLO 的警报。

有人可能会认为 SRE 实施并不严格属于高性能 MySQL 的一部分,但我们不同意。在她的书*《加速》*中,尼古拉·福斯格伦博士说:“我们的衡量应该关注结果,而不是产出。”有效 MySQL 管理的一个关键方面是对数据库健康状况进行良好的监控。传统监控是一条相对铺好的道路。由于 SRE 是一个新领域,如何实施 SRE 原则来应对 MySQL 还不太清楚。随着 SRE 原则的不断获得认可,DBA 的传统角色将发生变化,包括 DBA 如何考虑监控他们的系统。

可靠性工程对 DBA 团队的影响

多年来,监控数据库性能依赖于对单个服务器性能的深入研究。这仍然具有很大的价值,但更多地倾向于是关于反应性测量,比如对性能不佳的服务器进行分析。在门户守卫 DBA 团队的时代,这是标准操作程序,当时其他人不被允许知道数据库的运行方式。

进入谷歌对可靠性工程的介绍。DBA 的角色变得更加复杂,演变成了更多的站点可靠性工程师(SRE)或数据库可靠性工程师(DBRE)。团队必须优化他们的时间。服务水平帮助您定义客户何时感到不满意,并允许您更好地平衡您的时间,解决性能问题和扩展挑战,以及处理内部工具的工作。让我们讨论您需要监视 MySQL 的不同方式,以确保成功的客户体验。

定义服务水平目标

在进入如何衡量客户对数据库集群性能是否满意之前,我们必须首先了解我们的目标是什么,并就描述这些目标的共同语言达成一致。以下是一些问题,可以作为组织中的对话开端,以定义这些目标:

  • 什么是适合衡量成功的指标?
  • 这些指标的哪些值对客户和我们的业务需求是可接受的?
  • 在何时我们被认为处于降级状态?
  • 何时我们完全处于失败状态并需要尽快进行补救?

有些问题有明显的答案(例如,源数据库宕机,我们不接受任何写入,因此业务停滞)。有些问题则不那么明显,比如定期任务有时会占用所有数据库磁盘 I/O,突然其他所有操作变慢。在整个组织中对我们正在衡量的内容和原因有共享理解,可以帮助指导优先级对话。通过组织内持续对话达成共识,有助于指导您是否可以将工程工作投入新功能,或者是否需要更多投入于性能改进或稳定性。

在 SRE 实践中,关于客户满意度的讨论将使团队对于服务水平指标(SLIs)、SLOs 和服务水平协议(SLAs)在业务方面的健康状况达成一致。让我们首先定义这些术语的含义:

服务水平指标(SLI)

用非常简单的术语来说,SLI 回答了这个问题,“我如何衡量我的客户是否满意?”答案代表了用户角度的健康系统。SLIs 可以是业务级别的指标,比如“面向客户的 API 的响应时间”,或者更基本的“服务是否正常”。您可能会发现,根据数据的上下文以及与产品的关系,您需要不同的指标或度量标准。

服务水平目标(SLO)

SLO 回答了这个问题,“为了确保我的客户满意,我可以允许我的 SLI 的最低值是多少?” SLO 是我们希望在给定 SLI 下达到的目标范围,以被视为健康服务。如果您认为正常运行时间是 SLI,那么您希望在给定时间段内正常运行的次数就是 SLO。必须将 SLO 定义为在给定时间范围内的值,以确保每个人对 SLO 的含义达成一致。 SLI 加上 SLO 形成了了解客户是否满意的基本方程。

服务水平协议(SLA)

SLA 提供了这个问题的答案,“我愿意同意什么样的 SLO 并承担后果?” SLA 是一个 SLO,已包含在与业务的一个或多个客户(付费客户,而不是内部利益相关者)的协议中,如果未达到该 SLA 则会有财务或其他惩罚。重要的是要注意,SLA 是可选的。

我们在本章中不会过多涉及 SLAs,因为它们往往需要更多的业务讨论而不是工程讨论。这种决定主要取决于业务期望如果在合同中承诺 SLA 会得到什么销售额,以及如果 SLA 被违反是否值得冒险损失收入。希望这样的决定是基于我们在这里涵盖的关于选择 SLIs 和匹配 SLOs 的内容。

定义这些 SLI、SLO 和 SLA 不仅指导业务的健康状况,还指导工程团队内的规划。如果一个团队没有达到其约定的 SLO,那么就不应继续进行新功能的工作。对于数据库工程团队也是如此。如果我们在本章讨论的潜在 SLO 之一没有达到,那就应该引发为什么没有达到的讨论。当您拥有数据来解释为什么客户体验不佳时,您可以就团队优先事项进行更有意义的对话。

使客户满意需要什么?

在选择一组指标作为您的 SLIs 后,可能会有诱惑将目标设定为 100%。然而,您必须抵制这种冲动。请记住,选择指标和目标的目的是随时通过客观指标评估您的团队是否可以通过新功能进行创新,或者稳定性是否有可能降至客户可接受水平以下,因此需要更多关注和资源。目标是定义使客户满意的绝对最低要求。如果客户对您的页面在两秒内加载感到满意,那么没有必要设定页面在 750 毫秒内加载的目标。这可能会给工程团队带来不合理的负担。

以正常运行时间作为指标和目标值的例子,我们可以宣称“我们不会有任何停机时间”,但在实施和跟踪是否达到目标时,这意味着什么?达到三个九的可用性并不是一件小事。一整年的三个九仅相当于八个多小时,换算成每周仅为 10 分钟。你承诺的九越多,这就越困难,团队将不得不花费更多昂贵的工程时间来实现这样的承诺。表 2-1 是亚马逊网络服务展示挑战的有用数据表。

表 2-1. 各种可用时间

可用性 每年停机时间 每月停机时间 每周停机时间 每日停机时间
99.999% 5 分钟,15.36 秒 26.28 秒 6.06 秒 0.14 秒
99.995% 26 分钟,16.8 秒 2 分钟,11.4 秒 30.3 秒 4.32 秒
99.990% 52 分钟,33.6 秒 4 分钟,22.8 秒 1 分钟,0.66 秒 8.64 秒
99.950% 4 小时,22 分钟,48 秒 31 分钟,54 秒 5 分钟,3 秒 43 秒
99.900% 8 小时,45 分钟,36 秒 43 分钟,53 秒 10 分钟,6 秒 1 分钟,26 秒
99.500% 43 小时,48 分钟,36 秒 3 小时,39 分钟 50 小时,32 分钟,17 秒 7 分钟,12 秒
99.250% 65 小时,42 分钟 5 小时,34 分钟,30 秒 1 小时,15 分钟,48 秒 10 分钟,48 秒
99.000% 3 天,15 小时,54 分钟 7 小时,18 分钟 1 小时,41 分钟,5 秒 14 分钟,24 秒

因为工程时间是有限资源,选择服务水平目标时不要追求完美。产品中并非所有功能都需要这些九来满足客户,因此随着产品功能集的增长,你会发现根据特定功能影响或其带来的收入,你将有不同的服务水平指标和目标。这是可以预期的,也是一个深思熟虑过程的标志。你在这里有一个关键任务:检测数据集何时成为不同利益相关者的瓶颈,危及性能。这也意味着找到一种方法来区分这些不同利益相关者的需求,以便为他们提供合理的服务水平指标和目标。

这些指标和目标也��产品和工程之间具有统一语言的有效方式,指导在“将工程时间花在新功能上”与“将时间花在弹性和解决问题上”之间做出决策。这也是一种决定,从我们想要实现的事情清单中,基于客户体验来确定哪个最重要的方式。你可以使用服务水平指标和目标来指导工作优先级的对话,否则很难达成一致。

应该衡量什么

假设有一家公司,其产品是一个在线商店。由于增加了在线购物,公司看到了更多的流量,基础设施团队需要确保数据库层能够处理增加的需求。在本节中,我们将讨论作为虚构基础设施团队时应该如何衡量的内容。

定义服务水平指标和目标

定义一个良好的服务水平指标和相匹配的服务水平目标的核心在于简洁地解释如何为客户提供愉快的用户体验。我们不会花费大量时间在抽象层面上解释如何创建有意义的服务水平指标和目标。² 在 MySQL 的背景下,它需要是一个定义了三个主要主题的表示:可用性、延迟和关键错误缺失。

对于我们的在线商店示例,这意味着页面加载速度要快,至少在一个月内 99.5% 的时间内快于几百毫秒。这还意味着一个可靠的结账流程,在给定日历月内只允许 1% 的时间发生间歇性故障。请注意这些指标和目标的定义。我们没有将 100% 定义为要求,因为我们生活在一个失败不可避免的世界中。我们使用时间跨度,以便团队可以准确平衡其在新功能和弹性之间的工作。

“我期望我的数据库请求中有 99.5% 在两毫秒内无错误地提供服务”既是一个具有明确 SLO 的充分 SLI,又不简单。您无法通过一个指标来确认所有这些。这是对数据库层行为的单句表述,以提供可接受的客户体验。

那么在我们的在线商店中,可以构建这种客户体验画面的度量标准是什么?从在生产环境中对页面加载进行采样负载率的合成测试开始。这对于作为一个一致的信号表明“一切正常”是有用的。但这只是一个开始。让我们讨论跟踪不同信号的各个方面以构建画面。随着我们通过这些示例,我们将把它与我们的在线商店联系起来,帮助您可视化这些不同的度量标准如何创建一个良好的客户体验画面。首先,让我们谈谈跟踪查询响应时间。

监控解决方案

在 SLIs 和 SLOs 的背景下进行查询分析和监控查询延迟需要关注客户体验。这意味着依赖可以在查询响应时间超过约定阈值时尽快向您发出警报的工具。让我们讨论一下您可以采取的几种路径来实现这种监控水平。

商业选项

这是一个例子,支付一个竞争优势在于 MySQL 性能分析的供应商可以让您的组织获得丰厚回报。像SolarWinds 数据库性能管理这样的工具可以大大简化查询性能分析的自动化,并让您的工程团队中的大部分人都能够访问。

开源选项

一个成熟的开源选项是Percona 监控与管理,简称 PMM。它作为一个客户端/服务器对运行。您在数据库实例上安装客户端,它收集并发送指标到服务器部分。服务器端还有一组仪表板,允许您查看与性能相关的图表。PMM 的一个主要优点是,仪表板的组织受到 Percona 社区长期监控 MySQL 性能经验的指导。这��其成为一个极好的资源,让新手工程师熟悉如何监控 MySQL 性能。

您可以采取的另一种方法是将数据库慢日志和 MySQL 性能模式输出发送到一个集中位置,您可以使用像pt-query-digest这样的知名工具,它是 Percona Toolkit 包的一部分,来分析日志并更深入地了解数据库实例花费时间的情况。虽然有效,但这个过程可能会很慢,并且如果使用不当可能会影响客户。理想情况下,您希望在客户注意到问题之前发现问题。在发生问题后被动地检查日志,您会面临因为发现性能退化需要花费很长时间以及挖掘各种事后证据的风险,从而磨损客户信任。

最后,使用性能模式来分析 MySQL 性能可能非常有帮助,正如您将在第三章中看到的那样。您可以使用它来找出瓶颈,使您的实例在相同规格下做更多事情,节省基础设施成本,或回答“为什么这个操作花费这么长时间?”这不是一个确定您是否符合服务可靠性承诺的工具,因为它深入到 MySQL 的内部。对于服务水平性能评估,我们需要一种新的关于性能的思考方式。

现在让我们深入了解一些额外的指标,帮助您进一步了解您在线商店的客户体验。您应该考虑从 MySQL 中获取的指标,而不是输出。我们还将涵盖一些单凭 MySQL 指标无法衡量的事例。

监控可用性

一个间歇性下线的在线商店会冒着侵蚀购物者信心的风险。这就是为什么可用性作为一个独立的指标,以及作为您对客户体验的看法的一部分,是如此重要。

可用性是能够在没有错误的情况下响应客户请求。用标准的 HTTP 术语来表述,这可能是一个明确成功的响应,比如 200 响应代码,或者是成功接受请求并承诺异步完成相关工作的响应,比如 202 accepted。在单体主机系统时代,可用性曾经是一个简单的指标。如今,大多数架构要复杂得多。可用性的概念也演变成对分布式系统故障的更微妙的反映。在尝试将可用性转化为数据库架构的 SLI 和 SLO 时,考虑进一步讨论更多细节(以及我们在线商店的示例),例如以下内容:

  • 在处理不可避免的灾难性故障时,哪些功能是不可妥协的,哪些功能是“nice to have”(例如,客户是否可以继续使用现有的购物车并结账,但在此故障期间可能无法添加新商品)?
  • 我们将哪些类型的故障定义为“灾难性”(例如,列表搜索失败可能不是灾难性,但结账操作失败就是)?
  • “降级功能”是什么样子的(例如,我们是否可以在需要时加载通用推荐而不是基于过去购买历史的定制推荐)?
  • 在一组可能的故障场景中,我们可以为核心功能承诺的最短平均恢复时间(MTTR)是多少(例如,如果支持购物车结账系统的数据库写入失败,我们可以安全地多快地切换到新的源节点)?

在选择一组代表可用性的指标时,您希望与客户支持团队设定期望,“100%的正常运行时间”是不合理的,重点是在一个理解和接受组件故障不可避免的世界中提供尽可能最佳的客户体验。

验证可用性的首选方法是来自客户端或远程端点。如果您可以访问客户端的数据库访问日志,这可以被动地完成。明确地说,这意味着如果您的应用程序是 PHP 并且在 Apache 下运行,您需要访问 Apache 日志以确定 PHP 是否发出任何连接到数据库的错误。您也可以主动验证可用性。如果您的环境被隔离并且无法访问客户端日志,考虑设置远程代码来执行对数据库的操作以确保其可用性。这可以是一些简单的操作,比如一个SELECT 1查询,用于验证 MySQL 是否接收并解析您的查询,但不访问存储层。或者这可以更复杂,比如从表中读取实际数据或执行写入和随后读取以验证写入是否成功。来自网络中其他位置的这种合成事务可以让您了解您的应用程序是否可用。

远程验证可用性对于跟踪可用性目标非常有用。它无法帮助您在问题出现之前获得洞察。用作可用性问题的领先指标的一个 MySQL 指标是Threads_running。它跟踪当前在给定数据库主机上正在运行的查询数量。当运行的线程数量快速增长且没有显示任何下降迹象时,这表明查询完成速度不够快,因此正在堆积并消耗资源。允许这个指标增长通常会导致数据库主机在源节点上��起完全的 CPU 锁定或强烈的内存负载,这可能导致操作系统关闭整个 MySQL 进程。如果这种情况发生在源节点上,显然会导致重大故障,因此您应该努力获得领先指标。监视的起点是检查您有多少 CPU 核心,如果Threads_running超过了这个值,这可能表明您的服务器正处于危险状态。除此之外,您还可以监视接近max_connections时的情况,这是另一个检查工作进度过载的数据点。

“安全设置”部分在第五章中提供了关于如何设置 MySQL 线程的制动器的见解。

高性能 MySQL 第四版(GPT 重译)(一)(2)https://developer.aliyun.com/article/1484257

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
5天前
|
前端开发 JavaScript 安全
JavaScript 权威指南第七版(GPT 重译)(七)(4)
JavaScript 权威指南第七版(GPT 重译)(七)
29 0
|
5天前
|
前端开发 JavaScript 算法
JavaScript 权威指南第七版(GPT 重译)(七)(3)
JavaScript 权威指南第七版(GPT 重译)(七)
39 0
|
5天前
|
前端开发 JavaScript Unix
JavaScript 权威指南第七版(GPT 重译)(七)(2)
JavaScript 权威指南第七版(GPT 重译)(七)
43 0
|
5天前
|
前端开发 JavaScript 算法
JavaScript 权威指南第七版(GPT 重译)(七)(1)
JavaScript 权威指南第七版(GPT 重译)(七)
69 0
|
5天前
|
存储 前端开发 JavaScript
JavaScript 权威指南第七版(GPT 重译)(六)(4)
JavaScript 权威指南第七版(GPT 重译)(六)
126 3
JavaScript 权威指南第七版(GPT 重译)(六)(4)
|
5天前
|
前端开发 JavaScript API
JavaScript 权威指南第七版(GPT 重译)(六)(3)
JavaScript 权威指南第七版(GPT 重译)(六)
68 4
|
5天前
|
XML 前端开发 JavaScript
JavaScript 权威指南第七版(GPT 重译)(六)(2)
JavaScript 权威指南第七版(GPT 重译)(六)
69 4
JavaScript 权威指南第七版(GPT 重译)(六)(2)
|
5天前
|
前端开发 JavaScript 安全
JavaScript 权威指南第七版(GPT 重译)(六)(1)
JavaScript 权威指南第七版(GPT 重译)(六)
34 3
JavaScript 权威指南第七版(GPT 重译)(六)(1)
|
5天前
|
存储 前端开发 JavaScript
JavaScript 权威指南第七版(GPT 重译)(五)(4)
JavaScript 权威指南第七版(GPT 重译)(五)
43 9
|
5天前
|
前端开发 JavaScript 程序员
JavaScript 权威指南第七版(GPT 重译)(五)(3)
JavaScript 权威指南第七版(GPT 重译)(五)
40 8