本文主要对 OceanBase 的DDL实现做一下简单介绍,并且通过跟MySQL DDL 的实现做对比,来帮助我们更加容易理解。
MySQL DDL 的算法
MySQL 的DDL实现算法主要有 copy、inplace和instant。
copy
copy 算法的实现方法较为简单,MySQL 会建立一个新的临时表,把源表的所有数据写入到临时表,在此期间无法对源表进行数据写入。MySQL 在完成临时表的写入之后,用临时表替换掉源表。
copy 算法会对表进行锁定,所以该种算法下的DDL属于Offline DDL,在执行DDL的时候,不能并发写操作,并且因为是copy数据,所以速度也相对较慢。
inplace
inplace 算法属于 Online DDL,根据是否需要重建表,又分为了rebuild 和 no-rebuild。
no-rebuild 不需要重建表,只需要修改表的元数据,所以速度很快。
rebuild 是需要重建表的,虽然不会创建临时表,但是实际底层的实现还是会创建新的 ibd 文件最后切换,相当于物理文件的“copy”。这个操作是 Online的,但并不是说不会锁表,其实中间会经历两次MDL 写锁,会影响写操作,只不过因为持有时间非常短,所以业务基本无感知。
instant
对于一些inplace需要重建表的操作,可以通过instant来实现快速执行,因为它只需要修改数据数据字典中的内容而不再需要重建表,最典型的就是立刻加列。立刻加列完成后,当需要查询表中数据时,会将新增列的默认值追加到读取的数据后面并返回。
当然,在实际业务中,大部分的MySQL DDL操作都是原生+工具结合的方式来做的,支持no-rebuild和instant的DDL使用原生,其他的DDL则使用工具,比如 pt-osc、gh-ost。
OB的DDL实现
OB的DDL包括 Online DDL 和 Offline DDL,其中 Online DDL 不影响读写,Offline DDL会堵塞写操作,直至DDL完成,这期间读操作不受影响。
Offline DDL
OB 的 Offline DDL 使用的是两表双写的方案。
两表双写方案的思路是新建一张临时的隐藏表格(对用户不可见)用于双写,同时在后台将原表的数据补全到新建的临时表格中,然后将原表重命名为一个另外的临时表格,将补全数据的临时表格重命名为原表原来的名字,最后将原表的删除。
- 用户发起对 Src Table (T1) 的DDL操作;
- 停读写并新建隐藏表:停读写,新建一张隐藏表 Temp Table(T2),T2 是基于 T1 做 DDL得到的Schema,假设此时的Schema Version为S1
- 等事务结束:等待表格 T1 的所有分区上使用过比Schema Version S1小的事务都结束,获取一个快照点
- 主表补全:基于步骤3上获取的快照点,扫描表 T1 的数据,按照 T2 的Schema形式排序并写入表 T2
- 依赖对象数据重建:重建索引表、约束等
- 表名切换:将T1命名为T3,T2命名为T1,假设此时Schema Version S2
- 写Barrier日志:每个分区独立处理写Barrier日志
- 恢复读写:允许新表格上的读写
- 删除原表:将表T3删除
从上面的流程可以看出来,Offline DDL 的实现其实跟 MySQL 的 COPY 方式实现原理非常的像,不过OB因为是分布式数据库,所以会加入很多分布式数据库的元素以及保障,并且底层做了很多性能的优化。
Online DDL
Online DDL 主要有两种,原表上操作,比如列重命名,基本上只需要修改元数据信息,这个操作会非常的快;如果是新增索引的操作,是需要重整非原表数据的,具体的耗时跟表的数据量成正比。
拿创建全局索引流程来举例:
简单来说会在创建索引开始时,拿到一个快照点,根据当前快照获取存量数据,并且排序并生成索引。全量数据获取完成后,获取增量数据,最后改成一个可读可写的状态,然后这个索引就可以用来做查询优化了。
建索引因为是ONLINE 操作,所以执行过程中是不会堵塞读写操作的。
当前版本支持的Online DDL 和 Offline DDL
下面支持的操作对应的版本是 v4.2.2 社区版本。
Online DDL操作:
Offline DDL操作:
DDL 执行以及进度查询
DDL 语法跟MySQL是完全相同的,这里就不过多赘述了。
查看DDL进度
OB 提供了实时 DDL 进度展示功能,通过查询 GV$SESSION_LONGOPS 视图,可以展示 DDL 操作的执行状态和进度。
mysql> select * from oceanbase.gv$session_longops\G;
各个字段含义:
- sid:现在没有填值,为默认的 -1。
- trace_id: OBServer 程序日志的ID,可以用该ID来搜索相关的日志文件。
- opname:建索引时,会展示 create index 信息。
- target:建索引时,展示正在创建的索引名。
- svr_ip: 调度任务在哪个 OBServer 执行。
- svr_port:调度任务在哪个 OBServer 执行。
- start_time:索引构建开始时间,这里只精确到日期,跟 Oracle 是兼容的。
- elapsed_seconds: 索引构建执行的时间,单位为秒。
- time_remaining: 兼容 Oracle 的字段,暂时还没有实现剩余时间预测的能力。
- last_update_time: 统计信息收集的时间,也是精确到日期,跟 Oracle 是兼容的。
- message:里面包含了多个信息,ENANT_ID为租户 ID,TASK_ID为DDL 的任务 ID,STATUS 为 DDL 执行到的状态,REPLICA BUILD 指的是数据补全阶段,索引数据补全主要分为扫描主表数据,排序,写入到索引表阶段,三个阶段处理的行数分别对应于ROW_SCANNED, ROW_SORTED 和 ROW_INSERTED,因排序阶段可能会进行多轮归并,所以ROW_SORTED 的行数通常比 ROW_SCANNED 和 ROW_INSERTED 要多。
写在最后
OB因为是分布式数据库,所以DDL其实还设计很多分布式相关的底层逻辑实现,比如多副本如何调度、主备库怎么同步等等,这里就不详细介绍了。如果大家想要了解更详细的细节,可以到官网查询或者问答区咨询。
有兴趣的朋友,可以关注下微信公众号,会同步更新内容的。