背景
目前,IMCI已经具备分析开放格式ORC文件的能力,通过外表的方式关联OSS对象存储上的ORC文件,查询时直接访问ORC文件,并无缝与上层算子对接,通过并行和批量化技术实现查询性能提升。由于ORC是开放格式,用户无需将数据来回流转,比如将离线数仓计算结果回流到关系型数据库,真正实现一份数据,多种计算的效果。存储在OSS对象上的数据,除了一部分来源于离线数据计算结果;还有一部分来源于归档。归档数据放OSS的直接原因是成本,用户可以通过导出方式将这部分数据写入到OSS。这种方式能降低存储成本,但对于用户查询则不友好,进行查询搜索时,需要同时访问PolarDB和OSS上的数据,并在业务层做聚合,查询性能也无法保障。
为此,PolarDB需要提供一套全生命周期管理用户数据的方案,通过分区表的方式来管理不同的存储介质,比如,热分区采用InnoDB引擎,温分区采用X-Engine引擎,冷分区采用OSS。不同分区采用不同引擎,解决了存储成本和查询一体化的问题;查询的性能问题则交由IMCI+混合分区来解决。对用户来说,仍然是一张表,在使用方式完全不变的情况下,节省存储成本,并提升查询效率。本文主要描述列存引擎支持PolarDB混合分区的方案。
ORC文件分析加速
目前列存通过外表方式关联是通过COMMENT=‘columnar=1’ CONNECTION='oss://xxx/region.orc,来关联orc文件。表本身在PolarDB中仍然是InnoDB引擎,执行时,通过判断是否有列存索引来决定是否走列存计划。这种方式的只适用于只读表,因为如果有增量的写入,会写入到innodb引擎,列存分析时只会分析ORC文件的内容。对于数据回流的场景,这种只读场景也能很好适应。
CREATE TABLE `region` (
`r_regionkey` bigint(20) NOT NULL,
`r_name` varchar(25) NOT NULL,
`r_comment` varchar(152) DEFAULT NULL,
PRIMARY KEY (`r_regionkey`)
) ENGINE=InnoDB COMMENT='columnar=1' CONNECTION='my_oss_server/region.tbl.orc'
PolarDB混合分区
混合分区结构

混合分区查询
每个列存表的通过rowid唯一标识一条记录,rowid逻辑递增。列存引擎在处理数据时,通过延迟物化的优化来减少数据在算子间传递。具体来说,从表中扫描的数据,非必要可以不物化数据,只是传递rowid即可。在真正需要访问这个数据时,再通过(rowid,col_id)去访问特定的数据。列存分区表在存储引擎层是若干个独立的列存表组成,因此多个分区表的rowid可能存在冲突。为了解决rowid冲突问题,在执行时引入了虚拟rowid,唯一标识表中的一个物理行。对于OSS表,目前没有延迟物化的逻辑,往上层算子传递都是具体数据,当一条SQL同时需要访问PolarStore分区和OSS分区时,算子都走物化逻辑。同时,OSS表的Scan算子暂时不具备filter下推能力,因此,涉及到OSS分区的访问,需要filter作为其父亲节点来做过滤。以简单查询select c1,c2 from t where c1 > 2为例,两种plan的计划如下:


| 查询涉及的分区 | 执行计划 |
|---|---|
| PolarStore分区 | Plan-A |
| OSS分区 | Plan-B |
| PolarStore分区 + OSS分区 | Plan-B |
对于分区表来说,无论是否包含OSS分区,分区的prune能力仍然保留,主要区别在于,在扫描分区时,需要感知当前的分区是Innodb分区还是OSS分区,不同的分区采用完全不同的两套接口来访问数据,完全解耦。分区表Scan算子抽象了一套接口,逐一访问InnoDB分区和OSS分区数据。扫描算子与其他算子统一通过Data方式交互,这样做到所有修改都收敛在算子本身。
数据列物化模式
前面提到了,默认的Scan算子与其它算子交互的数据是rowid-map,通过延迟物化的方式来减少数据传递。对于混合分区的场景,由于底层数据是完全不同的介质和存储结构,需要统一数据传输格式,Scan算子支持了物化模式。为了避免对于已有逻辑的侵入,在rowid-mask的基础上,新增一层转换,来实现rowid->data的转换。实际上,用户在查询时,大部分时间都是在查热分区(InnoDB分区),因此,pruning后,如果所有分区都是InnoDB分区,那么这个转换的消耗不会存在,所有filter都可以下推到Scan算子完成。这种实现方式,使得可以根据具体SQL来灵活切换两种执行模式。

ORC引擎支持列索引
802新增支持存储引擎ORC,支持将数据按ORC格式写入到OSS对象存储,目的是为了节省冷数据的存储成本。
由于ORC引擎仍然采用行式执行器进行查询,相对列存并行,批量化处理较慢,为了提升SQL查询效率,需要ORC引擎也支持列存索引。
ORC引擎目前实现比较简单,支持创建和插入,更新和删除支持还不完善,不支持创建二级索引。列存索引是InnoDB引擎的一个特殊二级索引,为ORC引擎再支持一套类似的机制。实际上,ORC文件存储在OSS上,执行DDL操作时,为ORC表字典新增列存属性,关联OSS信息,这样分析时可以采用列存引擎进行分析。
引擎&数据字典
ORC引擎的“列索引”相对于InnoDB列索引的一个核心区别是,对于InnoDB引擎而言,列索引是一个特殊的“二级索引”,而ORC引擎是一个列存表,“列索引”没有数据实体,只有元数据。对于ORC引擎的DDL操作,需要同步为这些DDL接口适配创建列索引和内存结构,列索引包含了表的所有列(不存在为部分列建列索引),这些列信息与ORC文件的列一一对应。
ORC引擎是表维度的,index_id实际由(table_id, index_id)生成,在redo日志和index-meta中需要新增引擎类型标识是InnoDB引擎还是ORC引擎,利用这个信息,在故障恢复时,无需为ORC引擎重建列索引数据,因为所有ORC引擎的数据都在OSS远端,列存索引没有实体数据,只需为它重建元数据即可。
访问归档ORC文件适配
通过innodb分区归档到OSS的表,存储路径格式为:表名/分区名/二级分区名/xxx.orc
1.列属性与MySQL列类型映射
2.单表多ORC文件优化
语法使用
单表:
创建ORC引擎表(Create table)
//创建orc引擎表,后续写入会以orc格式写到OSS上。
create table xxx engine=orc comment='columnar=1';
备注:这种方式直接写远端存储,效率比较低。
InnoDB引擎转ORC引擎(Alter table)
//将表转换为orc表,数据以orc格式存储在OSS上。
alter table xxx engine=orc;
//将表转换为orc表,数据以orc格式存储在OSS上,同时创建列索引
alter table xxx engine=orc comment='columnar=1';
ORC引擎新增/删除列索引
//为orc表新增列索引
alter table xxx engine=orc comment='columnar=1';
//清理orc表的列索引
alter table xxx engine=orc comment='columnar=0';
分区表:
转换为ORC分区的列索引属性继承于表的属性,如果表本身带列索引,那么转换的ORC分区,也会自动带上列索引。转换分区引擎属性通过新增语法 alter table xxx change partition Pxx 实现。
//带列索引的分区表
CREATE TABLE t1(a1 INT, a2 INT, a3 INT, a4 INT) comment='columnar=1'
PARTITION BY RANGE(a1)
(
PARTITION p1 VALUES LESS THAN (20),
PARTITION p2 VALUES LESS THAN (40),
PARTITION p3 VALUES LESS THAN (60),
PARTITION p4 VALUES LESS THAN MAXVALUE
);
//转换的orc分区,自动关联列存索引,查询走列存模式
ALTER TABLE t1 CHANGE PARTITION p1 ENGINE = orc;
//不带列索引的分区表
CREATE TABLE t1(a1 INT, a2 INT, a3 INT, a4 INT)
PARTITION BY RANGE(a1)
(
PARTITION p1 VALUES LESS THAN (20),
PARTITION p2 VALUES LESS THAN (40),
PARTITION p3 VALUES LESS THAN (60),
PARTITION p4 VALUES LESS THAN MAXVALUE
);
//转换的orc分区,不带列索引,查询走行存模式。
ALTER TABLE t1 CHANGE PARTITION p1 ENGINE = orc;
使用限制
1.不支持单独为某个分区表,转为为ORC分区时指定列索引属性
CREATE TABLE t1(a1 INT, a2 INT, a3 INT, a4 INT)
PARTITION BY RANGE(a1)
(
PARTITION p1 VALUES LESS THAN (20),
PARTITION p2 VALUES LESS THAN (40),
PARTITION p3 VALUES LESS THAN (60),
PARTITION p4 VALUES LESS THAN MAXVALUE
);
//转换change partition操作,不支持comment语法
ALTER TABLE t1 CHANGE PARTITION p1 ENGINE = orc comment='columnar=1';
2.对于innodb分区表,只支持带有表级列索引的表归档为ORC分区,这个主要是为了保证innodb列索引属性与ORC表的列索引属性一致。
create table t1_columns_index (a datetime, b int comment "columnar=1", c char(3) comment "columnar=1")
partition by range columns(a, b, c) (
partition p1 values less than ('2022-02-01', 100, 'ddd'),
partition p2 values less than ('2023-02-01', 200, 'ggg')
);
alter table t1_columns_index add primary key(a,b,c);
//报错,修改分区属性为ORC不支持
alter table t1_columns_index change partition p1 engine=orc;
3.不支持二级分区归档为ORC引擎。
列存索引视图
访问imci_indexes视图时,由于orc表没有字典缓存,为避免访问每个分区的名字时都去访问字典,orc的分区采用表名+oss_part的方式展示。对于单表与innodb引擎一致。另外,orc分区的数据都在OSS上,列存没有实际数据,因此rowid始终为0。下表是一个表名为sales4_imci的分区表的列存索引视图。
CREATE TABLE `sales4_imci` (
`id` int(11) DEFAULT NULL,
`order_time` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='columnar=1' CONNECTION='default_oss_server'
/*!99990 800020205 PARTITION BY RANGE COLUMNS(order_time)
(PARTITION p0 VALUES LESS THAN ('2021-9-1') ENGINE = InnoDB,
PARTITION p20211001000000 VALUES LESS THAN ('2021-10-01 00:00:00') ENGINE = ORC,
PARTITION p20211101000000 VALUES LESS THAN ('2021-11-01 00:00:00') ENGINE = InnoDB,
PARTITION p20291101000000 VALUES LESS THAN ('2029-11-01 00:00:00') ENGINE = ORC) */
+-------------+-------------------------------+-----------+-----------+--------+
| schema_name | table_name | num_packs | pack_size | row_id |
+-------------+-------------------------------+-----------+-----------+--------+
| sbtest | sales4_imci_oss_part | 0 | 65536 | 0 |
| sbtest | sales4_imci#P#p20211101000000 | 1 | 65536 | 31 |
| sbtest | sales4_imci_oss_part | 0 | 65536 | 0 |
| sbtest | sales4_imci#P#p0 | 0 | 65536 | 0 |
+-------------+-------------------------------+-----------+-----------+--------+
参考文档
https://aliyuque.antfin.com/nituizi/oncxfu/ev3a3t
https://aliyuque.antfin.com/nituizi/oncxfu/mhe9yk




