起因

半夜线上MySQL表结构变更失败
image.png
image.png
_ghc表是什么?gh-ost方案的原理和详细步骤是什么?_ghc表在gh-ost方案中的环节和作用是什么?

gh-ost方案

GitHub’s online schema migration for MySQL,github开源的一种MySQL在线变更方案,特点是对主库负载压力低,且使用起来很灵活。

基于触发器trigger的Online DDL工具

介绍 gh-ost 之前,先来简单了解一下在gh-ost 之前基于触发器的方案原理。
Online DDL 大致分为5步:

  1. 建立新表:根据原来的表结构和要执行 alter 语句,新建一个更新表结构之后的table_new。
  2. 复制数据:把原来表的已有数据 copy 到table_new。
  3. 增量同步:在 copy 的过程中,生产业务会有新的数据过来,这些数据要同步到table_new,这一步是 “Online” 的关键。
  4. 替换表名:copy 和同步完成后,锁住源表,交换表名,table_new替换源表。
  5. 删除老表:删除源表(可选),完成 online DDL。

这其中比较重要的第三步,如何同步增量的数据。最开始办法就是使用触发器,在主库源表上增加几个触发器,例如当源表执行 INSERT,UPDATE,DELETE 语句,就把这些操作通过触发器同步到table_new上,在table_new上执行的语句和源表的语句就属于同一个事务,显然这样会额外增加主库的负载,影响主库的性能。
后面出现了异步trigger的模式,使用触发器把对源表的操作保存到一个 Changelog 表中,不真正的去执行,专门有一个后台的线程从 Changelog 表读取数据应用到新表上。这种方式一定程度上缓解了主库的压力,但是保存到 Changelog 表也同样是属于同一个事务中,对性能也有不小的影响。

gh-ost 的文档 中列举了触发器方案的不足之处,大致有以下几点:

  • Triggers, overhead: 触发器是用存储过程的实现的,就无法避免存储过程本身需要的开销。
  • Triggers, locks: 增大了同一个事务的执行步骤,更多的锁争抢。
  • Trigger based migration, no pause: 整个过程无法暂停,假如发现影响主库性能,停止 Online DDL,那么下次就需要从头来过。
  • Triggers, multiple migrations: 考虑到对主库的负载压力,在同一个主库上,同时对多个表进行并行的操作,显然是风险很大的。
  • Trigger based migration, no reliable production test: 无法在生产环境做测试。
  • Trigger based migration, bound to server: 触发器操作还是在MySQL服务内部,不能解耦。

    gh-ost方案原理

    从上面的描述可以看出,触发器的作用是源表和新表之间的增量数据同步,gh-ost 放弃了触发器,使用 binlog 来同步。gh-ost 模拟成为一个从库,可以从主库/备库上拉取 binlog,处理过滤之后重新应用到主库上去,相当于主库上的增量操作通过 binlog 又应用回主库本身,不过是应用在新表上。引用一下官网的图:
    image.png
    gh-ost默认方案:

  • 连接从库

  • 校验完后,在主库创建新表table_new以及变更记录表table_ghc(校验binlog row格式、权限、外键、trigger等)
  • 迁移原表数据到新表(主库内部执行)
  • 模拟从库的从库,拉取解析过滤出增量binlog应用到主库
  • cut-over阶段,锁住主库的源表,用新表替换掉原表,并清理table_ghc

gh-ost 这种方案带来诸多好处,例如:

  • 整个流程异步执行,对于源表的增量数据操作没有额外的开销,高峰期变更业务对性能影响小。
  • 降低写压力,触发器操作都在一个事务内,gh-ost 应用 binlog 是另外一个连接在做。
  • 可停止,binlog 有位点记录,如果变更过程发现主库性能受影响,可以立刻停止拉binlog,停止应用 binlog,稳定之后继续应用。
  • 可测试,gh-ost 提供了测试功能,可以连接到一个备库上直接做 Online DDL,在备库上观察变更结果是否正确,再对主库操作,心里更有底。
  • 并行操作,对于 gh-ost 来说就是多个对主库的连接。

    几种连接模式如下图所示:

    image.png
    关于数据迁移环节

    迁移过程中,row copy和binlog apply是同时进行,其中原则是binlog apply的优先级一定大于row copy操作的优先级。

    row copy部分

    | 原表 | 新表 | | —- | —- | | select | insert ignore into |

binlog apply部分

原表 新表
insert replace into
update update 新表(全列更新)
delete delete

关于cut-over环节

方案利用了MySQL的一个特性,就是原子性的rename请求,在所有被blocked的请求中,优先级永远是最高的

时序 会话A 会话B 其他会话
1 create table tbl_old;
阻止rename过早执行


2 lock table tbl write, tbl_old write;

3

对tbl的业务读写请求,被阻塞
4
rename table tbl to tbl_old, tbl_new to tbl;
原子操作,被阻塞

5

对tbl的业务读写请求,被阻塞
6 drop table tbl_old;

7 unlock tables;
释放当前会话的所有表锁。


8 rename 完成
9 业务读写请求继续

无论会话ABC在哪个环节出现失败,都可以保证业务不受影响。

最后

image.png

课后题

为什么DBA会要求尽量在业务低峰期进行数据库变更(gh-ost)?