| 导语Gh-ost改表工具是MySQL主流的2种开源改表工具之一,因为可限速,入侵小而在业界广泛使用,然而Gh-ost存在1个P0级的未修复BUG,可能导致数据丢失,本文对这个问题进行了分析,并给出了解决方案。
1. MySQL改表结构,目前主要有3种方式:onlineDDL、pt-osc、gh-ost
2. Online DDL问题:
1)改大表会复制延迟,业务读取不到最新数据
2 无法限速也可能导致主机负载过高,业务读写产生抖动
3. pt-osc改表问题:
1) 不可暂停
2) 交换表名的时候会有短时间的表不存在报错
3) 要在表上建触发器,是侵入式的,可能产生未知问题
所以改表一般选择Gh-ost工具。
Gh-ost是一个由原GitHub 工程师开发的 MySQL 在线表结构更改工具,它的名字是 "GitHub's Online Schema Transmogrifier" 的缩写。这个工具是为了解决在 GitHub 上线时,对 MySQL 数据库进行表结构更改时的一些问题而开发的。
"ghost" 这个单词在英语中的意思是 "鬼魂",这也暗示了这个工具的工作方式:它在更改表结构时,会创建一个新的 "影子" 表,然后将原表的数据复制到新表中,最后在适当的时候切换到新表,从而实现无缝的、在线的表结构更改。这种方式可以避免直接在原表上进行更改时可能产生的长时间锁表等问题。
如上图所示,具体的改表原理为:
使用gh-ost给表tb加索引:
ALTER TABLE tb ADD INDEX `idx_1` ();
执行完成后,对账发现tb表有一条记录缺失,解析binlog,发现该记录有写入记录,但主备机上查询均找不到这条记录!
表功能说明:
tb:原始表
_tb_del:临时表(空表),交换表名用
_tb_gho:影子表(替代tb)
原理:
session1, session3是Gh-ost线程,session2是业务进程。
在交换表名这一步,因为session1已经锁定了tb,_tb_del,session2写入数据和session3请求元数据锁都被阻塞。删除_tb_del和解锁tb后,获取元数据锁成功,此时RENAME操作优先级高于DML,所以是先执行rename,再执行业务的insert,这样的情况下数据没有问题。
下面我们看下异常流程:
如上图所示,在删除_tb_del后,按顺序,应该拿到_tb_gho的元数据锁,然后等待获取tb的元数据锁。如果这个时候_tb_gho表恰好有查询,则获取_tb_gho的元数据锁被阻塞,此时解锁tb,业务的insert请求会先成功,此时再获得_tb_gho的元数据锁成功,完成交换表名操作。业务写入的4这条记录实际上在交换表名后的_tb_del表,最后步骤删除_tb_del表,这条记录也就被删除了!
至于_tb_gho为什么会有访问,分析业务是不可能访问这个表,因为他们不知道这个表的存在。但数据库管理系统和MySQL会不会某种情况下扫描这个表,分析是有可能的,比如库表信息采集系统。
那么针对这种情况,有什么解决方案呢?
原理: Gh-ost实际还支持另外一种换表方式2阶段换表:如上图所示,先将原始表重命名为临时表,再将影子表重命名为原始表
具体是使用cut-over参数:
--cut-over: 这个参数用来指定换表名的方式,可以是atomic或者two-step。atomic方式会在一个原子操作中完成换表名,而two-step方式会先将原始表重命名为一个临时的名字,然后再将"影子"表重命名为原始表的名字。
优点: 数据一致性强 缺点:
原理:
缺点:
关键代码如下:
// ExpectMetadataLock expects a PENDING metadata lock on OriginalTable to show up in `performance_schema.metadata_locks` that has given characteristics
func (this *Applier) ExpectMetadataLock(sessionId int64) error {
found := false
query := `
select /*+ gh-ost */ m.owner_thread_id
from performance_schema.metadata_locks m join performance_schema.threads t
on m.owner_thread_id=t.thread_id
where m.object_type = 'TABLE' and m.object_schema = ? and m.object_name = ?
and m.lock_type = 'EXCLUSIVE' and m.lock_status = 'PENDING'
and t.processlist_id = ?
`
err := sqlutils.QueryRowsMap(this.db, query, func(m sqlutils.RowMap) error {
found = true
return nil
}, this.migrationContext.DatabaseName, this.migrationContext.OriginalTableName, sessionId)
if err != nil {
return err
}
if !found {
return fmt.Errorf("cannot find PENDING metadata lock on original table: `%s`.`%s`", this.migrationContext.DatabaseName, this.migrationContext.OriginalTableName)
}
return nil
}
原理:
加入数据核对:增量核对改表前5分钟到改表完成时间段_tb_del表中的主键,必须在tb表中全部存在
核平后再清理_tb_del表
缺点:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。