---
title: "OBKV-Table 的数据模型 - OceanBase 数据库 V4.4.1 | OceanBase 文档中心"
description: OBKV-Table 的数据模型 本文档介绍了 OBKV-Table 的数据模型。OBKV-Table 提供表模型接口，在概念定义上和 SQL 接近，比如也有行列的概念。 表组织方式 表组织方式是 OBKV-Table 的底层存储结构，决定了数据在磁盘上的存储方式。OBKV-Table 支持两种表组织方式： 索引组织…
---
切换语言

- 中文站 - 简体中文
- International - English
- 日本站 - 日本語

文档反馈![](https://mdn.alipayobjects.com/huamei_22khvb/afts/img/A*P8CuR4UJ_FkAAAAAAAAAAAAADiGDAQ/original) OceanBase 数据库KV 型 - V 4.4.1

# OBKV-Table 的数据模型

更新时间：2026-02-09 13:57:46

[编辑](https://github.com/oceanbase/oceanbase-kv/edit/V4.4.1/zh-CN/100.obkv-table/700.obkv-table-reference/205.obkv-table-data-model.md)  

本文档介绍了 OBKV-Table 的数据模型。OBKV-Table 提供表模型接口，在概念定义上和 SQL 接近，比如也有行列的概念。

## 表组织方式

表组织方式是 OBKV-Table 的底层存储结构，决定了数据在磁盘上的存储方式。OBKV-Table 支持两种表组织方式：

- 索引组织表（Index Organized Table）
 - 堆组织表（Heap Organized Table）。OBKV 客户端从 V2.1.1 版本开始支持堆组织表。

创建表时指定表组织形式的语法是在 `CREATE TABLE` 语句后增加表选项 `ORGANIZATION`：

```sql
CREATE TABLE table_name
column_definition
ORGANIZATION [=] {INDEX | HEAP};

```

其中，`ORGANIZATION` 参数用来指定表中数据行的存储顺序，取值如下：

- INDEX：表示表模型为聚集索引表模型，即索引组织表。
 - HEAP：表示表模型为非聚集索引表模型，即堆组织表。

如果不指定 `ORGANIZATION` 选项，则该选项取值与配置项 [default_table_organization](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000003381505) 的值相同。

### 索引组织表

索引组织表是 OBKV-Table 的默认表组织方式，适用于大多数场景。索引组织表的索引是基于主键的，因此可以快速定位到数据。

例如，创建一个索引组织表：

```sql
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 等对写入吞吐量和存储成本敏感的场景。

例如，创建一个堆组织表：

```sql
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 操作类型](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000003983678)。

## 表识别标志

#### 注意

OBKV-Table 从 V4.4.1 开始支持此功能。强烈建议在建表时添加 `kv_attributes` 设置，以便系统能够准确识别 OBKV 表类型。

在创建 OBKV-Table 表时，我们建议添加 `kv_attributes='{"Table": {}}'` 设置，以便正确识别为 OBKV-Table 表。示例如下：

```sql
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 对象值。

```java
ColumnValue empCol = new ColumnValue("department", "oceanbase_product");

```

### Row

Row 是多对的 ColumnValue，里面存储着一行的值，可以有一对或者是多对的 ColumnValue。

如下示例为构造一行数据, 为了简化编码, OBKV-Table 提供了多种编码方式。

方式 1：

```java
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：

```java
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 一般是主键或者主键的前缀，定义的接口如下：

```java
TableQuery addScanRange(Object[] starts, Object[] ends);

```

```

参数说明：

- 参数为两个数组，数组的维数为主键列的个数。
 - `<starts[m], ends[m]>` 代表主键的第 m 列的取值范围。

示例 schema 如下：

```java
CREATE TABLE test(c1 INT, c2 INT, c3 INT, c4 INT, PRIMARY KEY(c1, c2));

```

示例查询如下：

```java
//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 在处理查询条件时有所不同：

1. SQL：

      - 在 `WHERE` 子句中，所有列都可以作为过滤条件。
      - 不需要区分主键和非主键。
      - SQL 优化器会自动将主键相关的条件转为 ScanRange，非主键条件转为 Filter。
 2. OBKV-Table：

      - 主要用于简单的事务处理场景。
      - 没有内置的优化器。
      - 需要用户自己通过接口设置 ScanRange 和 Filter。

为什么要区分 ScanRange 和 Filter？

- ScanRange（扫描范围）：

     - 用于主键或主键前缀。
     - 数据库中的数据是按主键排序的。
     - 可以快速定位到符合条件的数据范围。
 - Filter（过滤器）：

     - 用于非主键列。
     - 无法直接定位数据，需要扫描。
     - 用于在扫描过程中判断数据是否符合条件。

两者都有的情况下，查询过程如下：

1. 首先使用 ScanRange 快速找到可能符合条件的数据范围。
 2. 然后在这个范围内，使用 Filter 进一步筛选出最终符合所有条件的数据。

需要注意的是：

- ScanRange 在查询计划中一定要有，否则即使有 Filter，也会走全表扫描路径，导致消耗数据库大量的 I/O 资源。
 - OBKV-Table 服务端没有提供优化器能力, 不会将写在 Filter 中的主键条件转换为 ScanRange。

 上一篇 下一篇 ![有帮助](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*y6ocSqN8cqsAAAAAAAAAAAAAARQnAQ)![无帮助](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*BG9IQJyLHF8AAAAAAAAAAAAAARQnAQ)![反馈](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*eTWdQKCRKHwAAAAAAAAAAAAAARQnAQ)[AI](https://www.oceanbase.com/obi) 咨询热线
