Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > MySQL gh-ost DDL 变更

MySQL gh-ost DDL 变更工具的实现

作者:Bing@DBA

本文主要介绍了MySQL gh-ost DDL变更工具的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

1. MDL 锁介绍

MySQL 的锁可以分为四类:MDL 锁、表锁、行锁、GAP 锁,其中除了 MDL 锁是在 Server 层加的之外,其它三种都是在 InnoDB 层加的。

下面主要介绍一下:MDL 元数据锁,主要作用就是维护 DDL 过程中数据的安全性 & 正确性。

当对一个表进行 DML 时,需要加 MDL 读锁,当需要对一张表结构进行变更时,需要加 MDL 写锁。读锁之间不互斥,即可以多个线程对一张表进行并发增删改。读写锁与写锁,之间是互斥的,用来保证变更表结构操作的安全性。因此,如果有两个线程要同时给一个表加字段,其中一个要等另一个执行完才能开始执行。

读操作与写操作,都需要加 MDL 读锁,而 DDL 需要加 MDL 写锁,两者互斥的,那么 Online DDL 如何保障并发 DML 呢,这里就要介绍 Online DDL 加锁过程:

其中第二阶段占用了 DDL 整个过程的大量时间,在这段时间 DDL 才是真正的 online。

那么问题来了,如果有一个大查询持有 MDL 读锁,那么我们此时进行一个 DDL 操作后,因为查询持有读锁,DDL 需要加写锁,所以变更必须等待查询结束才能进行,此时再进行一个查询就会被卡住,请看案例👇

Session 1Session 2Session 3Session 4
begin;select * from sbtest1 limit 5;
select * from sbtest1 limit 5;
alter table sbtest1 drop column fantasy ,ALGORITHM=INPLACE, LOCK=NONE; ❌
select * from sbtest1 limit 5; ❌

Session 1 开启一个事物,执行一条查询,此时 Session 1 持有 MDL 读锁;
Session 2 也执行一条查询,执行一条查询,正常执行;
Session 3 执行一条 DDL 语句,因为需要 MDL 写锁,被 Session 1 读锁;
Session 4 执行一条查询,因为需要读锁,但是因为 Session 3 也处于等待状态,后面的增删改查都处于等待状态。也就是说这张表完全不可用了。

即使变更支持 online 也非常怕长事务,所以生产环境大表变更前,我们可以去查会话和 innodb_trx 表,确认没有长事务,避免变更被 MDL 读锁堵塞。

看了上面的案例,我们理想状态下 DDL 变更是什么样的呢?

2. 变更工具

MySQL 变更比较常用的工具有大名鼎鼎的 PT 生态 **percona pt-online-schema-change **和 Facebook OSC 但是它们都是基于触发器来实现的,简单来讲就是通过数据库的触发器把作用在源表的操作在一个事务内同步到修改后的表中,这在业务高峰期时会极大的加重主库的负载。

gh-ost 是由 Github 开发的一款开源 **online DDL **工具,使用订阅和过滤 binlog 的方式代替原来的触发器来做的增量数据同步,这样可以降低主库负载,异步的执行。

使用 gh-ost 的优点

限制:

3. gh-ost 原理解析

官方图解 (https://github.com/github/gh-ost)

在这里插入图片描述

主要执行过程:

cut-over 即表 rename 阶段,gh-ost 利用了 MySQL 的一个特性,原子性的 rename 请求,在所有被 blocked 的请求中,rename 优先级永远是最高的。gh-ost 基于此设计了该方案:一个连接对原表加锁,另启一个连接尝试 rename 操作,此时会被阻塞住,当释放 lock 的时候,rename 会首先被执行,其他被阻塞的请求会继续应用到新表。

4. 安装部署

下载地址 gh-ost GA 版本存档 二进制包,开箱即用。

请添加图片描述

5. 操作演示

5.1. 重点参数介绍

下面介绍 gh-ost 中重点参数:

上面就是常用的参数,用户可以控制切换时间,锁超时时间,以及迁移阶段对源库的负载。除此之外 gh-ost 还有三种操作模式:

请添加图片描述

大部分变更我们使用 connect to master 模式,其它两种模式感兴趣详细可以参数官方文档,在此不详细介绍:https://github.com/github/gh-ost/blob/master/doc/cheatsheet.md

5.2. 执行变更

/myinstall/gh-ost \
--max-load=Threads_running=30 \
--critical-load=Threads_running=50 \
--critical-load-interval-millis=5000 \
--chunk-size=1000 \
--dml-batch-size=10 \
--user="cooh" \
--password="112233" \
--host='127.0.0.1' \
--port=3306 \
--database="sbtest" \
--table="sbtest1" \
--alter="engine=innodb" \
--verbose \
--assume-rbr \
--cut-over=default \
--cut-over-lock-timeout-seconds=1 \
--allow-on-master \
--concurrent-rowcount \
--default-retries=30 \
--heartbeat-interval-millis=2000 \
--panic-flag-file=/myinstall/ddl_room/ghost.panic.flag \
--postpone-cut-over-flag-file=/myinstall/ddl_room/ghost.postpone.flag \
--serve-socket-file=/myinstall/ddl_room/ghost.sock \
--throttle-additional-flag-file=/myinstall/ddl_room/ghost.pause.flag \
--timestamp-old-table \
--execute 2>&1 | tee  /myinstall/ddl_room/ddl_sbtest1.log &
# 参数解释和设置技巧
# 这两个参数可以先使用 show status like 'Threads_running'; 
# 了解平时数据库线程数,再进行设置。
--max-load=Threads_running=30
--critical-load=Threads_running=50
# 下面两个参数影响着迁移阶段的速度,如果开启后观察数据库压力较大,可以动态调整
--chunk-size=1000
--dml-batch-size=10
# 各种操作在 panick 前重试次数,默认 60 次
--default-retries
# 切换完成后,给源表添加时间戳,如果不要求切换完立即删除源表,建议使用该参数
--timestamp-old-table
# 切换完后,立即删除源表,如果表比较大会影响 IO 可根据实际情况设定
--ok-to-drop-table
# 设置暂停、退出 flag 的文件位置,建议为 gh-ost 创建一个单独文件夹存放 flag 文件
--panic-flag-file
--throttle-additional-flag-file
--serve-socket-file
# 如果想自定义切换时间,可以使用该参数,常被嵌入自动化 DDL 平台
--postpone-cut-over-flag-file
# 步骤失败后,重试次数,例如切换获取锁,尝试多少次,如果一直没有则终止任务
--default-retries

执行 DDL:操作过程中会自动创建两个中间状态表 _gho 幽灵表也就是目标表,_ghc 是记录 gh-ost 执行状态的表:

cooh@mysql 16:36:  [sbtest]>show tables;
+------------------+
| Tables_in_sbtest |
+------------------+
| _sbtest1_ghc     |
| _sbtest1_gho     |
| sbtest1          |
+------------------+
3 rows in set (0.00 sec)

可以通过查询 _ghc 表来查询变更进度和状态:

cooh@mysql 16:40:  [sbtest]>select * from _sbtest1_ghc order by id desc limit 1\G
*************************** 1. row ***************************
         id: 344
last_update: 2022-04-06 16:40:12
       hint: copy iteration 100 at 1649234412
      value: Copy: 99999/99999 100.0%; Applied: 0; Backlog: 0/1000; Time: 4m0s(total), 2s(copy); streamer: mysql-bin.000005:344430440; Lag: 0.99s, HeartbeatLag: 1.00s, State: postponing cut-over; ETA: due
1 row in set (0.00 sec)


Copy: 99999/99999 100.0%; 需要迁移 99999 行,目前已迁移 99999 行,进度 100.0%
Applied: 0,指在二进制日志中处理的 event 数量。在上面的例子中,迁移表没有流量,因此没有被处理日志 event。
Backlog: 0/1000,表示我们在读取二进制日志方面表现良好,在二进制日志队列中没有任何积压(Backlog)事件。
Backlog: 7/1000,当复制行时,在二进制日志中积压了一些事件,并且需要应用。
Backlog: 1000/1000,表示我们的 1000 个事件的缓冲区已满(程序写死的 1000 个事件缓冲区,低版本是 100 个)此时就注意 binlog 写入量非常大,gh-ost 处理不过来 event 了,可能需要暂停 binlog 读取,需要优先应用缓冲区的事件。
State: 目前 gh-ost 的状态
streamer: mysql-bin.000005:344430440; 表示当前已经应用到 binlog 文件位置

此时状态为:postponing cut-over 即等待切换,因为我们使用
--postpone-cut-over-flag-file=/myinstall/ddl_room/ghost.postpone.flag 参数,由用户来控制切换时间,如果我们不删除 ghost.postpone.flag 就会一直处理等待状态,我们删除后:

Copy: 99999/99999 100.0%; Applied: 0; Backlog: 0/1000; Time: 8m38s(total), 2s(copy); streamer: mysql-bin.000005:344547927; Lag: 0.99s, HeartbeatLag: 0.04s, State: migrating; ETA: due
2022-04-06 16:44:50 INFO Setting RENAME timeout as 1 seconds
2022-04-06 16:44:50 INFO Session renaming tables is 200
2022-04-06 16:44:50 INFO Issuing and expecting this to block: rename /* gh-ost */ table `sbtest`.`sbtest1` to `sbtest`.`_sbtest1_20220406163612_del`, `sbtest`.`_sbtest1_gho` to `sbtest`.`sbtest1`
2022-04-06 16:44:50 INFO Found atomic RENAME to be blocking, as expected. Double checking the lock is still in place (though I don't strictly have to)
2022-04-06 16:44:50 INFO Checking session lock: gh-ost.194.lock
2022-04-06 16:44:50 INFO Connection holding lock on original table still exists
2022-04-06 16:44:50 INFO Will now proceed to drop magic table and unlock tables
2022-04-06 16:44:50 INFO Dropping magic cut-over table
2022-04-06 16:44:50 INFO Releasing lock from `sbtest`.`sbtest1`, `sbtest`.`_sbtest1_20220406163612_del`
2022-04-06 16:44:50 INFO Tables unlocked
2022-04-06 16:44:50 INFO Tables renamed
2022-04-06 16:44:50 INFO Lock & rename duration: 1.006946435s. During this time, queries on `sbtest1` were blocked
[2022/04/06 16:44:50] [info] binlogsyncer.go:164 syncer is closing...
2022-04-06 16:44:51 INFO Closed streamer connection. err=<nil>
2022-04-06 16:44:51 INFO Dropping table `sbtest`.`_sbtest1_ghc`
[2022/04/06 16:44:51] [error] binlogsyncer.go:631 connection was bad
[2022/04/06 16:44:51] [error] binlogstreamer.go:77 close sync with err: Sync was closed
[2022/04/06 16:44:51] [info] binlogsyncer.go:179 syncer is closed
2022-04-06 16:44:51 INFO Table dropped
2022-04-06 16:44:51 INFO Am not dropping old table because I want this operation to be as live as possible. If you insist I should do it, please add `--ok-to-drop-table` next time. But I prefer you do not. To drop the old table, issue:
2022-04-06 16:44:51 INFO -- drop table `sbtest`.`_sbtest1_20220406163612_del`
2022-04-06 16:44:51 INFO Done migrating `sbtest`.`sbtest1`
2022-04-06 16:44:51 INFO Removing socket file: /myinstall/ddl_room/ghost.sock
2022-04-06 16:44:51 INFO Tearing down inspector
2022-04-06 16:44:51 INFO Tearing down applier
2022-04-06 16:44:51 INFO Tearing down streamer
2022-04-06 16:44:51 INFO Tearing down throttler
# Done

表示 gh-ost 已执行完成,ghc 表已清理,源表名改为:_sbtest1_20220406163612_del

cooh@mysql 16:46:  [sbtest]>show tables;
+-----------------------------+
| Tables_in_sbtest            |
+-----------------------------+
| _sbtest1_20220406163612_del |
| sbtest1                     |
+-----------------------------+
2 rows in set (0.00 sec)

5.3. 动态控制

用户可以通过操作特定的文件对正在执行的 gh-ost 进行动态控制,请看下面案例:

6. 风险提示

到此这篇关于MySQL gh-ost DDL 变更工具的实现的文章就介绍到这了,更多相关MySQL gh-ost DDL 变更内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

您可能感兴趣的文章:
阅读全文