Skip to content

背景

当我们想在线修改表结构时,我们通常不希望因为一个表结构修改语句而导致整个表被全程锁定,从而无法进行业务操作。在这种情况下,我们就需要一个表结构在线更改工具。

完成此任务有两种主要机制:

  1. 使用触发器:在每次进行插入、修改、删除操作时,使用一个触发器将这些变更应用到新表上,然后再进行切换。

  2. 伪装成从库:这是 gh-ost 采用的方式。它伪装成一个 MySQL 从库,获取 binlog 并将其应用到自己的表中。

gh-ost 的优点:

  • 异步:这可以提高操作的整体效率,同时让原本的操作对业务更加无感。由于事务的特性,可以保证主库上的操作会同步到 gh-ost 的表中,从而提高数据一致性。
  • 低侵入性:gh-ost 的工作原理是伪装成从库来获取 binlog,因此对原有的业务没有任何侵入性,只要不影响主从复制即可。同时,这种伪装模式使得 gh-ost 可以根据系统的实时负载动态调整复制速率,从而对业务造成最小的影响。
  • 原子切换:与使用触发器的方式相比,gh-ost 的切换是原子性的。它只会在需要切换时短暂地阻塞整个表,将阻塞时间降到最低。

gh-ost 的缺点:

  • 对 binlog 有特定的格式要求,必须是 ROW 格式的 binlog 才能进行复制。
  • 对复制环境有特定的要求。

gh-ost 的工作流程

1. 初始化

gh-ost 解析 ALTER TABLE 语句,并根据它创建新的表结构,我们称之为 ghost 表

2. 数据复制

gh-ost 伪装成从库,通过 iterateChunks 的方式从主库获取数据块,将原表数据复制到 ghost 表中。与此同时,它实时地将 binlog 应用到 ghost 表上。

也就是说,在切换前,gh-ost 会持续追踪 binlog 的变化,将增量数据复制到 ghost 表中。在这个过程中,它会持续追踪当前的 binlog 位置。简单来说,gh-ost 并行执行以下两步:

  • 块复制:复制原有数据到 ghost 表。
  • 增量复制:跟踪 binlog 增量复制数据。

这两者并行执行,其中 binlog 的优先级更高。由于 binlog 是严格按照时间顺序进行的,因此可以避免幂等性风险。

3. 原子切换 (Cutover)

当数据复制完成后,即 ghost 表的数据与原表完全一致后,gh-ost 会进行原子切换。这也是 gh-ost 的核心精髓。

假设我们原来的表名为 user,ghost 表将命名为 user_gho,同时会有一个 changelog 表记录心跳等具体的 MySQL 状态。

在这一步,gh-ost 会首先获取一个 ghost 级别的锁,以避免多个 gh-ost 实例同时对同一张表和 ghost 表进行并发操作。

在一个会话 (session 1) 中,gh-ost 执行以下操作:

  1. 上一个锁,防止其他操作。
  2. 创建一个空的魔法表 users_del
  3. usersusers_del 表上锁,禁止对它们进行写入操作。
  4. 等待 binlog 追齐。
  5. 在另一个新的会话 (session 2) 中执行 RENAME 语句。由于之前上的锁,RENAME 语句会被阻塞并挂起。

RENAME 语句出现在 processlist 中,并且 session 1 持有的锁依然存在时,gh-ost 会发送解锁信号。

为什么需要等待 RENAME 出现在 processlist 中? 如果我们不等 RENAME 出现就直接释放锁,那么任何其他操作都可能抢先执行,导致 RENAME 语句出现问题。我们需要确保解锁后 RENAME 能立刻执行。

为什么要强调会话持有的锁还在? 假设 session 1 持有锁,但由于某种原因挂掉了。尽管它持有的锁会阻塞其他所有操作,但如果我们此时解锁,其他操作就可以继续,而 RENAME 所在的 session 1 已经挂掉,RENAME 语句将永远不会执行。因此,我们必须确保 RENAME 的会话还活着,并且持有锁。

当确认这两点后,usersusers_del 的挂起操作被唤醒,users_del 这个表会被 DROP 掉,然后解锁所有一开始锁住的表。

此时,数据库中只有 usersusers_ghousers_ghc

由于锁被释放,RENAME 的协程被触发,user_gho 被重命名为 users,而原来的 users 被重命名为 users_del

现在我们有了新表 users、旧表 users_delusers_ghc。最后,users_delusers_ghc 被删除,最终只剩下变更过的新表 users。至此,表结构变更完成,新表占据了原表的位置。

注意事项:

gh-ost 最好在业务低峰期进行,否则可能会对业务产生影响。DROP TABLE 操作也可能导致 I/O 抖动。此外,如果写入速度过快,binlog 可能会一直无法追平。