首批通过分布式安全可靠测评,为关键业务系统打造
OBKV-Table 的数据模型
更新时间:2026-02-09 13:57:46
本文档介绍了 OBKV-Table 的数据模型。OBKV-Table 提供表模型接口,在概念定义上和 SQL 接近,比如也有行列的概念。
表组织方式
表组织方式是 OBKV-Table 的底层存储结构,决定了数据在磁盘上的存储方式。OBKV-Table 支持两种表组织方式:
- 索引组织表(Index Organized Table)
- 堆组织表(Heap Organized Table)。OBKV 客户端从 V2.1.1 版本开始支持堆组织表。
创建表时指定表组织形式的语法是在 CREATE TABLE 语句后增加表选项 ORGANIZATION:
CREATE TABLE table_name
column_definition
ORGANIZATION [=] {INDEX | HEAP};
其中,ORGANIZATION 参数用来指定表中数据行的存储顺序,取值如下:
- INDEX:表示表模型为聚集索引表模型,即索引组织表。
- HEAP:表示表模型为非聚集索引表模型,即堆组织表。
如果不指定 ORGANIZATION 选项,则该选项取值与配置项 default_table_organization 的值相同。
索引组织表
索引组织表是 OBKV-Table 的默认表组织方式,适用于大多数场景。索引组织表的索引是基于主键的,因此可以快速定位到数据。
例如,创建一个索引组织表:
CREATE TABLE IF NOT EXISTS `employees` (
`employeeId` int NOT NULL,
`name` varchar(100) NOT NULL,
`department` varchar(50),
`position` varchar(50),
`workYear` int,
PRIMARY KEY (`employeeId`)
);
堆组织表
堆组织表是 OBKV-Table 的另一种表组织方式,主要适用于对写入性能和存储效率有较高要求的场景,尤其是在需要对数据进行分区存储时。堆组织表在写入流程中无需进行主键冲突校验,这有助于提升写入性能并减少额外的 I/O 消耗。同时,由于其不强制数据排序的特性,堆组织表能显著减少转储合并阶段的 I/O 资源占用。因此,它特别适合 LOB 等对写入吞吐量和存储成本敏感的场景。
例如,创建一个堆组织表:
CREATE TABLE test (
c1 bigint,
c2 varchar(20),
c3 bigint,
UNIQUE INDEX idx_unique_c1 (c1) -- 创建唯一索引可以加速查询
) ORGANIZATION = HEAP PARTITION BY KEY(c1) PARTITIONS 97; -- 通过 ORGANIZATION 参数指定堆表
使用限制如下:
- 堆组织表的本地唯一索引键(Unique Key Local)或堆组织表主键表的主键(Primary Key),必须包含所有分区列(Partition Key)。
- 堆组织表创建索引时,索引名不能和主键第一列列名相同。
- OceanBase 数据库堆组织表暂不支持以下 DDL 操作:
- 主键约束操作
- 修改列为主键
- 主键列类型长度由大改小
- 加主键列
- 修改主键为自增列
- 删主键列
- 分区分裂
- 堆组织表不支持的接口详见 OBKV-Table 操作类型。
表识别标志
注意
OBKV-Table 从 V4.4.1 开始支持此功能。强烈建议在建表时添加 kv_attributes 设置,以便系统能够准确识别 OBKV 表类型。
在创建 OBKV-Table 表时,我们建议添加 kv_attributes='{"Table": {}}' 设置,以便正确识别为 OBKV-Table 表。示例如下:
CREATE TABLE `t1` (
`c1` bigint NOT NULL,
`c1sk` varchar(20) NOT NULL,
`c2` varbinary(1024) DEFAULT NULL,
`c3` varchar(20) DEFAULT NULL,
`c4` bigint DEFAULT NULL,
PRIMARY KEY(`c1`, `c1sk`))
-- '{"Table": {}}' 该形式不可变更,Table 大小写不敏感
kv_attributes = '{"Table": {}}'
partition by range columns (`c1`) (
PARTITION p0 VALUES LESS THAN (300),
PARTITION p1 VALUES LESS THAN (1000),
PARTITION p2 VALUES LESS THAN MAXVALUE);
表结构
表结构决定了数据在表中的存储方式。OBKV-Table 的表结构主要由以下几个部分组成:
- ColumnValue
- Row
ColumnValue
ColumnValue 实际上代表着一对键值,也就是 列名 + 该列的值 的组合,使用上主要为初始化赋值和获取 ColumnValue 对象值。
ColumnValue empCol = new ColumnValue("department", "oceanbase_product");
Row
Row 是多对的 ColumnValue,里面存储着一行的值,可以有一对或者是多对的 ColumnValue。
如下示例为构造一行数据, 为了简化编码, OBKV-Table 提供了多种编码方式。
方式 1:
Row emp = row().add(colVal("employeeId", 12345))
.add(colVal("name", "zhangsan"))
.add(colVal("department", "oceanbase_dev"))
.add(colVal("position", "engineer"));
.add(colVal("workYear", 5));
方式 2:
ColumnValue id = colVal("employeeId", 12345);
ColumnValue name = colVal("name", "zhangsan");
ColumnValue dep = colVal("department", "oceanbase_dev");
ColumnValue pos = colVal("position", "engineer");
ColumnValue workYear = colVal("workYear", 5);
Row emp = new Row();
emp.add(id, name, dep, pos, workYear);
说明
OBKV-Table 提供了一些接口级别的语法糖(Syntactic sugar),比如在 MutationFactory 中提供了一些静态方法 ColumnValue colVal(String columnName, Object value) 以及 Row row(ColumnValue... columnValue),来做对象的创建等工作。在类似 Row 这种复合对象的构建中,可以减少部分编码工作量。
Rowkey:
- 主键在 OBKV-Table 中非常重要,它可以保证每条记录的唯一性。
- 如果主键是由不同的字段组成,纯粹的 KV 数据库依赖用户把不同字段编码在一起,而 OBKV-Table 支持用户直接按照字段填写 Rowkey。
查询
OBKV-Table 的查询方式主要由以下几个部分组成:
- ScanRange
- Filter
ScanRange
ScanRange 一般是主键或者主键的前缀,定义的接口如下:
TableQuery addScanRange(Object[] starts, Object[] ends);
ScanRange
ScanRange 一般是主键或者主键的前缀,定义的接口如下:
TableQuery addScanRange(Object[] starts, Object[] ends);
参数说明:
- 参数为两个数组,数组的维数为主键列的个数。
<starts[m], ends[m]>代表主键的第 m 列的取值范围。
示例 schema 如下:
CREATE TABLE test(c1 INT, c2 INT, c3 INT, c4 INT, PRIMARY KEY(c1, c2));
示例查询如下:
//SELECT c1, c2 FROM test WHERE c1 = 0 AND c2 >=2 AND c2 <= 40;
QueryResultSet resultSet = obTableClient.query("test")
.select("c1","c2")
.setScanRangeColumn('c1', "c2")
.addScanRange(new Object[]{0, 2}, new Object[]{0, 40})
.execute();
Filter 和 ScanRange 的区别
SQL 和 OBKV-Table 在处理查询条件时有所不同:
SQL:
- 在
WHERE子句中,所有列都可以作为过滤条件。 - 不需要区分主键和非主键。
- SQL 优化器会自动将主键相关的条件转为 ScanRange,非主键条件转为 Filter。
- 在
OBKV-Table:
- 主要用于简单的事务处理场景。
- 没有内置的优化器。
- 需要用户自己通过接口设置 ScanRange 和 Filter。
为什么要区分 ScanRange 和 Filter?
ScanRange(扫描范围):
- 用于主键或主键前缀。
- 数据库中的数据是按主键排序的。
- 可以快速定位到符合条件的数据范围。
Filter(过滤器):
- 用于非主键列。
- 无法直接定位数据,需要扫描。
- 用于在扫描过程中判断数据是否符合条件。
两者都有的情况下,查询过程如下:
- 首先使用 ScanRange 快速找到可能符合条件的数据范围。
- 然后在这个范围内,使用 Filter 进一步筛选出最终符合所有条件的数据。
需要注意的是:
- ScanRange 在查询计划中一定要有,否则即使有 Filter,也会走全表扫描路径,导致消耗数据库大量的 I/O 资源。
- OBKV-Table 服务端没有提供优化器能力, 不会将写在 Filter 中的主键条件转换为 ScanRange。