数据库
首页 > 数据库> > MySQL ONLINE DDL 工具之gh-ost

MySQL ONLINE DDL 工具之gh-ost

作者:互联网

gh-ost也是一种在线的解决DDL的方案,不依赖于触发器,它是通过模拟从库,在row binlog中获取增量变更,再异步应用到ghost表中。目前gh-ost已经收获了将近一万的star,并且在持续更新中。

2.1 主要工作流程

gh-ost工作流程如下:

  1. 创建影子表在影子表上执行变更这两步和pt-osc基本相同,只是创建的影子表名称不一样,这里是_t1_gho;

  2. 创建 binlog streamer :这一步的作用是为了同步t1表上的增量DML到 _t1_gho上。gh-ost会伪装成一个从库节点,读取数据库(可能是集群中的主节点或者从节点)的binlog,然后解析binlog,获取到对于t1的相应操作,然后转化成对 _t1_gho 表的操作;

  3. 同步数据:这一步也是循环同步数据,每次循环也会监控数据库的负载等,只是在确定需要同步数据范围的上下边界和ppt-osc有所不同;

    1. 首先确定全部需要同步数据范围的上限边界;

      select /* gh-ost `test`.`t1` */ `id` from `test`.`t1` order by  `id` asc limit 1 
      // 确定全部需要同步数据范围的下边界MIN(id)
      select /* gh-ost `test`.`t1` */ `id` from `test`.`t1` order by  `id` desc limit 1
      // 确定全部需要同步数据范围的上边界MAX(id)
      
    2. 确认本次循环同步数据范围的上边界

      select  /* gh-ost `test`.`t1` */  `id`
      from `test`.`t1`
      where ((`id` > _binary'8991')) and ((`id` < _binary'10000') or ((`id` = _binary'10000')))
      order by  `id` asc
      limit 1
      offset 998
      // 其中 8991是上次循环的上边界值
      // 10000 是需要同步数据范围的上边界MAX(id)
      // offset 998 这个值和--chunk-size的设置有关,--chunk-size值减 1
      
    3. 同步数据

      insert /* gh-ost `test`.`t1` */ ignore into `test`.`_t1_gho` (`id`, `c1`, `c2`, `c3`)
      (select `id`, `c1`, `c2`, `c3` from `test`.`t1` 
       force index (`PRIMARY`)
       where (((`id` > _binary'8991'))
       and ((`id` < _binary'9990') or ((`id` = _binary'9990')))) 
       lock in share mode
      // 其中 8991 是上次循环的上边界值
      // 其中 9990 是步骤b中获取到的数据
      
  4. 增量应用binlog

    这里说明一下,同步数据和增量应用binlog是同时进行的,没有确定的时间先后顺序,只要在binlog里发现有相应变更就会在影子表上重放。

  5. 同步数据和应用增量binlog

    • 若循环执行3-b时,若获取到数据则正常进入步骤3-c;

    • 若循环执行3-b时未获取到数据,则说明剩余未同步的数据小于--chunk-size的值,执行以下SQL确认本次的上边界值

      select /* gh-ost `test`.`t1` */ `id`
      from (
          select `id`  from   `test`.`t1`
          where ((`id` > _binary'9990')) 
          and ((`id` < _binary'10000') 
          or ((`id` = _binary'10000')))
          order by   `id` asc
          limit 999 ) select_osc_chunk
      order by  `id` desc
      limit 1
      

      若上面SQL获取到值,则循环执行步骤3-c同步数据;若未获取到值则数据已经完全同步。

  6. 更改表名:在更改表名之前会先对表加写锁,这点需要注意。

    原始表和影子表 cut-over 切换是原子性切换,但是基本都是通过两个会话的操作来完成。大致流程如下:

    • 会话 Cn1(这里代表一个或者多个会话): 对t1表正常执行DML操作。
    • 会话 gh1 : 创建_t1_del 防止提前RENAME表,导致数据丢失。
    • 会话 gh1 : 执行LOCK TABLES t1 WRITE,_t1_del WRITE。
    • 会话 Cn2 : LOCK TABLES之后进来的会话操作会被阻塞。
    • 会话 gh2 : 设置锁等待时间并执行RENAME
    set session lock_wait_timeout:=1
    rename /* gh-ost */ table `test`.`t1` to `test`.`_t1_del`, `test`.`_t1_gho` to `test`.`t1`;
    	// gh2 的操作因为 gh1 锁表而等待,后续有其他新发起的对表t1上的操作也会阻塞
    
    • 会话 gh1 会通过SQL 检查是否已经有会话在执行RENAME操作并且在等待MDL锁,此时会检测到gh2。

    • 会话gh1 : 基于上面执行的结果,执行DROP TABLE _t1_del。

    • 会话gh1 : 执行UNLOCK TABLES; 此时gh2的rename命令第一个被执行。而其他会话如Cn2的请求之后开始执行。

    这里涉及到的原理是基于 MySQL 内部机制:被 LOCK TABLE 阻塞之后,执行 RENAME 的优先级高于 DML,也即先执行 RENAME TABLE ,然后执行 DML,即使DML发起的时间早于RENAME的时间。

    另外要先在表上加写锁的根本原因其实还是为了保证数据的一致性。gh-ost在同步原表上的变更操作是使用的拼接binlog的形式,和原表上发生的操作不属于同一个事务。所以在收尾 cut-over 时要先对表加上写锁,阻塞原表上的变更,待增量的binlog应用完成后再去更改表名,这样才能保证数据不丢失。而pt-osc不需要显式加锁是因为原表和影子表上的更新是在同一个事务里的,原表变更完成,影子表上的表更也就完成了,在做RENAME时会阻塞原表上的更新,RENAME完成后变更就发生在了新表上了(之前的影子表)。

     

  7. 停止binlog streamer,处理收尾工作,结束。

2.2 使用限制和风险

1)使用要求和限制

  1. 必须有一个从库的binlog是row模式,并且binlog_row_image 设置成full。主节点没有特殊要求;

  2. 主备节点上,目标表的结构必须是相同的;

  3. 不支持外键约束和触发器

  4. 目标表上必须有主键或者唯一键,gh-ost使用该键遍历表

    • 主键或者唯一键不能包含为空的列。也就是说键中的列的属性应为 NOT NULL,或键中的列是可以为空 但是实际数据中没有NULL 值。

    • 默认情况下,若是唯一键中包含可为空的列,gh-ost不会运行,用户可以使用 --allow-nullable-unique-key ,但是依然要确保实际数据没有NULL值,若是有NULL 值,gh-ost不能保证能将其完全迁移走。

  5. 不允许迁移存在相同名称且大小写不同的表;

  6. 不支持多源复制,不过可以尝试使用 --allow-on-master 选项连接到主库

  7. 双主复制,只支持一台实例上有写请求的情况

  8. 不支持表更名的操作 :ALTER TABLE ... RENAME TO some_other_name

  9. PXC集群不能使用该工具。gh-ost在更改表名阶段是使用不同的线程执行LOCK TABLE,RENME ,DROP TABLE 操作的,由于PXC的验证机制这会导致执行操作的PXC节点发生死锁。

2)使用风险

  1. 更改列名

    1. 使用change方式更改非主键或者非唯一键的列名

      更改列名gh-ost也会发出警告,并退出:

      FATAL gh-ost believes the ALTER statement renames columns, as follows: map[id:id_new]; as precaution, you are asked to confirm gh-ost is correct, and provide with `--approve-renamed-columns`, and we're all happy. Or you can skip renamed columns via `--skip-renamed-columns`, in which case column data may be lost
      

      修改列名也会有丢失数据的风险,所以需要自己先确认gh-ost的行为是否符合预期,同时提供了两个选项来告诉gh-ost如何处理重命名的列。

      --approve-renamed-columns:该选项是告诉gh-ost要同步重命名列的数据

      --skip-renamed-columns:跳过重命名的列,也就是对于重命名的列上的数据不进行同步

    2. 更改主键或者唯一键的列名

      若表上只有主键或者唯一键,gh-ost会直接退出,并抛出以下日志:

      FATAL No shared unique key can be found after ALTER! Bailing out
      

      若表上既有主键又有唯一键,更改其中一个的列名,gh-ost会选择另一个键做为 共享唯一键。

    3. 先删除列然后添加改名后列

      该方式和 pt-online-schema-change一样会导致数据丢失。

  2. 对于外键的处理

    默认gh-ost不支持有外键引用或者包含外键的表变更。但是提供了相应的选项;

    --skip-foreign-key-checks :跳过外键的检查,这时不会对外键进行检查,最终会导致表上外键丢失或者外键引用的表不存在;

    -discard-foreign-keys :该选项明确告诉gh-ost不在影子表上创建外键,最终变更后的表会丢失外键。

  3. 创建唯一键或者主键

    使用gh-ost创建唯一键或者主键不会有相关警告,由于使用insert ignore into的方式同步数据,所以有可能会造成数据丢失。

  4. 锁争用问题

    在更改表名前会对表加写锁,若表上操作频繁可能会导致锁等待。

  5. 在开启半同步复制情况下,若设置 rpl_semi_sync_master_wait_point = AFTER_SYNC,在获取同步数据的上下边界时,有可能获取不到的最新上下边界,导致数据丢失。github上已经有人提交相关bug gh-ost may lose data when performing DDL on mysql with semi-sync replication enabled

2.3 丰富的监控功能

gh-ost在运行期间也会监控数据库的负载,和pt-osc类似,只是参数不同。另外gh-ost也提供了一些交互功能,动态的修改需要监控的指标和阈值等,这里不再赘述。

标签:binlog,DDL,ost,t1,ONLINE,test,id,gh
来源: https://www.cnblogs.com/ivan17/p/16331773.html