online schema change and create index

文章目录

      • online schema change
        • 为什么要允许数据库中同时存在两个版本的元数据?
        • 如何实现两个版本元数据同时提供服务,却不会产生一致性问题?
      • CREATE INDEX
        • 如何保证索引表达到与主表一致

online schema change

PolarDB-X Online Schema Change
PolarDB-X:让“Online DDL”更Online
VLDB13 | Online, Asynchronous Schema Change in F1
online schema change paper

online schema change的大致思路就是:允许数据库中同时存储在两个版本的元数据(schema),并且保证同时基于两个版本的元数据的事务操作不会造成数据不一致。

为什么要允许数据库中同时存在两个版本的元数据?

对于具有多个无状态计算节点(CN)的分布式数据库,每个CN上都会缓存一份元数据,并基于提供SQL服务。当执行ddl语句修改元数据时,最理想的情况是所有CN在同一时间切换到新版本的元数据,但这是不可能办到的。
online schema change and create index_第1张图片

所以DB中至少存在两个版本元数据是必然事件,那同时存在两个版本的元数据会存在什么问题呢?
online schema change and create index_第2张图片

以 CREATE INDEX 为例:
集群由 CN(计算节点) 和 DN(存储节点) 构成,每个 CN 中缓存一份 Schema。由于 CN0 和 CN1 异步加载 Schema,添加索引过程中可能存在一个时刻,CN0 认为有索引而 CN1 认为没有,此时产生两种异常

  1. 索引上有多余数据(Orphan Data Anomaly): CN0 执行了 INSERT,在表和索引上插入数据,随后 CN1 执行 DELETE。由于 CN1 认为没有索引,仅删除了表上的数据
  2. 索引上缺少数据(Integrity Anomaly): CN1 执行 INSERT,由于 CN1 认为没有索引,仅在表上插入数据,没有写相关的增量日志,导致索引创建完成后缺少这次 INSERT 的数据。

如何解决这个问题呢?

  1. MDL悲观锁:当执行ddl语句时,为所有CN的元数据加mdl锁,禁止向外继续提供服务。当所有CN的元数据全部更新成功之后,再解锁提供服务。这时虽然也有两个版本元数据,但是其并不向外提供服务,不会产生问题。
  2. 允许数据库存在两个版本元数据,且都对外提供服务,但是保证没有数据一致性问题。

显然第二种实现方式更好。

如何实现两个版本元数据同时提供服务,却不会产生一致性问题?

《 Online, Asynchronous Schema Change in F1 》论文中为元数据设计了若干状态:
online schema change and create index_第3张图片

online schema change and create index_第4张图片
schema的修改其实就是element的增删改,所以下面把element的状态直接称为schema的状态。

这时元数据的版本被定义为:元数据 + 状态。同一份元数据如果状态不同,也被称为两个版本的元数据。

这时新旧元数据的切换就变成:
old_schema_stateN -> new_schema_state1 -> new_schema_state2 -> new_schema_state3

其中state包括了absent,delete_only,write_only,public,注意state表示的是schema中修改的element的状态。

所以online schema change的工作就变为设计新旧元数据切换是的中间状态转变,保证相邻的两个版本的元数据不会产生数据一致性问题。

CREATE INDEX

下面以create index为例,介绍如何实现online schema change:
其状态转变为:
假设创建索引前元数据为S1,添加索引后的元数据为S2
S1 -> S2_delete_only -> S2_write_only -> S2_public

需要注意的是在添加索引的过程中,有三个问题:

  1. 索引表数据多于主表,产生多余数据(出现原因:先插入索引表和主表,但后只删除了主表)
  2. 索引表数据少于主表,却向外提供查询服务(出现原因:在索引表达到与主表一致前,就向外提供查询)
  3. 索引表数据等于主表,但却有不一致的数据(出现原因:一次更新没有同时修改索引表或者主表)

只要避免这三个问题,即保证了索引和主表的一致性

  • S1 -> S2_delete_only:S2_delete_only虽然有索引,但是不允许对索引insert、select,虽然可以对索引update,但是目前索引表上没有数据,不会出现问题1、2、3
  • S2_delete_only -> S2_write_only:S2_delete_only 和S2_write_only都允许对索引表delete,都不允许对索引select,都允许对索引表update,不会出现问题1、2、3
  • S2_write_only -> S2_public:S2_delete_only 和S2_write_only都允许对索引表delete,S2_public允许对索引select,但是此时索引已经与数据表一致,都允许对索引表update,所以不会出现问题1、2、3
如何保证索引表达到与主表一致

当达到S2_write_only阶段时,数据库开始允许主表和索引表双写,且同时开启backfill。
对于S2_delete_only以及之前阶段的数据,我们称之为存量数据,对于 S2_write_only阶段写入的数据,我们称之为增量数据。
那么为了保证索引和主表的一致性,在由S2_write_only转为S2_public状态之前,必须保证存量和增量数据都已经写入索引表。

  • 对于增量数据,毫无疑问会同时写入主表和索引表。
  • 对于存量数据,开启backfill回填到索引表,即select主表 ,然后insert索引表

注意,backfill时:

  • select主表需要加读锁,因为很可能出现一种情况backfill时,其他线程执行了一次update,同时更新了主表和索引表。但是如果此时对应的数据还未backfill到索引表上,则会把新数据写入索引表(或者对索引表不操作),但是之后的backfill会使得旧数据覆盖了新数据(或者直接写入旧数据,因为不加读锁backfill是一致性读),导致索引和主表数据不一致。
  • select主表加读锁会获取到部分增量数据,此时backfill索引表时,可能出现主键冲突或者唯一索引冲突,此时需要判断冲突的数据是否为增量数据,即判断主键是否一致,如果是,则略过。

当backfill完成时,索引表和主表达成一致,即可设置为public,向外提供查询服务。

你可能感兴趣的:(数据库,分布式系统,数据库,java,mysql)