ITPub博客

首页 > 数据库 > 数据库开发技术 > MySQL 在线模式迁移现状 — PlanetScale

MySQL 在线模式迁移现状 — PlanetScale

数据库开发技术 作者:ITPUB社群 时间:1970-01-01 08:33:44 0 删除 编辑

如何在 MySQL 中运行非阻塞的 schema 变更?这是一个永恒的问题。面对众多的第三方解决方案以及 MySQL 近期的进步,很难判断对于给定的 schema 迁移哪种方案更优。在本文中,我们提供 2024 年 MySQL 在线 schema 迁移现状的高层次概述。我们将讨论范围限定在 ALTER TABLE 语句,因为其他 DDL 语句通常很快(DROP TABLE 算是一个例外,但不在本文讨论范围内)。

我们首先考察 MySQL 的原生选项:INPLACE 和 INSTANT。有关参考,请参阅 Online DDL Operations MySQL 8.0 文档。

INPLACE,即 InnoDB Online DDL

这是 MySQL 对非阻塞 schema 变更的首次尝试。某些类型的 ALTER TABLE(支持的变更完整列表见上方链接)可以使用 ALGORITHM=INPLACE 运行。INPLACE schema 变更在技术上是非阻塞的,但有不少注意事项:

  • 在提交查询的服务器上,通常是主数据库服务器,DML 查询(SELECT、INSERT、UPDATE、DELETE 等)是非阻塞的,可以继续执行。其他 DDL 语句将会阻塞,这是预期行为。

  • 该操作非常消耗资源:MySQL 服务器会尽可能多地使用 CPU 和磁盘 I/O 来完成变更。这会对繁忙服务器上的性能造成影响。

  • 它需要额外的磁盘空间,最多可达原表所占用的磁盘空间。

  • 它是不可中断的。唯一的中止方式是强制 kill 查询。这随后会导致进一步的大规模清理操作,消耗更多磁盘 I/O。

  • 在副本服务器上,该操作不是非阻塞的。这意味着如果 ALTER TABLE 在主服务器上耗时 3 小时,那么从它完成的那一刻起,你可以预期复制会停滞,在接下来的约 3 小时内应用同样的变更,造成长达 3 小时的巨大延迟。

复制问题对大多数人来说是致命的。一种绕过方法是使用 SQL_LOG_BIN=0 在主服务器上运行 ALTER TABLE,使其不被复制。然后,在每个副本上分别以类似方式运行。这种技术可行,但可能导致不一致。你追踪了所有服务器吗?如果你随后从备份中恢复(或引导)一台服务器,而该变更从未在那里执行过,会怎样?你如何追踪这一点?此外,这种技术完成所需的时间会长 n 倍,因为你需要为每台服务器单独运行变更。你可以并行化部分工作,但可能无法全部并行化。

INPLACE 结论

基于这些原因,我们认为 INPLACE 不是非阻塞变更的好选择。

INSTANT schema 变更

即时 schema 变更几乎是数据库世界中的圣杯,在它有效的地方,它是仅次于披萨的最好的东西(前提是没有 bug)。MySQL 支持某些 schema 变更使用 ALGORITHM=INSTANT 运行。INSTANT DDL 最初由腾讯在六年前贡献给 MySQL,当时仅支持一种变更类型:ADD COLUMN。后来 MySQL 增加了对更多变更的支持,例如扩展 enum 列,或添加和删除 VIRTUAL 列。最近(一年前),MySQL 8.0.29 增加了对任意 ADD COLUMN 和 DROP COLUMN 的支持。

INSTANT 真正做到了即时执行。它不需要复制表,不需要额外磁盘空间,也不会给 CPU 带来压力。没有什么需要中断,因为操作在你眨眼之前就已经结束了。它在副本上也能即时执行。

听起来很完美!在支持的情况下,它基本上确实如此,但也有一些细微之处。再仔细看看支持操作的文档。仔细观察,你可以看到这些类型的受支持变更:

  • 更改列的默认值。

  • 添加/删除 VIRTUAL 列。

  • 修改 enum 定义。

  • 以及更多。

这些变更的共同点是,它们都是元数据变更。它们不影响现有行,不修改数据,不重构表,也不影响索引。ADD COLUMN 和 DROP COLUMN 是仅有的、实际会影响表数据或数据组织方式的受支持变更。另一个注意事项是,如果某一列参与了索引,你就不能使用 INSTANT DDL 来 DROP COLUMN。

所以你可以把列的默认值从 0 改为 1,但不能把可空列改为非空列。你可以添加和删除 GENERATED VIRTUAL 列,但不能添加和删除 GENERATED STORED 列。你可以修改 enum 定义,但不能把列的类型从 int 改为 bigint。

不支持的变更列表包括:

  • 更改列的数据类型。

  • 添加具有非字面量默认值的列。

  • 添加索引。

  • 修改 PRIMARY KEY 定义。

  • 添加/删除外键。

  • 更改表的字符集。

  • 进行分区变更。

也就是说,INSTANT DDL 距离满足 schema 变更的常见需求还有很长的路要走。在可能的情况下,INSTANT DDL 非常出色,并且在许多情况下都是更可取且推荐的方式。

INSTANT 的风险

INSTANT 乍看之下似乎没有风险。在大多数情况下,确实如此!即使你犯了错误,也可以通过反向的 INSTANT 操作来纠正。对大多数变更来说都是如此,除非数据被销毁。目前,INSTANT DDL 支持的唯一破坏性语句是 DROP COLUMN(针对“真实的”、非 VIRTUAL 列)。

删除列有两个主要风险:

  • 如果过早或意外执行,显然会有丢失重要数据的风险。

  • 破坏现有查询的风险。

丢失数据显然可能是一起重大事故,会导致服务中断以及长时间甚至数天的恢复工作。但为什么这尤其会构成即时风险呢?无论是否即时,造成的损害不都一样吗?

答案在于人类总是倾向于尽可能选择即时操作的行为模式。你距离摧毁自己的数据只有一步之遥,既没有屏障来阻止你,也没有机制(除了备份和延迟副本之外)能让你回到安全状态。我们将在介绍可回退性(Revertibility,特别是在 Vitess 中)概念时简要讨论这一点。

那破坏现有查询呢?也许数据确实可以丢弃,或者已经安全地聚合到其他地方,但可能仍有一批 SELECT 或 INSERT 查询在引用该列?

MySQL 提供了不可见列(invisible columns)来模拟表中没有某列时的样子。然而,它是有局限的。它只影响那些没有显式使用列名的查询,例如 SELECT * FROM my_table ... 或 INSERT INTO my_table VALUE (...) 。但任何 SELECT the_column FROM my_table 查询仍然可以完全访问列。在当今世界,SELECT * 和盲插 INSERT 查询已经不那么常见了。框架、工具和现代工程范式都倾向于显式和完全限定。不可见列在这里帮不上忙。

如果删除该列确实导致查询中断,那么你需要要么修复所有查询,要么尝试重新引入该列。现在让我们讨论可回退性。

可回退性

撤销变更有哪些选择?如何切换回之前的 schema?让我们用两个简单的例子来说明。

假设你的变更是 ALTER TABLE my_table ADD COLUMN name ... 。这看起来无害,却可能导致停机。name 可能是一个常见的列名。在多表语句中选择 name 的查询,例如 SELECT name, value FROM my_table JOIN another_table USING ... ,可能因为 name 列的新歧义而失败。

ADD COLUMN 的反向变更(anti-change)是 DROP COLUMN ,由于两者都支持 INSTANT DDL,你很可能能够快速且相对安全地恢复。

如果你的变更是 DROP COLUMN 呢?撇开数据丢失不谈,你会应用什么反向变更来恢复之前的 schema?不仅数据丢失了,元数据也丢失了。列类型是什么?长度是多少?是否可为空?除非你有之前的 schema,否则这些信息无法推断。很有可能你使用版本控制来管理 schema,因此能够提取之前的定义。不过值得指出的是,为 schema 迁移精心设计反向变更并非易事。

INSTANT 的结论

在可能的情况下,INSTANT 通常是进行在线 schema 变更的最佳方法。然而,它目前过于局限,不支持大多数常见的 schema 变更。在数据被破坏时,它不提供可回退性。MySQL 团队没有发布关于未来 MySQL 版本中 INSTANT DDL 支持的具体计划。

MySQL 外部的解决方案

目前有许多第三方工具可用于为 MySQL 执行在线 schema 变更。我们将重点关注 Vitess,它是 PlanetScale 非阻塞 schema 变更背后的技术。其他第三方工具包括 gh-ost、pt-online-schema-change、新近出现的 spirit 等。

这些工具都共享类似的基本设计,但运行方式不同。它们共同的主要特征包括:

  • 它们通过使用新 schema 创建影子表并缓慢复制数据来模拟 ALTER TABLE。

  • 与原生 MySQL ALTER TABLE 相比,它们可能且通常会花费更长时间才能完成。

  • 它们需要额外的磁盘空间,大约与现有表一样多(如果考虑碎片则更少,如果添加臃肿的索引等则更多)。

  • 它们会导致二进制日志膨胀(本质上整个表内容都会经过二进制日志)。

  • 它们尊重生产工作负载,并会根据需要暂停或限流,以便为生产流量让路(因此它们可能会运行更长时间)。

  • 它们以小批量变更的方式运行,因此能够将复制延迟保持在最低水平(并基于延迟进行限流)。

  • 它们是可中断的:操作可以中止且不会产生直接成本(清理可以在后续阶段完成)。

  • 它们能够处理几乎所有类型的 schema 变更。大多数存在外键限制。某些分区选项不建议使用,或者使用这些工具运行是完全错误的。

先说明一点:如果你的表有一个 color enum('red','green','blue') 列,而你想添加一个新的枚举值,使其变为 color enum('red','green','blue','orange'),你最好使用 INSTANT DDL。如上所述,有少数此类情况受 INSTANT DDL 支持,并且在这些情况下花费数小时进行迁移根本没有意义。

然而,对于(仍然占绝大多数的)变更,这些仍然是首选解决方案。首先,当然,我们已经确定 INSTANT 和 INPLACE 都没有覆盖所有类型的变更(它们只覆盖了可能变更中的少数)。但这也导致了一种新兴行为:在流程/自动化中维护两种不同的技术会带来更多复杂性。如果你已经不得不使用某个第三方解决方案,那么不妨一直使用它。

vitess 和 spirit 都更进一步,可以自动检测何时可以使用 INSTANT DDL 完成迁移,这意味着你不需要考虑它,也不需要知道哪个特定版本支持哪些变更。

vitess 进一步将可回滚性作为一等公民支持,不仅能够回滚到原始 schema,还能保留原本会丢失的数据,同时仍然考虑自变更以来任何新增、更新或删除的数据。

关于分区的说明

分区是一个奇怪的野兽,在 MySQL 中通过为每个分区创建一个“小”表来实现。因此,对分区的操作实际上是对表集合的操作。某些与分区相关的变更只应由 MySQL 处理。例如,对于 RANGE 分区表,DROP PARTITION 语句就是如此。其他一些分区变更更适合由 MySQL 处理,还有一些则最适合由在线 schema 变更工具处理。

第三方结论

大多数用例最适合(或只能很好地)由第三方在线 schema 变更工具处理,而在可预见的未来,这些工具仍然是首选方案。


转载声明:本文为转载整理,原文版权归原作者及原站点所有。

原文来源:PlanetScale Blog

原创地址:https://planetscale.com/blog/state-of-online-schema-migrations-in-mysql

原文作者:PlanetScale Blog;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2022-11-21

  • 博文量
    1090
  • 访问量
    546807