OceanBase 4.3.0 列存引擎解读:OLAP场景的入门券

近期,OceanBase 发布了4.3.0版本,该版本成功实现了行存与列存存储的一体化,并同时推出了基于列存的全新向量化引擎和代价评估模型。通过强化这些能力,OceanBase V4.3.0 显著提高了处理宽表的效率,增强了在AP(分析处理)场景下的查询性能,并依旧保持着对TP业务的良好支持。
这篇文章是基于列存功能进行的一次简单测试,并结合测试结果给出了一些面向用户的使用建议。

背景

首先需要给大家简单介绍一个背景知识点,就是OceanBase 存储层架构图中的 “基线数据” 和 “增量数据” 分别指什么?

存储层架构图如下:

OceanBase 数据库的存储引擎基于 LSM-Tree 架构,将数据分为静态的基线数据和动态的增量数据两部分。

数据库 DML 操作插入、更新、删除时,首先写入内存中的 MemTable,等内存中的 MemTable 达到一定大小时,转储到磁盘成为 SSTable,内存中的 MemTable 和转储之后的 SSTable 被统称为增量数据。当增量的数据达到一定规模的时候,会触发增量数据和老版本基线数据的合并,合并成新版本的基线数据,也就是新版本的基线 SSTable。同时每天晚上的空闲时刻,系统也会自动每日合并。

在进行查询时,需要分别对增量数据和基线数据进行查询,并将查询结果进行归并,返回给 SQL 层归并后的查询结果。OceanBase 也数据读取过程中实现了 Block Cache 和 Row Cache 两层缓存,来避免对基线数据的随机读。

列存整体架构

对于 AP 分析类查询,列存可以极大地提升查询性能,也是 OceanBase 做好 HTAP 的一项不可缺少的功能。用于进行 AP 分析的数据通常都是静态的,很少被原地更新,而 OceanBase 的 LSM Tree 架构中基线数据也是静态的,天然就适合列存的实现。增量数据是动态的,即使在列存表中,存储层的增量数据仍然是行存,同步的日志也都是行存模式,对于事务处理、日志同步、备份恢复都不会造成影响。这样就可以在一定程度上兼顾 TP 类和 AP 类查询的性能。

在 OceanBase 4.3 版本中创建表时,可以选择把表创建成行存表、列存表、行列冗余存储的表。无论是什么模式的表,表的增量数据均保持行存格式,因此列存表的 DML、事务、上下游数据同步都不会受到影响。

列存表和行存表在存储层的区别主要体现在基线数据的格式上,根据用户在建表时的设置,基线数据可以有行存,列存,行存列存冗余三种模式。

行存模式下,基线数据也是行存,如下图所示:

列存模式下,每列数据在基线数据中存储为一个独立的基线 SSTable,和行存有所不同,如下图所示:

行存列存冗余的模式下,在基线数据中会同时存储行存 SSTable 和列存 SSTable,如下图所示:

在这种行存列存冗余的模式下,优化器会根据访问行存和访问列存的代价高低,自动选择扫描列存 SSTable 还是行存 SSTable。

例如我们创建一个行列冗余存储的表 t_column_row。建表语句中的 with column group (all columns, each column) 表示这张表是行列冗余存储,其中的 each column 表示列存,all columns 表示再增加一份儿行存。

create table tt_column_row(c1 int primary key, c2 int , c3 int) with column group (all columns, each column);

如果我们不加过滤条件,查询表中的一个列的全量数据,就会生成下面的执行计划,计划中的 COLUMN TABLE FULL SCAN 算子表示优化器根据代价模型,选择扫描列存的基线数据。因为相比选择扫描行存数据,扫描列存数据会在存储层减少 c2 和 c3 列数据的额外 I/O 开销。

explain select c1 from t_column_row;
+-----------------------------------------------------------------+
| Query Plan                                                      |
+-----------------------------------------------------------------+
| ==============================================================  |
| |ID|OPERATOR              |NAME        |EST.ROWS|EST.TIME(us)|  |
| --------------------------------------------------------------  |
| |0 |COLUMN TABLE FULL SCAN|t_column_row|1       |3           |  |
| ==============================================================  |
| Outputs & filters:                                              |
| -------------------------------------                           |
|   0 - output([t_column_row.c1]), filter(nil), rowset=16         |
|       access([t_column_row.c1]), partitions(p0)                 |
|       is_index_back=false, is_global_index=false,               |
|       range_key([t_column_row.c1]), range(MIN ; MAX)always true |
+-----------------------------------------------------------------+

如果我们不指定过滤条件进行全表扫,就会生成下面的执行计划,计划中的 TABLE FULL SCAN 算子表示优化器选择扫描行存的基线数据。因为行存的增量数据和基线数据的格式都是行存,所以增量和基线归并起来速度会更快,这时候优化器会选择生产走行存的计划。

explain select * from t_column_row;
+-----------------------------------------------------------------------------------------------+
| Query Plan                                                                                    |
+-----------------------------------------------------------------------------------------------+
| =======================================================                                       |
| |ID|OPERATOR       |NAME        |EST.ROWS|EST.TIME(us)|                                       |
| -------------------------------------------------------                                       |
| |0 |TABLE FULL SCAN|t_column_row|1       |3           |                                       |
| =======================================================                                       |
| Outputs & filters:                                                                            |
| -------------------------------------                                                         |
|   0 - output([t_column_row.c1], [t_column_row.c2], [t_column_row.c3]), filter(nil), rowset=16 |
|       access([t_column_row.c1], [t_column_row.c2], [t_column_row.c3]), partitions(p0)         |
|       is_index_back=false, is_global_index=false,                                             |
|       range_key([t_column_row.c1]), range(MIN ; MAX)always true                               |
+-----------------------------------------------------------------------------------------------+

列存的基础性能测试

我这里使用 TPCH 100G 的测试集,对大家普遍比较关心的列存性能,进行了一个简单的测试,包括列存和行存的存储压缩率对比,以及列存的查询性能测试。版本是社区版的 OceanBase_CE-v4.3.0.1。

存储压缩率测试

首先对 OceanBase 4.3.0 版本的列存表压缩率做一个测试,对比行存表。

分别把 TPCH 100G 的数据导入一批纯行存表和一批纯列存表中,选择最大的一张表 lineitem 计算存储开销,导入的 lineitem.tbl 大小大约是 76 GB,在行存表中占用了 22.5 GB 的存储空间,在列存表里占用了 15 GB 的存储空间。

-- 列存表 lineitem 定义
CREATE TABLE lineitem (l_orderkey BIGINT NOT NULL,l_partkey BIGINT NOT NULL,l_suppkey INTEGER NOT NULL,l_linenumber INTEGER NOT NULL,l_quantity DECIMAL(15,2) NOT NULL,l_extendedprice DECIMAL(15,2) NOT NULL,l_discount DECIMAL(15,2) NOT NULL,l_tax DECIMAL(15,2) NOT NULL,l_returnflag char(1) DEFAULT NULL,l_linestatus char(1) DEFAULT NULL,l_shipdate date NOT NULL,l_commitdate date DEFAULT NULL,l_receiptdate date DEFAULT NULL,l_shipinstruct char(25) DEFAULT NULL,l_shipmode char(10) DEFAULT NULL,l_comment varchar(44) DEFAULT NULL,PRIMARY KEY(l_orderkey, l_linenumber))row_format = condensedpartition by key (l_orderkey) partitions 4with column group(each column);

对于 lineitem 这张表的表结构来说,列存表的存储空间大约是行存表的 2/3 左右。这一点大家也容易理解,相比行存表,列存表中同一列中的数据属于同一类型,如果能够集中存储同一类型的数据,就可以选择效率更高的方式进行压缩。

为什么列存的压缩效果对比行存,看上去并没有想象中的这么强?因为 OceanBase 的行存表,压缩能力已经非常好了。不过即使我们在行存表的存储压缩上做过诸多优化,列存的压缩效果也会比行存表稍好一些,并且表中的列越多,数据量越大,列存的压缩效果就会越明显。

查询性能测试

找了三台 6C 35G 的机器做测试(后续所有功能都在这几台机器上做的测试),简单对比行存表和列存表的一些查询性能。

我们创建了两张表,一张是纯行存表 lineitem_row,一张是纯列存表 lineitem_column,都导入了 TPCH 100G 的数据集。

走主键点查

先通过两条 SQL 测下列存表和行存表走主键时的点查性能:

-- 列存
select * from lineitem_column where l_orderkey = 7 and l_linenumber = 1;
1 row in set (0.035 sec)select * from lineitem_column where l_orderkey = 7;
7 rows in set (0.036 sec)-- 行存
select * from lineitem_row where l_orderkey = 7 and l_linenumber = 1;
1 row in set (0.044 sec)select * from lineitem_row where l_orderkey = 7;
7 rows in set (0.044 sec)

行存和列存走主键的点查性能都在 0.04 秒左右,基本是一致的(为避免篇幅过长,图中只展示 SQL 的执行时间,不再展示查询的结果数据)。

无索引全表扫

我们不走主键和索引,分别在列存表和行存表中执行以下 SQL:

-- 列存
select * from lineitem_column where l_extendedprice = 13059.24;
102 rows in set (0.467 sec)-- 行存
select * from lineitem_row where l_extendedprice = 13059.24;
102 rows in set (2.306 sec)

返回的行数是 102 行,行存表执行时间在 2.31 秒左右,列存表的执行时间在 0.47 秒左右。对于 lineitem 这种有十几个列的小宽表,列存表的性能是行存表的五倍。

过滤条件不走宽表的主键和索引时,虽然列存需要拼各个列的结果,这一步会比行存表更耗时。但列存相比行存,可以省下大量的 I / O 开销,行存表在这里是做了一次真正的 “全表扫描”。

在过滤条件中只有单列,并且没能走主键和索引的前提下,表越宽(列数越多),相比行存表,列存表的查询性能就会越好。在这个例子中,行存表如果想通过省下 I / O 开销来提高性能,需要在 l_extendedprice 列上创建索引。所以在一些场景下,列存表相对于行存表,可以节省一些创建和维护索引的额外开销。

在不走主键和索引的前提下,我们把过滤条件变的更复杂一些,让过滤条件中包含多个列的计算:

-- 列存
select * from lineitem_column where l_partkey + l_suppkey = 20999999;
7 rows in set (5.091 sec)-- 行存
select * from lineitem_row  where l_partkey + l_suppkey = 20999999;
7 rows in set (6.254 sec)

这次列存性能还是强于行存,但是只是略强,优势没有上面这个测试好几倍的性能差距了。

我们继续让过滤条件中包含更多个列的计算:

-- 列存
select * from lineitem_column where l_partkey + l_suppkey +l_extendedprice + l_discount + l_tax = 19173494.34;
1 row in set (15.675 sec)-- 行存
select * from lineitem_row  where l_partkey + l_suppkey +l_extendedprice + l_discount + l_tax = 19173494.34;
1 row in set (15.837 sec)

我们可以发现一个规律,当过滤条件的表达式中涉及的列数持续增加时,行存表的性能会逐渐逼近列存表。

简单来说,在列存表中,需要先根据主键值,把不同列的对应行拼在一起,然后才能做列与列之间的运算,拼列并在不同列间做计算的操作会为列存表带来额外的开销,当这个开销逐渐逼近在行存表中额外列的 I/O 开销时,行存表的性能就会逐渐逼近列存表。

聚合计算

对于简单聚合,列存表的性能比行存表更好一些。下图左边的 lineitem_column 为列存表,右边的 lineitem_row 为行存表。 

对于复杂一些的聚合计算,例如 max(l_partkey + l_suppkey),列存和行存的性能如下:

-- 列存表
select max(l_partkey + l_suppkey) from lineitem_column;
+----------------------------+
| max(l_partkey + l_suppkey) |
+----------------------------+
|                   20999999 |
+----------------------------+
1 row in set (19.302 sec)-- 行存表
select max(l_partkey + l_suppkey) from lineitem_row;
+----------------------------+
| max(l_partkey + l_suppkey) |
+----------------------------+
|                   20999999 |
+----------------------------+
1 row in set (4.833 sec)

在有表达式的聚合计算测试中,列存表的性能明显是不如行存表的。一个原因是刚刚提到过的,在列存表中,需要先把 l_partkey 和 l_suppkey 两列的对应行拼在一起,然后才能做加法运算,拼列并在不同列间做计算的操作会为列存表带来额外的开销。另一个原因就是当前的 4.3.0.1 版本,在列存的表达式过滤这里做了比较深入的向量化执行优化,而列存的表达式聚合优化则被排期在了后续的版本中。

我们可以得出一个结论:聚合函数中包含多列表达式运算的场景,不是当前列存版本的优势场景。不过 lineitem 这张表的表结构中列数并不多,如果测试的表是大宽表,例如有几百或者上千个列,在同样的场景下,因为列存表相比行存表节省的 I/O 开销会比 lineitem 表更多,上述测试结果可能会有所变化。

更新不同百分比的列存数据,对查询性能的影响

因为列存表的基线数据和增量数据格式不同,所以如果增量数据较多的场景下,在查询时进行增量数据和基线数据归并的过程中,需要对行、列数据的格式进行转换和整合,这里必然会比行存表增加一些额外的开销。

我们通过更新不同百分比的连续列存数据来进行一个测试,因为 l_orderkey 列是均匀分布的,所以我们通过 l_orderkey 的不同范围来改不同比例的数据,例如:

-- l_orderkey 在 1 到 600000000 之间均匀分布
-- 通过 where l_orderkey <= 6000000 更新百分之一的数据
-- 因为 l_orderkey 是主键列,所以更改的数据也可以保证是连续的
update lineitem_column set l_partkey = l_partkey + 1,l_suppkey = l_suppkey - 1,l_quantity = l_quantity + 1,l_extendedprice = l_extendedprice + 1,l_discount = l_discount + 0.01,l_tax = l_tax + 0.01,l_returnflag = lower(l_returnflag), l_linestatus = lower(l_linestatus),l_shipdate = date_add(l_shipdate, interval 1 day),l_commitdate = date_add(l_commitdate, interval 1 day),l_receiptdate = date_add(l_receiptdate, interval 1 day),l_shipinstruct = lower(l_shipinstruct),l_shipmode = lower(l_shipmode),l_comment = upper(l_comment)where l_orderkey <= 6000000;
Query OK, 6001215 rows affected (4 min 2.397 sec)
Rows matched: 6001215  Changed: 6001215  Warnings: 0-- 多次执行走主键查询
select * from lineitem_column where l_orderkey = 7;
select * from lineitem_column where l_orderkey = 600000000;
(0.036 sec)-- 多次执行不走主键查询
select * from lineitem_column where l_suppkey = 825656;
(31.722 sec)

以下是更改列存表不同数据百分比后,走主键查询时间,以及不走主键查询时间:

列存表更新数据百分比(%)走主键查询时间(秒)不走主键查询时间(秒)
00.030.5
10.0332
20.0354
30.0380
50.03126
100.03245
200.03495
300.03733
400.031075
500.041453
600.041636
700.041916
800.042195
900.042468
1000.042793

由上面的表格可以整理出下面这张曲线图,横轴是增量数据占全表数据的百分比,纵轴是不走主键(和索引)的查询时间。列存场景,在增量数据中更新不同百分比的数据(测试过程中一直不进行合并),对查询性能的影响曲线几乎是一条纯线性的直线。

这里需要注意的是:上述测试均是更新表中连续的数据。

如果是随机更新一定比例的非连续数据,性能会比更新连续数据更差。因为在 OceanBase 查询时,数据文件读 IO 的最小单位是 16KB 左右的变长数据块,我们称之为微块(Micro Block),如果修改的数据不连续,可能在修改表中少量的数据时,也能够改动到大量微块中的数据。所以即使我们只修改了 10% 的表数据,可能就已经修改了这张表所涉及到的 100% 的微块数据,这时候的查询性能和修改 100% 的表数据可能就不会有太大差别了。

完成这个测试之后,我们可以得到一个结论:对于列存表来说,如果存在大量更新操作,并且没有及时合并,查询性能是不优的。推荐批量导入数据后发起一次合并,这样可以获得最优的查询性能。

列存适用场景

根据上面的测试结果,咱们可以简单总结出 OceanBase 列存表的两个适用场景:

  • 宽表场景。
    • 当查询只需要扫描宽表的单列或者少数列时,列存表可以省下大量的磁盘 I / O 开销。行存宽表为了解决这个问题,需要再特定列上创建索引,让扫描主表的动作变为扫描列数更少的索引。所以列存表相比行存表,可以省下在特定列上创建索引的存储开销和索引的维护开销。
  • 读多写少的 AP 数仓场景。
    • 在数仓场景下,经常需要执行复杂的分析查询,但这些查询往往只关注表的某些特定列。列存表由于其数据按列存储,能够高效地支持这类 AP 查询,可以减少大量不必要的数据 I / O 开销。
    • OceanBase 为了支持列存表频繁的小事务写入,避免列存表在数据更新操作上性能收到较大影响,让列存表的增量数据保持了行存的格式。因为列存表的基线数据和增量数据格式不同,如果增量数据较多的场景下,在查询时进行增量数据和基线数据归并的过程中,需要对行、列数据的格式进行转换和整合。这一步会比行存表增加额外的开销,并且对列数据的扫描耗时会随着增量数据的增长而线性增长,所以列存表更适合读多写少的场景。

列存基础语法

列存相关的语法在 OceanBase 官网上写的已经很清楚了,这里再 “冗余” 地介绍一遍。

租户级配置项

对于 OLAP 业务,我们推荐默认创建列存表,通过下面的配置项即可实现让租户创建出来的表默认就是列存表:

-- 修改配置项,对当前租户生效
alter system set default_table_store_format = "column";   // 默认创建列存表
alter system set default_table_store_format = "row";      // 默认创建行存表
alter system set default_table_store_format = "compound"; // 默认创建行列冗余存储的表-- 查看配置项的值(默认是 row)
show parameters like 'default_table_store_format';

创建列存表

创建纯列存表的新语法是 with column group,当用户建表时最后指定 with column group(each column) 即代表创建纯列存表:

-- 创建纯列存表
create table t1 (c1 int, c2 int) with column group (each column);-- 创建纯列存分区表
create table t2(pk int,c1 int,c2 int,primary key (pk)
) partition by hash(pk) partitions 4
with column group (each column);

对于部分场景,用户可以忍受一定程度的数据冗余,希望带来 AP / TP 业务场景的兼顾,此时可以增加行存数据的冗余,通过 with column group 语法增加 all columns 关键字即可:

-- 创建行列冗余存储表
create table t2 (c1 int, c2 int) with column group(all columns, each column);

上述语法中的 with column group 后不同选项表示的含义如下:

      • all columns:把所有列聚合在一起成组,看成一个宽列,其实就是:按行进行存储。
      • each column:表中的每一列分别使用列格式来存储。
      • all columns, each column 一起出现,意味着默认创建列存表后同时冗余行存, 每个副本存储两份基线数据。

创建列存索引

和创建列存表类似,也是通过 with column group 指定索引属性。注意这里不要和 “对列存建索引” 混淆,列存索引的含义是:索引表的存储结构是列存格式,相比行存索引,也可以减少存储层的 I / O 开销。

-- 在 t1 表的的 c1, c2 列上创建纯列存的索引
create index idx1 on t1(c1, c2) with column group(each column);-- 在 t1 表的的 c1 列上创建行列冗余存储的索引
create index idx2 on t1(c1) with column group(all columns, each column);-- 在 t1 表的的 c2 列上创建纯列存的索引,额外地存储非索引列 c1 列数据到索引中
alter table t1 add index idx3 (c2) storing(c1) with column group(each column);

上述例子中,通过 storing(c1) 在索引中存储额外列的目的是:优化特定查询的性能,既能让查询避免回主表查询 c1 列的值,也可以减少为 c1 列进行索引排序的代价(idx3 中的 c1 列只是冗余存储在索引中,并不是索引列,索引只需要对 c2 列排序。如果 c1 列为索引列,则需要对 c2、c1 进行排序)。

explain select c1 from t1 order by c2;
+------------------------------------------------------------------------------------+
| Query Plan                                                                         |
+------------------------------------------------------------------------------------+
| ==========================================================                         |
| |ID|OPERATOR              |NAME    |EST.ROWS|EST.TIME(us)|                         |
| ----------------------------------------------------------                         |
| |0 |COLUMN TABLE FULL SCAN|t1(idx3)|1       |5           |                         |
| ==========================================================                         |
| Outputs & filters:                                                                 |
| -------------------------------------                                              |
|   0 - output([t1.c1]), filter(nil), rowset=16                                      |
|       access([t1.c1]), partitions(p0)                                              |
|       is_index_back=false, is_global_index=false,                                  |
|       range_key([t1.c2], [t1.__pk_increment]), range(MIN,MIN ; MAX,MAX)always true |
+------------------------------------------------------------------------------------+

例如上面这条 SQL 的计划,利用 idx3 索引,可以消除对 c2 列的排序(计划中不需要分配 sort 算子),而且因为索引中冗余存储了一个非索引列 c1,所以不需要进行索引回表(is_index_back=false)。

行存表和列存表的相互转化

这几个存储格式相互转化的语法,着实有点儿绕。

行存转列存:

create table t1(c1 int, c2 int);-- 这个语法有点儿奇怪,add 这个关键字给人一种是行存表转行列冗余存储表的错觉……
alter table t1 add column group(each column);

列存转行存:

alter table t1 drop column group(each column);

行存转冗存(为了表达方便,“行列冗余存储” 在下面均被简称为“冗存”):

create table t1(c1 int, c2 int);alter table t1 add column group(all columns, each column);

冗存转行存:

alter table t1 drop column group(all columns, each column);
说明:这里的 drop column group(all columns, each column); 执行后,不用担心没有任何 group 来承载数据,所有列会被放到一个叫做 DEFAUTL COLUMN GROUP 的默认 group 中。DEFAUTL COLUMN GROUP 中的存储格式,由租户级配置项 default_table_store_format 的取值决定,当没有修改过默认值(row)时,执行完成后 t1 就会变成纯行存表。

列存转冗存:

create table t1(c1 int, c2 int) with column group(each column);alter table t1 add column group(all columns);

冗存转列存:

alter table t1 drop column group(all columns);

列存相关 Hint

对于列存行存冗余表,优化器会根据代价选择走行存或者列存扫描。如果用户还是希望手动调优,走列存扫描,可以通过指定 USE_COLUMN_TABLE 这个 Hint 来强制走列存扫描。类似地,通过 NO_USE_COLUMN_TABLE 可以强制表走行存扫描:

explain select /*+ USE_COLUMN_TABLE(tt_column_row) */ * from tt_column_row;
+--------------------------------------------------------------------------------------------------+
| Query Plan                                                                                       |
+--------------------------------------------------------------------------------------------------+
| ===============================================================                                  |
| |ID|OPERATOR              |NAME         |EST.ROWS|EST.TIME(us)|                                  |
| ---------------------------------------------------------------                                  |
| |0 |COLUMN TABLE FULL SCAN|tt_column_row|1       |7           |                                  |
| ===============================================================                                  |
| Outputs & filters:                                                                               |
| -------------------------------------                                                            |
|   0 - output([tt_column_row.c1], [tt_column_row.c2], [tt_column_row.c3]), filter(nil), rowset=16 |
|       access([tt_column_row.c1], [tt_column_row.c2], [tt_column_row.c3]), partitions(p0)         |
|       is_index_back=false, is_global_index=false,                                                |
|       range_key([tt_column_row.c1]), range(MIN ; MAX)always true                                 |
+--------------------------------------------------------------------------------------------------+explain select /*+ NO_USE_COLUMN_TABLE(tt_column_row) */ c2 from tt_column_row;
+------------------------------------------------------------------+
| Query Plan                                                       |
+------------------------------------------------------------------+
| ========================================================         |
| |ID|OPERATOR       |NAME         |EST.ROWS|EST.TIME(us)|         |
| --------------------------------------------------------         |
| |0 |TABLE FULL SCAN|tt_column_row|1       |3           |         |
| ========================================================         |
| Outputs & filters:                                               |
| -------------------------------------                            |
|   0 - output([tt_column_row.c2]), filter(nil), rowset=16         |
|       access([tt_column_row.c2]), partitions(p0)                 |
|       is_index_back=false, is_global_index=false,                |
|       range_key([tt_column_row.c1]), range(MIN ; MAX)always true |
+------------------------------------------------------------------+

如何通过计划判断是否走了列存?如上面的计划所示,扫描走行存时,explain 中显示的是 TABLE FULL SCAN,走到列存时,显示的是 COLUMN TABLE FULL SCAN。

列存使用建议

我们对列存进行了一些测试,并了解了列存的基础语法,最后简单总结下对 OceanBase 列存功能的使用建议。

  1. 对于新创建的 OceanBase 4.3.0 及以上版本的集群,并且应用场景是 OLAP 数据仓库。建议通过修改组户级配置项 default_table_store_format,让创建表时的默认存储格式为列存,该配置项的默认值是 row。
  2. 对于从低版本升级到 OceanBase 4.3.0 及以上版本的集群,并且希望利用新版本提供的列存能力来优化旧的行存表,OceanBase 提供两种方式:
    • 创建列存索引。
      • 优:在部分列上创建列存索引,适用于宽表场景。并且建列存索引是 Online DDL,不影响业务。
      • 劣:增量数据需要在原表和索引表中写两份,写入增量数据时会占用更多内存和磁盘空间。
    • 通过 alter table 修改原表的存储格式。
      • 优:对写友好,增量数据只用在原表中写一份(增量数据都是行存格式)。
      • 劣:属于 Offline DDL,执行 DDL 期间会锁表,无法更新数据。
  3. 行列冗余模式仅适用于 HTAP 场景,优化器会根据代价估算,自动选择对列数据的访问使用行存还是列存。纯 AP 场景,建议使用纯列存。
  4. 对于列存表来说,如果存在大量更新操作,并且没有及时合并,那么查询性能是不优的。推荐批量导入数据后发起一次合并,以获得最优的查询性能(列存表合并速度会比行存表慢一些)。合并操作需要在租户内执行 alter system major freeze; 然后在系统租户执行 select STATUS from CDB_OB_MAJOR_COMPACTION where TENANT_ID = 租户ID; 判断合并是否完成,当 STATUS 变为 IDLE 即表示合并完成。当然,也可以通过 OCP 白屏化工具完成合并操作。
  5. 合并后,推荐做一次统计信息收集。收集统计信息方法如下:
    • 在业务租户一键对所有表收集统计信息,启动16个线程并发收集

CALL DBMS_STATS.GATHER_SCHEMA_STATS ('db', granularity=>'auto', degree=>16); 

    • 观测统计信息进度可以通过视图 GV$OB_OPT_STAT_GATHER_MONITOR。

6. 可以通过旁路导入来进行批量数据的导入,使用这种方式导入数据的表无需做合并,就能达到最优列存扫描性能。支持全量旁路导入的工具包括 obloader、原生 load data 命令。

7. 对于数据量较大的表,Cold Run 的性能一般会弱于 Hot Run。

8. 对于非大宽表场景,不使用列存也可能达到和列存相当的性能。这得益于 OceanBase 行存版本中微块级别的行列混合存储架构。

9. 还有哪些可以进一步提升列存表 AP 性能的方法?在实践中的经验分享:

    • 如果用户或者业务可以接受的话,建表时候字符集不要使用 utf8mb4,而是使用 binary,可以提升性能。例如:
create table t1(c1 int, c2 int) CHARSET=binary with column group (each column);
    • 当 charset 需要是 utf8mb4 时,如果用户或者业务可以接受的话,在创建 mysql 租户时可以把 collation 指定为 utf8mb4_bin,如:locality = 'F@z1', collate = utf8mb4_bin。或者在建表时指定 utf8mb4_bin 字符集,建表时带上:CHARSET = utf8mb4 collate=utf8mb4_bin。
    • 列存 PoC 推荐配置:
-- 设置 collation 为 utf8mb4_bin
set global collation_connection = utf8mb4_bin;
set global collation_server = utf8mb4_bin;set global ob_query_timeout=10000000000;
set global ob_trx_timeout=100000000000;
alter system set_tp tp_no = 2100, error_code = 4001, frequency = 1;
alter system set _trace_control_info=''
alter system set _rowsets_enabled=true;
alter system set _bloom_filter_enabled=1;
alter system set _px_message_compression=1;
set global _nlj_batching_enabled=true;
set global ob_sql_work_area_percentage=70;
set global max_allowed_packet=67108864;
set global parallel_servers_target=1000; -- 建议是 cpu 的 10 倍
set global parallel_degree_policy = auto;
set global parallel_min_scan_time_threshold = 10;
set global parallel_degree_limit = 0; alter system set _pushdown_storage_level = 4;
alter system set _enable_skip_index=true;
alter system set _enable_column_store=true;
alter system set compaction_low_thread_score = cpu_count;
alter system set compaction_mid_thread_score = cpu_count;

未来展望

在 V4.3.x 后续版本,还会支持列存副本的形态,以减少 TP、AP 混合负载情况下,行存列存冗余带来的存储开销。

如下图:只读列存副本可以部署在独立的 Zone 里,不仅可以做到 TP 业务和 AP 业务的物理资源隔离,列存合并和行存合并也可以做到互不影响,适合读写并发都高的 HTAP 混合负载场景。

这篇博客到此为止,都是偏用户向的内容,对于大多数 OceanBase 列存版本的使用者来说,了解完这些内容,可以有很多参考了。

参考

  • OceanBase 官网文档《列存》
  • OceanBase 官网文档《创建索引》
  • OceanBase 官网文档《更改表》
  • 列存研发同学寒晖的博客《OceanBase v4.3 特性解读:查询性能提升之利器列存储引擎》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/20073.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【计算机毕业设计】谷物识别系统Python+人工智能深度学习+TensorFlow+卷积算法网络模型+图像识别

谷物识别系统&#xff0c;本系统使用Python作为主要编程语言&#xff0c;通过TensorFlow搭建ResNet50卷积神经算法网络模型&#xff0c;通过对11种谷物图片数据集&#xff08;‘大米’, ‘小米’, ‘燕麦’, ‘玉米渣’, ‘红豆’, ‘绿豆’, ‘花生仁’, ‘荞麦’, ‘黄豆’, …

2023年亚太杯A题:果园采摘机器人的图像识别,一二题

问题一&#xff1a;基于附件1中提供的可收获苹果的图像数据集&#xff0c;提取图像特征&#xff0c;建立数学模型&#xff0c;计算每幅图像中的苹果的数量&#xff0c;并绘制附件1中所有苹果的分布直方图。 对于自动采摘机器人&#xff0c;首要的能力就是识别出苹果对象&#…

数字信号处理实验四:IIR数字滤波器设计及软件实现

一、实验目的 1. 掌握MATLAB中进行IIR模拟滤波器的设计的相关函数的应用&#xff1b; 2. 掌握MATLAB的工具箱中提供的常用IIR数字滤波器的设计函数的应用&#xff1b; 3.掌握MATLAB的工具箱中提供的模拟滤波器转数字滤波器的相关的设计函数的应用。 二、实验内容 本实验为…

秋招突击——算法打卡——5/30——复习{最大上升子序列的和、面试算法缺陷补充}——新做:{回文数+补充 自定义Stoi实现、正则表达式匹配}

文章目录 复习导弹拦截——最大上升子序列和推理过程实现代码补充昨日面试 新作回文数实现代码 字符串转整数正则表达式匹配个人实现思路分析实现代码如下 参考做法思路分析实现代码 总结 复习 导弹拦截——最大上升子序列和 同样类型题目链接&#xff1a;导弹拦截重做这道题…

力扣刷题--485. 最大连续 1 的个数【简单】

题目描述 给定一个二进制数组 nums &#xff0c; 计算其中最大连续 1 的个数。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,0,1,1,1] 输出&#xff1a;3 解释&#xff1a;开头的两位和最后的三位都是连续 1 &#xff0c;所以最大连续 1 的个数是 3. 示例 2: 输入&…

WindowManager相关容器类

窗口中容器类介绍&#xff1a; 本节内容较多&#xff0c;建议结合前面的内容一起阅读&#xff1a; 1、addWindow的宏观概念 2、WindowManager#addView_1 3、WindowManager#addView_2 1&#xff09;、WindowContainer&#xff1a; class WindowContainer<E extends WindowC…

算法(一)递归

文章目录 递归的概念递归三要素递归demo打印100次“hello word”斐波那契数列 递归的概念 递归算法是一种直接或者间接调用自身函数或者方法的算法。 递归三要素 递归条件结束 因为递归是循环调用自身&#xff0c;因此就必须要有结束条件&#xff0c;或者就会OOM。 函数的功…

低代码开发系统是什么?它有那些部分组成?

低代码开发系统是什么&#xff1f;它有那些部分组成&#xff1f; 一、引言 在当今快速变化的商业环境中&#xff0c;企业对于快速响应市场需求、降低开发成本和提高开发效率的需求日益增强。低代码开发系统&#xff08;Low-Code Development Platform&#xff09;应运而生&am…

安卓启动 性能提升 20-30% ,基准配置 入门教程

1.先从官方下载demohttps://github.com/android/codelab-android-performance/archive/refs/heads/main.zip 2.先用Android studio打开里面的baseline-profiles项目 3.运行一遍app&#xff0c;这里建议用模拟器&#xff0c;&#xff08;Pixel 6 API 34&#xff09;设备运行&a…

思科防火墙 网线连接的端口还是down 已配置 端口还是down

环境&#xff1a; 思科防火墙fpr-2100 isco Firepower 2100 系列防火墙是思科系统&#xff08;Cisco Systems&#xff09;推出的一款中端网络安全和防火墙设备。这一系列的产品主要针对中到大型企业的需求&#xff0c;提供高性能的威胁防护和网络流量管理功能。 问题描述&am…

Java微服务智慧工地可视化SaaS云解决方案源码

智慧工地是指运用信息化手段&#xff0c;围绕施工过程管理&#xff0c;建立互联协同、智能生产、科学管理的施工项目信息化生态圈&#xff0c;并将此数据在虚拟现实环境下与物联网采集到的工程信息进行数据挖掘分析&#xff0c;提供过程趋势预测及专家预案&#xff0c;实现工程…

排序算法之直接选择排序【图文详解】

P. S.&#xff1a;以下代码均在VS2019环境下测试&#xff0c;不代表所有编译器均可通过。 P. S.&#xff1a;测试代码均未展示头文件stdio.h的声明&#xff0c;使用时请自行添加。 博主主页&#xff1a;LiUEEEEE                        …

基于tensorflow和NasNet的皮肤癌分类项目

数据来源 https://challenge.isic-archive.com/data/#2019 数据划分 写了个脚本划分 for line in open(ISIC/labels.csv).readlines()[1:]:split_line line.split(,)img_file split_line[0]benign_malign split_line[1]# 0.8 for train, 0.1 for test, 0.1 for validati…

快蜗牛OZON数据分析,OZON快蜗牛数据

在当今电商行业蓬勃发展的背景下&#xff0c;OZON作为俄罗斯及东欧市场的重要电商平台&#xff0c;其数据背后蕴藏着巨大的商业价值。快蜗牛&#xff0c;作为专注于OZON平台的数据分析工具&#xff0c;为卖家提供了深入的市场洞察和策略指导。接下来看看快蜗牛OZON数据分析&…

线上 | OpenSergo - [规范]

INDEX 1 参考资料2 OpenSergo 与 Sentinel 关系3 规范体系3.1 服务元数据ReportMetadataRequest 信息![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/ffba569841ae4668b4cff74e4d41d21f.png)##### ReportMetadataReply 信息![在这里插入图片描述](https://img-blog…

BurpSuite2024.5

1 工具介绍 本版本更新介绍 此版本引入了Burp Scanner对WebSockets的支持、对记录登录编辑器的改进、WebSocket 匹配和替换规则以及许多性能改进。 Burp Scanner 支持 WebSockets 我们已更新内部代理的配置以允许 WebSocket 流量。这使 Burp Scanner 现在可以抓取依赖 WebSo…

基于大模型的智慧零售教育科研平台——技术方案

一、概述 1.1背景 随着数字经济的快速发展和全社会数字化水平的升级&#xff0c;人工智能的积极作用越来越凸显&#xff0c;人工智能与各个行业的深度融合已成为促进传统产业转型升级的重要方式之一。ChatGPT的出现掀起了又一波人工智能发展热潮&#xff0c;人工智能行业发展势…

Linux sudo用户权限管理小实验001

Linux sudo用户权限管理和审计-初步 1、设置历史指令的保存数量 默认history指令可以查看当前用户执行的1000条历史命令的条目 2、使用export指令设置HISTSIZE环境变量的数量为999999条。 3、基于date指令&#xff0c;输出日期和时间 4、设置linux系统history相关变量&…

预编码算法(个人总结)

引言 预编码算法是现代无线通信系统中的关键技术&#xff0c;特别是在多输入多输出&#xff08;MIMO&#xff09;系统中。它们通过在发送端对信号进行处理&#xff0c;减少干扰并提高信道容量。这种技术广泛应用于5G、Wi-Fi和卫星通信系统中。本教程将详细介绍预编码算法的背景…

FV悬浮球,安卓真正小而美的神器,满足你的一切需求。

如果你问安卓最强软件有哪些&#xff0c;不同的人可能会有不同的答案&#xff0c;但如果是问我&#xff0c;那我的答案中一定会有他。 FV悬浮球 他是ES文件浏览器&#xff0c;原作者的新作品&#xff0c;经过几年的开发&#xff0c;拥有了超过400项功能&#xff0c;但大小只有…