gh-ost使用问题记录

因为 pt-osc 对数据库性能影响较大,且容易造成死锁问题,目前我们在线更改表结构都使用 gh-ost 工具进行修改,这里记录一下使用 gh-ost 过程中的问题,以作记录;首先先复习一下gh-ost的基本实现,gh-ost的基本实现原理如下图所示:

gh-ost使用问题记录

根据源码,核心步骤如下:

  1. initiateStreaming: 初始化 binlog events streaming
  2. initiateApplier: 初始化 applier
  3. addDMLEventsListener: 添加对指定表的binlog event过滤
  4. ReadMigrationRangeValues: 获取对应表唯一索引的 min & max 值
  5. executeWriteFuncs: 通过applier向ghost表写入数据, binlog event 相比 copy rows具有更高优先级
  6. iterateChunks: 根据 min & max的值, 批量插入数据到 ghost 表
  7. cutOver: rename & drop新旧表

问题一:gh-ost导致最新一次写操作丢失

原因分析:

在 initiateStreaming 的过程中通过 show master status 获取主节点当前的 binlog name & pos & Executed_Gtid_Set,然后通过 binlog name & pos 和当前的数据库节点建立复制通道,而后在 ReadMigrationRangeValues 的过程中通过 select min(unique_key) 和 select max(unique_key) 快照读的方式获取原表数据的范围。

问题就出在这里,根据事务的提交流程,如果sync_binlog != 1,那么 binlog name & pos 是在binlog flush阶段之后进行更新;如果 sync_binlog = 1,那么 binlog name & pos 是在 binlog sync 阶段之后进行更新,这时事务还没有在 Innodb 中完成 commit。因此,最新的一次事务对于 select min() & max() 这样的快照读是不可见的,最终造成了写操作的丢失。

如何修复:

这里有两种解决办法:1. 虽然 binlog name & pos 的信息是在 Innodb memory commit 之前进行更新,但是show master status 的 Executed_Gtid_Set 是在 Innodb memory commit 完成之后进行更新的,因此 gh-ost 可以使用 Executed_Gtid_Set 来与数据库节点建立复制通道来解决这个问题。

  1. 在 ReadMigrationRangeValues 的过程中使用 select min() & max() lock in share mode 当前读来解决这个问题;

问题二:添加唯一索引时有可能导致数据丢失:

在使用 gh-ost 做 “add column field1 int not null, add unique index uniq_idx_field1(field1)” 或 “add column field1 int not null default 0, add unique index uniq_idx_field1(field1)” 这样的操作时,会导致整张表只剩下一条数据;

在执行 “add unique index uniq_idx_field1(field1)” 这样的操作时,如果表中的 field1字段存在重复数据,会导致第 2~n 条重复数据丢失。

相比之下,pt-osc 工具提供的 –check-unique-key-change 参数可以在出现以上情况时进行 warning 退出,有补救的可能。

问题三:高并发写入时gh-ost无法结束:

gh-ost使用问题记录

如截图所示,Applied一直在增大,而 Copy保持不变。这是因为在通过 Applier 向 ghost 表中写数据时,binlog events apply 相比rows copy 具有更高的优先级;同时,由于 gh-ost 是通过监听 mysql binlog的方式获取增量写操作,对源MySQL节点的侵入较小;因此,在MySQL实例高并发写入时,gh-ost会忙于 apply binlog events而无法结束。

扩展一:gh-ost的cutover过程:

gh-ost使用问题记录

如上图所示,根据gh-ost -cut-over参数的不同会选择不同的 cur-over 算法,默认是github的 atomic算法,也可以选择 facebook的 OSC算法。

atomic算法// atomicCutOver

gh-ost使用问题记录

这里为什么需要 magic_old表呢?

是为了防止lockSessionId被意外关闭后,可以阻塞rename操作。lockSessionId被意外关闭后,original table可以被写入,会造成数据不一致。至于为何要对 magic_old表加锁,我个人认为是防止magic_old表被意外删除。

源码实现如下:

go;gutter:true; func (this *Migrator) atomicCutOver() (err error) {      // 设置 cutover 标记, 限流函数会使用此标记 atomic.StoreInt64(&this.migrationContext.InCutOverCriticalSectionFlag, 1) defer atomic.StoreInt64(&this.migrationContext.InCutOverCriticalSectionFlag, 0)      // 通知释放锁的通道 okToUnlockTable := make(chan bool, 4)      // 最后删除 magic old table defer func() { okToUnlockTable RENAME released -> queries on original are unblocked.</p> <pre><code> // 阻塞等待锁被释放 if err := </code></pre> <pre><code> facebook OSC算法// cutOverTwoStep cutOverTwoStep很巧妙地利用MySQL不同 session 下 alter table x rename to x1; 和 rename table x1 to x; 不同的锁机制进行 cutover,值得深入研究。 ![gh-ost使用问题记录](https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230605/727246-20220330114826899-1784650786.png) 源码如下: ;gutter:true;
/*
* cutOverTwoStep() 将阻塞原始表,等待原始表上的binlog events全部应用到 ghost 表,然后进行非原子的表rename操作,original->old, then new->original;
* 在rename过程中,原始表不存在,查询操作将失败。
*/
func (this *Migrator) cutOverTwoStep() (err error) {
atomic.StoreInt64(&this.migrationContext.InCutOverCriticalSectionFlag, 1)
defer atomic.StoreInt64(&this.migrationContext.InCutOverCriticalSectionFlag, 0)
atomic.StoreInt64(&this.migrationContext.AllEventsUpToLockProcessedInjectedFlag, 0)
     // 首先通过 lock tables write; 对原始表加锁
if err := this.retryOperation(this.applier.LockOriginalTable); err != nil {
return err
}
// 等待原始表上的 binlog events 全部应用到 ghost 表
if err := this.retryOperation(this.waitForEventsUpToLock); err != nil {
return err
}
     // 1. 这里使用和 LockOriginalTable 操作相同的session来执行 alter original_table rename magic_old_table; 来重
// 命名原表,该操作不会被 LockOriginalTable 操作加的锁阻塞,相同的 session 执行 rename table original to magic 将阻塞。
// 2. 将 ghost 表重命名为 original 表; 这里采用在另一个 session 上执行 rename table ghost to original;来进行,因此在
// 原 session 上执行将被锁阻塞。
if err := this.retryOperation(this.applier.SwapTablesQuickAndBumpy); err != nil {
return err
}
// 对原表解锁
if err := this.retryOperation(this.applier.UnlockTables); err != nil {
return err
}

lockAndRenameDuration := this.migrationContext.RenameTablesEndTime.Sub(this.migrationContext.LockTablesStartTime)
renameDuration := this.migrationContext.RenameTablesEndTime.Sub(this.migrationContext.RenameTablesStartTime)
log.Debugf("Lock & rename duration: %s (rename only: %s). During this time, queries on %s were locked or failing", lockAndRenameDuration, renameDuration, sql.EscapeName(this.migrationContext.OriginalTableName))
return nil
}

Original: https://www.cnblogs.com/juanmaofeifei/p/16070998.html
Author: 卷毛狒狒
Title: gh-ost使用问题记录

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/590943/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 类加载器ClassLoader

    1.双亲委派模型 java是根据双亲委派模型的加载类的,当一个类加载器加载类时,会先尝试委托给父类加载器去加载,直到到达启动类加载器顶层若加载不了,则再让子类加载器去加载直到类成功…

    数据库 2023年6月16日
    099
  • SQL语言基础

    SQL语言基础 SQL (Structured Query Language:结构化查询语言) 是用于管理关系数据库管理系统(RDBMS)。 SQL 的范围包括数据插入、查询、更新…

    数据库 2023年5月24日
    068
  • 23种设计模式之访问者模式(Visitor Pattern)

    文章目录 概述 访问者模式的优缺点 访问者模式的使用场景 访问者模式的结构和实现 * 模式结构 模式实现 总结 概述 访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可…

    数据库 2023年6月6日
    081
  • springboot~手动加载thymeleaf模版

    thymeleaf在spring-mvc时代很是盛行,与freemaker组成了两大模版引擎,而进行springboot之后,很多项目都采用前后分离的模式,这使得模板引擎关注度少了…

    数据库 2023年6月6日
    0105
  • Spark学习(1) Spark入门

    什么事spark Spark是一种快速、通用、可扩展的大数据计算引擎.项目是用Scala进行编写,基于内存计算的 包括交互式查询和流处理 spark内置项目 Spark SQL:是…

    数据库 2023年6月16日
    086
  • 我是个怎样的人

    我是一个怎样的人 我是一个怎样的人, 我是一个虚伪的人. 我麻木的观察着这个世界, 对好坏, 真假, 我都去同样看待, 不去区分. 我是一个怎样的人, 我是一个善良的人. 我温柔的…

    数据库 2023年6月9日
    072
  • Java学习-第一部分-第二阶段-第三节:异常

    异常 笔记目录:(https://www.cnblogs.com/wenjie2000/p/16378441.html) 运行下面的代码,看看有什么问题->引出异常和异常处理…

    数据库 2023年6月11日
    0109
  • 得体的注释,让我总能想起TA

    作为一个技术TL,在排查生产问题时,我经常要周转于各个工程里。系统和服务多起来后,要我了解每一段代码具体的来龙去脉逐渐几乎不可能了。 例如,今天,我要查一下调用某个三方接口所配置的…

    数据库 2023年6月9日
    0160
  • 部署zabbix监控服务

    部署zabbix监控服务 部署zabbix监控服务 什么是zabbix zabbix的特点 zabbix的配置文件 部署zabbix zabbix服务端安装 准备工作 数据库操作 …

    数据库 2023年6月14日
    086
  • IDEA中如何查看接口的所有实现类呢?

    接口是我们日常开发中常用的操作,那么如何查看一个接口有哪些实现类呢?下文笔者将讲述IDEA编辑器中 查看实现类的快捷方法,如下所示 在spring源码阅读中,每一个接口都有很多实现…

    数据库 2023年6月11日
    070
  • MySQL45讲之优化器选错索引

    前言 本文简要介绍了优化器选择索引的依据,以及如何人为地引导优化器选择较好的执行方案。 [En] This paper briefly introduces the basis f…

    数据库 2023年5月24日
    0107
  • 如何干涉MySQL优化器使用hash join

    GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源。 GreatSQL是MySQL的国产分支版本,使用上与MySQL一致。 前言 实验 总结 前言 数据库的…

    数据库 2023年6月11日
    082
  • MYSQL(进阶篇)——一篇文章带你深入掌握MYSQL

    MYSQL(进阶篇)——一篇文章带你深入掌握MYSQL 我们在上篇文章中已经学习了MYSQL的基本语法和概念 在这篇文章中我们将讲解底层结构和一些新的语法帮助你更好的运用MYSQL…

    数据库 2023年6月14日
    091
  • MySQL45讲之幻读

    前言 本文介绍了什么是虚读,虚读存在的问题和解决方法,以及间隙锁带来的麻烦。 [En] This paper introduces what is phantom reading,…

    数据库 2023年5月24日
    076
  • MySQL数据库 DDL 阻塞问题定位 【转载】

    陈臣,甲骨文MySQL首席解决方案工程师,公众号《MySQL实战》作者,有大规模的MySQL,Redis,MongoDB,ES的管理和维护经验,擅长MySQL数据库的性能优化及日常…

    数据库 2023年5月24日
    093
  • IDEA中Git的使用

    Git在IDEA中的使用 JAVA技术交流群:737698533 创建和导入 创建一个新项目到Gitee上 首先创建一个仓库,勾选上初始化 获取新创建仓库的路径 然后随便在一个文件…

    数据库 2023年6月16日
    073
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球