OceanBase 4.3 特性解析:列存技术

在涉及大规模数据的复杂分析或即时查询时,列式存储是支撑业务负载的关键技术之一。相较于传统的行式存储,列式存储采用了不同的数据文件组织方式,它将表中的数据以列为单位进行物理排列。这种存储模式允许在分析过程中,查询计算仅需针对所需的列数据进行扫描,从而避免了不必要的整行扫描,显著降低了IO和内存等资源的消耗,进而提升了计算效率。此外,列式存储天然具备更佳的数据压缩优势,能够实现较高的压缩比,有效节约了存储空间,并降低了网络传输带宽的占用。

常见的列存存储引擎在实现上往往假设不会有大量随机更新, 尽量保证列存组织数据是静态的。当真正伴随大量数据随机更新时,也会不可避免的存在系统性能问题。OceanBase LSM-Tree 架构可以将基线数据和增量数据分别处理,正好可以解决这一场景问题。因此 OceanBase 4.3 版本基于当前架构基础进行扩展,正式推出列存引擎,在一个架构、一个数据库上,实现了列存和行存数据存储一体化,兼顾 TP 和 AP 查询性能。

为了让有分析诉求的用户顺畅使用新版本,围绕列存引擎,从优化器到执行器、从 DDL 到事务处理等多模块都进行了适配优化。包括基于列存的新的代价模型和向量化引擎,查询下压功能的扩展和增强,Skip Index,新的列式编码算法,自适应 Compaction 等。本文将深入探讨 OceanBase 4.3 版本带来的列存能力、应用场景,以及用户关心的未来发展规划

一、列存整体架构

OceanBase 作为原生分布式数据库,默认情况下会为用户数据创建多个副本。为了充分利用多副本的优势,为用户提供数据强校验和数据迁移重用等增强体验,OceanBase 自研的 LSM-Tree 存储引擎做了深度优化:

○  基线数据:相较于业内常见的 LSM-Tree 实现逻辑,OceanBase 提出了"每日合并"的概念。用户可定期或根据操作选择一个全局版本号,所有副本的租户数据将在这个版本上进行一轮 Major Compaction,生成这个版本的基线数据。所有副本在同一版本下的基线数据完全一致,物理上保持一致。

○  增量数据:相对于基线数据,增量数据是指在最新版本的基线数据之后写入的数据。增量数据可以是刚写入Memtable的内存数据,也可以是已经转储为SSTable 的磁盘数据。增量数据在每个副本中独立维护,不保证一致性,并且包含了所有多版本的数据。

基于列存应用场景随机更新量可控的背景,OceanBase 4.3 结合自身基线数据和增量数据的特质,提出了一套对上层透明的列存实现方式:基线数据存储为列存模式,增量数据保持行存,确保用户所有 DML 操作不受影响,上下游同步无缝接入,列存表数据仍然可以像行存表一样进行所有事务操作。列存模式下每列数据存储为一个独立 SSTable,所有列的 SSTable 组合成为一个虚拟 SSTable 作为用户的列存基线数据。同时,用户可根据实际业务诉求在建表环节指定设置,基线数据可以支持行存、列存、行存列存冗余三种模式,提供更好的灵活性。

1716796000

OceanBase 4.3 版本中不仅在存储引擎中实现了列存模式,更从优化器、执行器以等多维度进行列存的适配优化。用户在迁移到列存模式后基本上不会感受到业务变化,能够像使用行存一样享受到列存带来的性能优势。列存引擎的全面优化,也使得 OceanBase 真正实现了 TP & AP 一体化,实现了一套引擎、一套代码支持不同类型业务的目标,打造更加完善的 HTAP 混合负载实时分析能力。

二、OceanBase 实现列存,有哪些天然优势

(一)成熟的 LSM-Tree 引擎

与传统数据库相比,OceanBase 拥有天然的 Delta Store,非常适合实现列存。基于 LSM-Tree 存储引擎的支持,OceanBase 列存不仅支持完整的事务,而且基础算子的性能不弱于传统的 TP 数据库。在列存上,完整的事务支持使得 OceanBase 在更新方面具有天然优势,所有事物语义和多样事物的管理对用户来说完全透明的,用户可以轻松切换到列存模式,将列存数据库当成行存数据库使用,对业务完全透明,不需要做任何改动。

(二)完善的执行引擎

OceanBase 不仅拥有完整的执行引擎,还具备通用的优化器是通用的。在行存模式下,OceanBase 已经实现向量化存储引擎的无缝对接,无需任何修改即可支持向量化执行。此外,OceanBase 实现一套优化器的代码在上层对行存和列存进行不同代价的估算,使得用户的 SQL 可以自动选择行存或列存。

(三)灵活的原生分布式

OceanBase 天然支持分布式并行查询引擎,未来还可以轻松扩展到列存异构副本。列存异构副本的优势体现在用户需要完全硬隔离的应用场景中,未来的OceanBase 版本将新增这一功能。

综上所述,OceanBase 凭借其天然优势推动了 4.3 版本中列存功能的实现。引入列存储引擎后,OceanBase 整体架构在外部表现上完全不变,并且从架构层面支持了列存相关的三种模式:

○  基线列存 +增量行存:基线数据采用列存方式存储,增量数据采用行存方式存储。

○  灵活的行存/列存索引:可以对行存表建立列存索引,也可以对列存表建立行存索引,还可以对两者进行任意组合。由于所有列存表和索引的底层存储结构是统一的,因此 OceanBase 可以自动支持列存和行存的索引。

○  列存副本:OceanBase 正在研发的列存副本功能。得益于原生分布式能力,只需对模式或表做部分修改,即可以通过 Compaction 将新增的只读副本转换为列存存储模式。

三、列存使用方法

(一)默认创建列存表

对于 OLAP 业务需求,我们推荐默认创建列存表。如何确保租户创建出来的表默认为列存表?只通过下面的配置项即可实现:

alter system set default_table_store_format = "column";

随后我们创建的表格没有指定 column group 时,默认创建为列存表。

OceanBase(root@test)>create table  t1 (c1 int primary key, c2 int ,c3 int);
Query OK,0 rows affected (0.301 sec)

OceanBase(root@test)>show create table t1;

CREATE TABLE `t1` (
  `c1` int(11) NOT NULL,
  `c2` int(11) DEFAULT NULL,
  `c3` int(11) DEFAULT NULL,
  PRIMARY KEY (`c1`)
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 1 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0
WITH COLUMN GROUP(each column)

1 row in set (0.101 sec)

(二)指定创建列存表

为了方便用户创建列存表,列存引入新的语法 with column group,当用户建表时最后指定 `with column group(each column)` ,即表示创建列存表。

OceanBase(root@test)>create table  tt_column_store (c1 int primary key, c2 int ,c3 int) with column group (each column);
Query OK,0 rows affected (0.308 sec)

OceanBase(root@test)>show create table tt_column_store;

CREATE TABLE `tt_column_store` (
  `c1` int(11) NOT NULL,
  `c2` int(11) DEFAULT NULL,
  `c3` int(11) DEFAULT NULL,
  PRIMARY KEY (`c1`)
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 1 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0 WITH COLUMN GROUP(each column)

1 row in set (0.108 sec)

(三)指定创建列存行存冗余表

在某些场景下,用户可以容忍一定程度的数据冗余,以满足 AP/TP 业务场景的双重需求。此时,可以增加行存数据的冗余,通过 `with column group` 语法增加指定 `all columns` 即可实现。

create table  tt_column_row (c1 int primary key, c2 int , c3 int) with column group (all columns, each column);
Query OK, 0 rows affected (0.252 sec)

OceanBase(root@test)>show create table tt_column_row;
CREATE TABLE `tt_column_row` (
  `c1` int(11) NOT NULL, 
  `c2` int(11) DEFAULT NULL, 
  `c3` int(11) DEFAULT NULL, 
  PRIMARY KEY (`c1`)
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 1 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0 WITH COLUMN GROUP(all columns, each column)

1 row in set (0.075 sec)

(四)列存扫描

如何查看是否列存扫描计划?计划展示上新增 COLUMN TABLE FULL SCAN,描述列存表的范围扫描。

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

计划展示上新增 COLUMN TABLE GET,描述列存表上的指定主键的 get 操作。

OceanBase(root@test)>explain select * from tt_column_store where c1 = 1;
+--------------------------------------------------------------------------------------------------------+
| Query Plan                                                                                             |
+--------------------------------------------------------------------------------------------------------+
| ===========================================================                                            |
| |ID|OPERATOR        |NAME           |EST.ROWS|EST.TIME(us)|                                            |
| -----------------------------------------------------------                                            |
| |0 |COLUMN TABLE GET|tt_column_store|1       |14          |                                            |
| ===========================================================                                            |
| Outputs & filters:                                                                                     |
| -------------------------------------                                                                  |
|   0 - output([tt_column_store.c1], [tt_column_store.c2], [tt_column_store.c3]), filter(nil), rowset=16 |
|       access([tt_column_store.c1], [tt_column_store.c2], [tt_column_store.c3]), partitions(p0)         |
|       is_index_back=false, is_global_index=false,                                                      |
|       range_key([tt_column_store.c1]), range[1 ; 1],                                                   |
|       range_cond([tt_column_store.c1 = 1])                                                             |
+--------------------------------------------------------------------------------------------------------+
12 rows in set (0.051 sec)

如何通过 Hint 指定列存行存冗余表走列存扫描?对于列存行存冗余表,优化器会根据代价选择走行存或者列存扫描,如简单场景做全表扫描,会默认使用行存生成计划。

OceanBase(root@test)>explain select * 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.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                                 |
+--------------------------------------------------------------------------------------------------+

如果用户希望通过手动调优走列存扫描,可以通过 hint USE_COLUMN_TABLE 来强制 tt_column_row 表走列存扫描。

OceanBase(root@test)>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                                 |
+--------------------------------------------------------------------------------------------------+

类似的方式,通过 Hint NO_USE_COLUMN_TABLE 可以强制表不进行列存扫描。

OceanBase(root@test)>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 |
+------------------------------------------------------------------+
11 rows in set (0.053 sec)

四、未来展望

OceanBase 4.3 列存的引入,为用户的数据分析以及实时分析场景提供了新的选择。未来,OceanBase 列存将持续演进,为用户带来更加丰富的 feature、更强劲的性能以及更灵活的部署模式。

第一,更丰富的功能。目前,我们支持纯列存储引擎,未来将实现可自定义的灵活列组组织支持,满足不同场景的分析需求。此外,我们计划将增量旁路导入功能进一步增强,帮助用户实现高效的数据导入,缩短数据分析准备时间。

第二,更好的性能。增强 Skip Index 的支持,使其能够更好地满足用户的查询需求。此外,我们计划实现格式一体化,目前存储的格式多样化,未来将实现存储格式与 SQL 向量化引擎的紧密结合,使得在执行 SQL 计算时,系统能够识别不同的存储格式,从而帮助用户节省更多的数据转换开销。

第三,更灵活的部署模式。在未来的版本中,我们将支持 OLAP 所需的异构副本,以满足用户对强依赖异构副本的需求。此外,未来还将支持存算分离模式,使得所有用户的 AP 数据库都能够以更低的成本享受存储与计算的分离。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vitis HLS 学习笔记--接口存储器布局模型

目录 1. 简介 2. 详解 2.1 数据对齐 2.2 数据结构填充 3. 总结 1. 简介 软件开发者写的程序会在 CPU 处理器上运行,而硬件开发者设计的“内核”则会在 FPGA 上运行。这两部分需要通过一个精心设计的接口来沟通,就像两个人用对讲机来交流一样。为了…

GDPU unity游戏开发 寻路与导航

学会寻路,出门在外,身份不是他给的,他做不了你一直的导航。 角色寻路 角色控制器替换为普通的角色控制器,给实验九的地形增加NavMesh Surface组件,然后给角色增加NavMesh Agent组件,并选择合适的参数。通过…

mac安装brew遇到的一些问题

mac终端执行命令/bin/bash -c “$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)”,无法建立连接或连接超时错误,错误信息如下: curl: (28) Failed to connect to raw.githubusercontent.com port 443 af…

高性能MySQL(第3版)电子书笔记

Mysql官方文档:https://dev.mysql.com/doc/refman/5.7/en/ 高性能MySQL(第3版):百度网盘,基于Mysql5.1和Mysql5.5 本机版本 mysql> select version(); ------------ | version() | ------------ | 5.7.32-log |…

Numba 的 CUDA 示例(4/4):原子和互斥

本教程为 Numba CUDA 示例 第 4 部分。 本系列第 4 部分总结了使用 Python 从头开始学习 CUDA 编程的旅程 介绍 在本系列的前三部分(第 1 部分,第 2 部分,第 3 部分)中,我们介绍了 CUDA 开发的大部分基础知识&#xf…

第十一届蓝桥杯C++青少年组中/高级组国赛2020年10月真题解析

一、单选题 第1题 在数组中,数组名表示( ). A:数组第1个元素的首地址 B:数组第2个元素的首地址 C:数组所有元素的首地址, D:数组最后1个元素的首地址 答案:A 数组名是一个地址,指向第一个元素 第2题 …

locale本地化库学习

std::locale 类型的对象(本地环境对象)是不可变刻面的一个不可变索引集。C 输入/输出库的每个流对象都与一个 std::locale 对象关联,并用它的各刻面来分析及格式化所有数据。另外,每个 std::basic_regex 对象也都与一个本地环境对…

如何用群晖当异地组网服务器?

在当今信息化时代,远程通信成为了企业和个人之间不可或缺的一部分。特别是对于跨地区的通信需求,一个可靠的异地组网服务器是必不可少的。而群晖(Synology)作为一款功能强大的网络存储设备,可以被用作办公室或家庭的异…

【小白专用24.6.8】C#Lambda表达式

Lambda表达式可以采用以下任意一种形式的表达式&#xff1a; 1.表达式Lambda&#xff0c;表达式为其主体&#xff1a; (input-parameters) > expression 1 2.语句Lambda&#xff0c;语句块作为其主体&#xff1a; (input-parameters) > {<sequence-of-statements>…

★pwn 24.04环境搭建保姆级教程★

★pwn 24.04环境搭建保姆级教程★ &#x1f338;前言&#x1f33a;Ubuntu 24.04虚拟机&#x1f337;VM&#x1f337;Ubuntu 24.04镜像 &#x1f33a;工具&#x1f337;可能出现的git clone错误&#x1f337;复制粘贴问题&#x1f337;攻击&#x1f337;编题 &#x1f33a;美化&…

C++的STL 中 set.map multiset.multimap 学习使用详细讲解(含配套OJ题练习使用详细解答)

目录 一、set 1.set的介绍 2.set的使用 2.1 set的模板参数列表 2.2 set的构造 2.3 set的迭代器 2.4 set的容量 2.5 set的修改操作 2.6 set的使用举例 二、map 1.map的介绍 2.map的使用 2.1 map的模板参数说明 2.2 map的构造 2.3 map的迭代器 2.4 map的容量与元…

【数据结构】栈的应用

目录 0 引言 1 栈在括号匹配中的应用 2 栈在表达式求值中的应用 2.1 算数表达式 2.2 中缀表达式转后缀表达式 2.3 后缀表达式求值 3 栈在递归中的应用 3.1 栈在函数调用中的作用 3.2 栈在函数调用中的工作原理 4 总结 0 引言 栈&#xff08;Stack&#xff09;是一…

【网络编程开发】8.TCP连接管理与UDP协议 9.IP协议与ethernet协议

8.TCP连接管理与UDP协议 三次握手 三次握手的过程在TCP/IP网络通信中起着至关重要的作用&#xff0c;它不仅确保了数据的可靠传输&#xff0c;还为两端的数据传输提供了稳定的连接初始化过程。这一过程涉及到几个关键步骤&#xff0c;每个步骤都有其特定的目的和功能。 步骤&…

LSTM卷土重来之Vision-LSTM横空出世!!

在Transformer诞生三年后&#xff0c;谷歌将这一自然语言处理的重要研究扩展到了视觉领域&#xff0c;也就是Vision Transformer。 论文链接&#xff1a;https://arxiv.org/abs/2406.04303 项目链接: https://nx-ai.github.io/vision-lstm/ GPT-4o深夜发布&#xff01;Plus免…

MySQL—多表查询—联合查询

一、引言 之前学习了连接查询。现在学习联合查询。 union&#xff1a;联合、联盟 对于union查询&#xff0c;就是把多次查询的结果合并起来&#xff0c;形成一个新的查询结果集 涉及到两个关键字&#xff1a;union 和 union all 注意&#xff1a; union 会把上面两个SQL查询…

Python魔法之旅-魔法方法(23)

目录 一、概述 1、定义 2、作用 二、应用场景 1、构造和析构 2、操作符重载 3、字符串和表示 4、容器管理 5、可调用对象 6、上下文管理 7、属性访问和描述符 8、迭代器和生成器 9、数值类型 10、复制和序列化 11、自定义元类行为 12、自定义类行为 13、类型检…

MySQL—多表查询—子查询(介绍)

一、引言 上一篇博客学习完联合查询。 这篇开始&#xff0c;就来到多表查询的最后一种形式语法块——子查询。 &#xff08;1&#xff09;概念 SQL 语句中嵌套 SELECT 语句&#xff0c;那么内部的 select 称为嵌套查询&#xff0c;又称子查询。 表现形式 注意&#xff1a; …

零基础入门学用Arduino 第一部分(二)

重要的内容写在前面&#xff1a; 该系列是以up主太极创客的零基础入门学用Arduino教程为基础制作的学习笔记。个人把这个教程学完之后&#xff0c;整体感觉是很好的&#xff0c;如果有条件的可以先学习一些相关课程&#xff0c;学起来会更加轻松&#xff0c;相关课程有数字电路…

转型AI产品经理(4):“认知负荷”如何应用在Chatbot产品

认知负荷理论主要探讨在学习过程中&#xff0c;人脑处理信息的有限容量以及如何优化信息的呈现方式以促进学习。认知负荷定律认为&#xff0c;学习者的工作记忆容量是有限的&#xff0c;而不同类型的认知任务会对工作记忆产生不同程度的负荷&#xff0c;从而影响学习效果。以下…

项目总结报告(Word模板)

2 项目工作成果 2.1 交付给用户的产品 2.2 交付给研发中心的产品 2.2.1 代码部分 2.2.2 文档部分 2.3 需求完成情况与功能及性能符合性统计 2.3.1 需求完成情况统计 2.3.2 功能符合性分析 2.3.3 性能符合性分析 3 项目工作分析 3.1 项目计划与进度实施分析 3.1.1 开发进度 3.1.…