---
title: "OBKV-HBase 数据模式 - OceanBase 数据库 V4.2.1 | OceanBase 文档中心"
description: OBKV-HBase 数据模式 本文介绍 OBKV-HBase 数据库对象的创建和管理。 注意 在进行数据库对象操作之前，确保您已经创建了 MySQL 租户。 数据库 OBKV 集群其实就是一个 MySQL 租户的 OceanBase 集群。库（Database） 是数据库对象的集合，用于权限管理和命名空间隔离。 在…
---
切换语言

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

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

# OBKV-HBase 数据模式

更新时间：2025-03-20 11:21:26

[编辑](https://github.com/oceanbase/oceanbase-kv/edit/V4.2.1/zh-CN/200.obkv-hbase/700.obkv-hbase-reference/150.obkv-hbase-schema-design.md)  

本文介绍 OBKV-HBase 数据库对象的创建和管理。

#### 注意

在进行数据库对象操作之前，确保您已经创建了 MySQL 租户。

## 数据库

OBKV 集群其实就是一个 MySQL 租户的 OceanBase 集群。库（Database） 是数据库对象的集合，用于权限管理和命名空间隔离。

在创建数据表之前，需要先在 MySQL 租户下创建数据库。有关数据库创建和管理的详细操作参加：

- [创建数据库](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220194)
 - [查看数据库](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220195)
 - [修改数据库](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220193)
 - [删除数据库](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220196)
 - [CREATE DATABASE](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000221424)

## 表组

表组（Table Group）是一个逻辑概念，表示一组表的集合。默认情况下，不同表之间的数据是随机分布的，没有直接关系。通过定义表组，可以控制一组表在物理存储上的邻近关系。  
 OBKV-HBase 在实现 HBase 模型的时候，将 HBase 的表映射为 OceanBase 数据库中的表组。假设 HBase 有一张 htable1 的表，在 OceanBase 数据库中, 需要创建一个 htable1 的表组。

#### 注意

一个表组下所有表的分区规则要一致。

在创建数据表之前，需要先创建对应的表组。

```
CREATE TABLEGROUP htable1;

```

关于表组的详细介绍和操作指导，参见：

- [关于表组](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220202)
 - [创建表组](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220199)
 - [查看表组信息](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220197)
 - [将表添加到表组](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220201)
 - [修改表组的 SHARDING 属性](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220198)
 - [管理表组内的表](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220203)
 - [删除表组](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220200)
 - [CREATE TABLEGROUP](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000221436)

## 表

OBKV-HBase 将 HBase 的 Column Family 映射为 OceanBase 中的一张普通表。在使用 HBase 之前，一定要先在数据库中建表组以及表。

如下是一个最简单的建表语句。假设 HBase 有一张 htable1 的表，有一个列族 family1。在 OceanBase 数据库中，需要创建一个 htable1 的表组以及一张普通表htable1$family1，其中普通表的命名规则为TableGroupName$FamilyName，并和 htable1 的表组进行绑定。

#### 注意

OBKV-HBase 暂时不支持多列族，一个表组中只能绑定一张表，但是支持在客户端不带列族做 Get 以及 Scan 操作。

create table htable1$family1 (
  K varbinary(1024),
  Q varbinary(256),
  T bigint,
  V varbinary(1048576) NOT NULL,
  primary key(K, Q, T))
TABLEGROUP =  htable1
partition by key(K) partitions 97;

```

虽然 OBKV-HBase 兼容了 HBase Schemless 的特性，但是基于 OceanBase 数据库实现的任何模型都有 Schema。上述建表语句，就是 OBKV-HBase 的物理存储模型。

参数说明：

- htable1 为建立的 HBase 表名，可自行命名。
 - family1 为列族名，可自行命名。  

  #### 说明

  表名与列族名之间用美元符号（$）连接，作为 OceanBase 数据库的表名。
 - K 列存储 HBase 表的 rowkey。
 - Q 列存储 Column Qualifier。
 - T 列存储时间戳版本 TimeStamp，为自 1970-01-01 UTC 至今的毫秒。
 - V 列存储 value，使用了 varbinary 类型，最长 1 MB，如果长度不够，可以用 longblob 类型。
 - K、Q、T 组成联合主键，标识 HBase 模型的一个 cell。

#### 注意

必须按照 K, Q, T, V 进行命名，不能自行修改。

通过上述 SQL 建表，一个 HBase 表中行的若干列数据，在关系表中存储为相邻的若干行，每一行存储 HBase 的一个 cell：`<row, column family, column qualifier, timestamp, value>`。

## Lob

HBase 是一个 KV 类的数据库，用户的数据都序列化为 Binary 的形式，数据有可能非常大，OceanBase 数据库支持不超过 512M 的大对象。

众所周知，大对象的 DML 性能都比较低，在使用的时候尽量避免使用大对象。正常情况下，建议在定义 Schema 的时候，V 列使用 varbinary 类型，最长 1M。如果用户实际场景不能避免操作超过 1M 的大对象，可以定义 V 列为 longblob 类型。

```
create table htable1$family1 (
  K varbinary(1024),
  Q varbinary(256),
  T bigint,
  V longblob NOT NULL,
  primary key(K, Q, T))
TABLEGROUP =  htable1
partition by key(K) partitions 97;

```

## 索引

表创建成功后，可以在表的一个或多个列上创建索引以加速表上的语句执行速度。索引使用正确的话，可以减少物理 IO 或者逻辑 IO。

OceanBase 数据库支持全局索引和局部索引、唯一索引和非唯一索引等。

#### 注意

OBKV-HBase 暂时不支持二级索引。

有关索引类型和使用的详细介绍，参见：

- [索引简介](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220204)
 - [创建索引](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220208)
 - [在分区表上建立索引](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000221077)
 - [查看索引](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220206)
 - [删除索引](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220205)

## 分区

上述建表过程中没有包含分区方式，建立的表是一个非分区表，仅适用于数据量很小的场景。一般情况下 HBase 业务表数据量都比较大，所以我们需要建立分区表。目前，OceanBase 数据库的 HBase 模型不能像 Apache HBase 一样自动进行分区（Region）分裂，所以需要在建表的时候选择一种分区方式。  
 OceanBase 数据库提供了多种分区策略，用于控制数据库如何将数据放入分区。OceanBase 数据库的基本分区策略包括范围（Range）分区、列表（List）分区和哈希（Hash）分区。

一个一级分区仅限使用一种数据分配方法。例如，仅使用 List 分区或仅使用 Range 分区。

在进行二级分区时，表首先通过一种数据分配方法进行分区，然后使用第二种数据分配方法将每个分区进一步划分为二级分区。

关于分区的详细介绍，参见 [分区类型](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220999)。

您可以参考如下流程决定使用哪一种分区方法。

### Key 分区

如果您的业务中没有用 Scan 进行范围扫描，或者说您只需要 Get 接口，那么使用 Key 分区。分区数需要是奇数，最好是素数，建议值为 97、193、389 等。Key 分区的显著优点是数据分布均匀，一般不会出现数据倾斜和热点。示例如下：

```
CREATE TABLE htable1$family1 (
  K varbinary(1024),
  Q varbinary(256),
  T bigint,
  V varbinary(1048576) NOT NULL,
  PRIMARY KEY(K, Q, T))
  TABLEGROUP =  htable1
PARTITION BY KEY(K) PARTITIONS 97;

```

### 虚拟列结合 Key 分区

如果您在业务中必须使用 Scan，但是扫描范围总是 rowkey 的一个定长前缀（前缀扫描），可以在 K 上定义一个虚拟列，然后对这个虚拟列进行 Key 分区。通过这样的方式，可以保证每次扫描只涉及一个分区。除非前缀数据有倾斜，这种分区方式也有利于避免热点。示例如下：

```
CREATE TABLE htable1$family1 (
  K varbinary(1024),
  Q varbinary(256),
  T bigint,
  V varbinary(1048576),
  K_PREFIX varbinary(1024) GENERATED ALWAYS AS (substring(K, 1, 4)),
  PRIMARY KEY(K, Q, T))
  TABLEGROUP =  htable1
PARTITION BY KEY(K_PREFIX) PARTITIONS 97;

```

参数说明：函数 `substring(K, 1, 4)` 表示取 K（即 HBase 表的 rowkey）的前 4 个字节为子串，这里的 “4” 需要根据业务 rowkey 特点，替换为前缀长度。

#### 说明

当前只支持使用 substring 构建虚拟列。

### Range 分区

如果您不仅需要前缀扫描和 Get，那么必须建立 Range 分区。建议您需要预先对 rowkey 的分布进行估计或采样，选定 Range 分区的分割点。如果预估不合理，这种分区方式很容易造成数据倾斜和热点，因尽量避免采用这种方式。示例如下：

```
CREATE TABLE htable1$family1 (
  K varbinary(1024),
  Q varbinary(256),
  T bigint,
  V varbinary(1048576),
  PRIMARY KEY(K, Q, T)
)
TABLEGROUP =  htable1
PARTITION BY RANGE columns (K)
(
  PARTITION p0 VALUES LESS THAN ('a'),
  PARTITION p1 VALUES LESS THAN ('w'),
  PARTITION p2 VALUES LESS THAN MAXVALUE);

```

参数说明：  
 通过 'a' 和 'w' 把整个 rowkey 值域空间分为 3 段。

### 分区管理

关于分区的详细操作指导，参见：

- [创建分区表](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220224)
 - [修改分区规则](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220230)
 - [添加分区](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220225)
 - [删除分区](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220227)
 - [TRUNCATE 分区](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220232)
 - [分区裁剪](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220226)

## 主键设计

### Key 分区键的设计

在 OBKV-HBase 中，分区键的设计对性能的提升非常重要。众所周知，在分布式系统中，为了保证分布式事务的 ACID 以及分布式读的快照一致性读，需要付出额外的性能代价，这也是很多开源 NoSQL 数据库不支持跨行事务，以及不提供全局快照一致性读的原因。

基于性能考虑，OBKV-HBase 不支持分布式事务，也不支持全局一致性快照读。但是支持节点级别的事务（开源 HBase 只支持单行事务），以及节点级别的一致性快照读。通过分区键的设计，可以将业务的请求，限制在一个分区内，可以做到既能满足扩展性的诉求，又能得到一个单机的性能以及一致性。

分区键是主键的前缀，如下举一个例子，比如是一个用户消息系统：

- 每个用户有个定长 64 位唯一 ID，userid
 - 一条消息，有个 64 位的消息 ID，msgid

主键设计为 userid | msgid，分区键定义如下：

```
CREATE TABLE htable1$family1 (
  K varbinary(1024),
  Q varbinary(256),
  T bigint,
  V varbinary(1048576),
  K_PREFIX varbinary(1024) GENERATED ALWAYS AS (substring(K, 1, 64)),
  PRIMARY KEY(K, Q, T))
  TABLEGROUP =  htable1
  PARTITION BY KEY(K_PREFIX) PARTITIONS 97;

```

如上，通过 substring 从 K 列的主键中提取了定长的分区键，一个 userid 的所有 scan/get/put/Batch 等都会访问一个分区，在单分区内，OBKV-HBase 会保证跨行的事务，以及一致性读。

### Key分区数的设计考虑

#### 为什么要关注分区数

- 分区数的调整是 DDL，重新调整有一定的代价。
 - 分区数过小不是太合适：举个例子，DB 的均衡算法会保证两个节点之间分区数的差异不超过 1，假设 3 个节点，7 个分区，那么三个节点上的分区数是 <3,2,2>，也即节点间分区的不均衡度为 (3-2)/3 = 33%。在假定没有数据倾斜的条件下，分区数几乎正比于数据的存储量以及访问量，分区的不均衡度也会体现在各个节点资源使用上的不均衡。
 - 分区数过大也不是太合适：举个例子，分区类似 miniDB，也就是一个 DB 中有多个 miniDB。每个 miniDB 独立管理自己的存储空间和数据访问，首先 miniDB 多会导致存储空间不能极致共享，另外 Scan 可能会跨多个 DB，有一定的性能损失，业务上的 Batch 操作难以直接下压给存储做极致的性能优化。

#### 分区数对性能的影响

- 一般情况，分区的大小对性能的影响不大。
 - 主要影响的是合并/迁移/备份等基于分区为粒度任务，分区过大，会导致单个任务的执行时间长，也会对空间的要求会更高。

#### 分区数如何选择

- 如果基于容量考虑，我们建议单分区单副本的数据量尽量不超过 100GB。在选取分区数的时候，尽量选择 23/59/97/193/389/997 等素数。比如三副本，24T 的存储空间占用，单分区单副本 8T，预估需要至少 80 个分区，基于上述推荐值，选择 97 个分区。
 - 分区数的变更是 DDL，有一定的开销。在设计分区数的时候，尽量避免频繁做分区数变更，要提前做好分区规划。假设当前的存储空间是 24T，但是未来一年，预期存储空间会扩大到 50T，那么在初始设计的时候，可以基于 50T 为基准，最好选择 193 的分区数。
 - 分区数最好不要选择过大，最大不建议超过 997。

#### 如何判断分区间的数据倾斜

Key 分区目前使用 MurmurHash，会基于分区键把数据 Hash 后，投递到对应的分区。除非有极端情况，比如数据 1 亿条，1kw的数据都是一个分区键，这种会造成比较严重的数据倾斜。如果数据 1 亿条，有部分分区键下有几千条数据，这种场景一般不会造成倾斜，因为在大样本下，经过 Hash 后算分区键，有比较大的随机性，从全局看，各个分区的数据应该是接近。

### 主键的常见设计考虑

#### 主键打散

在一个分布式系统中，主键打散至关重要。请求均衡打散在集群中各个机器上，可以最大化利用整个分布式系统的算力。

- 如果选择 Range 分区，主键前缀最好不要采用自增列或者时间列。比如一个消息系统的主键设计为 timestamp | msgid，就不是一个好的设计，会导致写入请求都几乎集中在一台机器上。
 - 尽量避免使用一个有很大基数的前缀作为主键前缀，比如一个消息系统的主键设计为 msgtype | msgid， 就不是一个好的设计（msgtype 的枚举数量较少），也会导致读写请求几乎都集中在一台机器上。

#### 主键设计

- 主键长度不宜过长，最好不要超过 1K。过长的主键会导致冗余存储，另外会导致访问性能不必要的下降，典型的是主键不能用大对象类型。
 - 如果使用 Range 分区，但是主键前缀需要是一个自增列或者时间列，会导致数据访问热点。可以考虑把这个自增的主键前缀 hash 后作为新的主键前缀，比如一个不优的主键设计 timestamp | msgid，可以考虑将主键变为 hashvalueoftime | timestamp | msgid。在使用 Key 分区的时候，Key 分区会将分区键 hash 打散到各个分区中，可以不用提取前缀 Hash。

## 相关文档

- 关于 OceanBase 数据库对象的详细介绍，参见 [数据库对象介绍](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000218200) 章节。
 - 关于 OceanBase 数据库对象的操作指导，参见 [数据库对象管理](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220194) 章节。
 - 关于 OceanBase 数据库的设计规范和约束，参见 [数据库设计规范和约束](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000218552) 章节。

 上一篇 下一篇 ![有帮助](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) 咨询热线
