首批通过分布式安全可靠测评,为关键业务系统打造
OBKV-HBase 数据模式
更新时间:2026-04-15 16:01:32
本文介绍 OBKV-HBase 数据库对象的创建和管理。
注意
在进行数据库对象操作之前,确保您已经创建了 MySQL 租户。
数据库
OBKV 集群其实就是一个 MySQL 租户的 OceanBase 集群。库(Database) 是数据库对象的集合,用于权限管理和命名空间隔离。
在创建数据表之前,需要先在 MySQL 租户下创建数据库。有关数据库创建和管理的详细操作参加:
表组
表组(Table Group)是一个逻辑概念,表示一组表的集合。默认情况下,不同表之间的数据是随机分布的,没有直接关系。通过定义表组,可以控制一组表在物理存储上的邻近关系。
OBKV-HBase 在实现 HBase 模型的时候,将 HBase 的表映射为 OceanBase 数据库中的表组。假设 HBase 有一张 htable1 的表,在 OceanBase 数据库中, 需要创建一个 htable1 的表组。
注意
一个表组下所有表的分区规则要一致。
在创建数据表之前,需要先创建对应的表组。
CREATE TABLEGROUP htable1;
关于表组的详细介绍和操作指导,参见:
表
OBKV-HBase 将 HBase 的 Column Family 映射为 OceanBase 中的一张关系表。在使用 HBase 之前,一定要先在数据库中建表组以及表。
表识别标志
对于 OBKV-HBase 表,该功能主要有以下两个作用:
- 识别 OBKV-HBase 表类型。如果是 OBKV-HBase 表,则可以识别该表支持分区分裂功能。
- 启用列族(Column Family)级别的 TTL 功能。
注意
强烈建议在建表时添加 kv_attributes 设置,以便系统能够准确识别 OBKV 表类型。
OBKV-HBase 类型的 kv_attributes 目前仅支持 TimeToLive 和 MaxVersions 两个设置参数,支持两个参数支持任意顺序设置、设置任意一个或均不设置。
示例如下:
-- 设置 TimeToLive 和 MaxVersions,这两个参数仅支持正整数类型值
CREATE TABLE t1$cf1 (
K VARBINARY(1024),
Q VARBINARY(256),
T BIGINT,
V VARBINARY(1048576) NOT NULL,
PRIMARY KEY(K, Q, T))
-- '{"HBase": {xxx}}' 该形式不可变更,HBase/TimeToLive/MaxVersions 大小写不敏感
kv_attributes ='{"HBase": {"TimeToLive": 3600, "MaxVersions": 3}}'
PARTITION BY KEY(K) PARTITIONS 97;
-- 仅设置 MaxVersions
CREATE TABLE t1$cf1 (
K VARBINARY(1024),
Q VARBINARY(256),
T BIGINT,
V VARBINARY(1048576) NOT NULL,
PRIMARY KEY(K, Q, T))
kv_attributes ='{"HBase": {"MaxVersions": 3}}'
PARTITION BY KEY(K) PARTITIONS 97;
-- kv_attributes 为空
CREATE TABLE t1$cf1 (
K VARBINARY(1024),
Q VARBINARY(256),
T BIGINT,
V VARBINARY(1048576) NOT NULL,
PRIMARY KEY(K, Q, T))
kv_attributes ='{"HBase": {}}'
PARTITION BY KEY(K) PARTITIONS 97;
普通表
普通表是 OBKV-HBase 的最基本的数据模型。假设 HBase 有一张 htable1 的表,有一个列族 family1。在 OceanBase 数据库中,需要创建一个 htable1 的表组以及一张普通表 htable1$family1,其中普通表的命名规则为 TableGroupName$FamilyName,并和 htable1 的表组进行绑定。
注意
OBKV-HBase Put、Delete、Get、Scan 接口支持多列族,示例请见数据操作示例。
CREATE TABLEGROUP htable1;
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;
CREATE TABLE htable1$family2 (
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、family2 为列族名,可自行命名。
说明
表名与列族名之间用美元符号($)连接,作为 OceanBase 数据库的表名。
- K 列存储 HBase 表的 Rowkey。
- Q 列存储 Column Qualifier。
- T 列存储时间戳版本 TimeStamp,为自 1970-01-01 UTC 至今的毫秒。
- V 列存储 value,使用了 varbinary 类型,最长 1 MB,如果长度不够,可以用 longblob 类型,但使用 longblob 会有 10% 左右的性能损耗。
- K、Q、T 组成联合主键,标识 HBase 模型的一个 cell。
注意
- 必须按照 K, Q, T, V 进行命名,不能自行修改。
- 当前版本仅支持一级分区。
通过上述 SQL 建表,一个 HBase 表中行的若干列数据,在关系表中存储为相邻的若干行,每一行存储 HBase 的一个 cell:<row, column family, column qualifier, timestamp, value>。
时序表
从 V4.3.5 BP2 版本开始,OBKV-HBase 支持了时序表的存储,时序表适用于时序场景。
注意
- 时序表模型由 K、T、S、V、G 几列组成,列名不可更改。
- 仅支持一级分区。
限制如下:
- 目前只支持 put、get、scan、delete 接口。每个接口都有一定限制,具体如下:
- put 接口限制:
- 不支持 Cell 级别 TTL。
- 不支持多列族。
- get 接口限制:
- 不支持 filter。
- 不支持多列族。
- 不支持 maxVersion,可设置但不会生效。
- 不支持 isCheckExistenceOnly。
- 不支持 isClosestRowBefore。
- 不支持 Rowkey TTL。
- scan 接口限制:
- 不支持 filter。
- 不支持多列族。
- 不支持 maxVersion,可设置但不会生效。
- 不支持 ReverseScan。
- 不支持 limit。
- 不支持 setBacth。
- 不支持 setCaching。
- 不支持 setMaxResultSize。
- 不支持 setRowOffsetPerColumnFamily。
- 不支持 setCacheBlocks。
- 不支持 Rowkey TTL。
- delete 接口限制:
- 不支持多列族。
- put 接口限制:
创建时序表
CREATE TABLEGROUP htable1;
CREATE TABLE `htable1$family1` (
`K` varbinary(1024) NOT NULL,
`T` bigint(20) NOT NULL,
`S` bigint(20) NOT NULL,
`V` json NOT NULL,
`G` bigint(20) GENERATED ALWAYS AS (ABS(T)),
PRIMARY KEY (`K`, `T`, `S`)
) PARTITION BY KEY(`K`) PARTITIONS 97;
CREATE TABLE `htable1$family2` (
`K` varbinary(1024) NOT NULL,
`T` bigint(20) NOT NULL,
`S` bigint(20) NOT NULL,
`V` json NOT NULL,
`G` bigint(20) GENERATED ALWAYS AS (ABS(T)),
PRIMARY KEY (`K`, `T`, `S`)
) PARTITION BY KEY(`K`) PARTITIONS 97;
参数说明:
- K 列存储 HBase 表的 Rowkey,可用作分区键。
- T 列存储时间戳版本 TimeStamp,为自 1970-01-01 UTC 至今的毫秒。
- S 列用于存储与同一个 K 和 T(时间戳)对应的多次不同写入的序列号。通常情况下,这个序列号是写入操作的时间戳。S 列在写入数据时无需指定,系统自动生成。
- V 列将多个 Q(Qualify) 和 V(value) 编码到 JSON 中。
- G 列是一个虚拟生成列,用于存储 T(时间戳)的绝对值(ABS),可作为时间维度的分区键。
- K、T、S 组成联合主键。
注意
必须按照 K, T, S, V, G 进行命名,不能自行修改。
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 暂时不支持二级索引。
有关索引类型和使用的详细介绍,参见:
分区
上述建表过程中没有包含分区方式,建立的表是一个非分区表,仅适用于数据量很小的场景。一般情况下 HBase 业务表数据量都比较大,所以我们需要建立分区表。OBKV-HBase 当前仅支持一级分区策略,支持 Range 分区和 Key 分区。
一级分区的 Range/Range Columns 分区支持自动分区分裂,这种分区方式适合范围扫描的场景,同时它还有普通 Range 分区所不具有的自动按照数据量分裂的能力,将数据量进行均匀分布。更多请参见 OBKV-HBase 自动分区分裂。
关于分区的详细介绍,参见 分区类型。
注意
一个表组下所有表的分区规则要一致。
一级分区策略
当前仅支持一级分区,可以参考如下流程决定具体使用哪一种分区方法。
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 段。
分区管理
关于分区的详细操作指导,参见:
主键设计
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 数据库对象的详细介绍,参见 数据库对象介绍 章节。
- 关于 OceanBase 数据库对象的操作指导,参见 数据库对象管理 章节。
- 关于 OceanBase 数据库的设计规范和约束,参见 数据库设计规范和约束 章节。