跳到主要内容
版本: 最新版本-3.5

主键表

主键表使用 StarRocks 设计的全新存储引擎。它的主要优势在于支持实时数据更新,同时确保复杂即席查询的高效性能。在实时业务分析中,决策可以受益于主键表,它使用最新的数据来实时分析结果,从而可以减轻数据分析中的数据延迟。

主键表的主键具有 UNIQUE 约束和 NOT NULL 约束,用于唯一标识每行数据。如果新数据行的主键值与表中现有数据行的主键值相同,则会发生 UNIQUE 约束冲突。然后,新数据行将替换现有数据行。

信息
  • 自 v3.0 起,主键表的排序键与表的主键分离,可以单独指定排序键。因此,提高了表创建的灵活性。
  • 自 v3.1 起,StarRocks 共享数据集群支持创建主键表。
    • 自 v3.1.4 起,可以在**本地磁盘**中创建和存储持久索引。
    • 自 v3.3.2 起,可以在**对象存储**中创建和存储持久索引。

使用场景

主键表可以支持实时数据更新,同时确保高效的查询性能。它适用于以下场景

  • 将事务处理系统中的流数据实时导入到 StarRocks 中。 在正常情况下,事务处理系统除了插入操作外,还涉及大量的更新和删除操作。如果您需要将数据从事务处理系统同步到 StarRocks,我们建议您创建一个主键表。然后,您可以使用工具(例如 CDC Connectors for Apache Flink®)将事务处理系统的二进制日志同步到 StarRocks。StarRocks 使用二进制日志来实时添加、删除和更新表中的数据。这简化了数据同步,并比使用采用 Merge-On-Read 策略的 Unique Key 表的查询性能高出 3 到 10 倍。有关更多信息,请参见从 MySQL 实时同步
  • 通过对单个列执行部分更新来联接多个流。在用户画像等业务场景中,最好使用扁平表来提高多维分析性能并简化数据分析师使用的分析模型。这些场景中的上游数据可能来自各种应用,例如购物应用、交付应用和银行应用,或者来自机器学习系统等系统,这些系统执行计算以获得用户的不同标签和属性。主键表非常适合在这些场景中使用,因为它支持对单个列的更新。每个应用或系统只能更新在其自身服务范围内保存数据的列,同时受益于实时数据添加、删除和更新以及高查询性能。

工作原理

Unique Key 表和 Aggregate 表采用 Merge-On-Read 策略。此策略使数据写入简单高效,但需要在数据读取期间在线合并多个版本的数据文件。此外,由于存在 Merge 运算符,谓词和索引无法下推到底层数据,这会严重影响查询性能。

但是,为了平衡实时更新和查询的性能,主键表中的元数据结构和读/写机制与其他类型的表不同。主键表使用 Delete+Insert 策略。此策略通过使用主键索引和 DelVector 来实现。此策略确保在查询期间只需要读取具有相同主键值的记录中的最新记录,这消除了合并多个版本的数据文件的需要。此外,可以将谓词和索引下推到底层数据,这大大提高了查询性能。

主键表中写入和读取数据的整体过程如下

  • 数据写入是通过 StarRocks 的内部 Loadjob 实现的,Loadjob 包括一批数据更改操作(Insert、Update 和 Delete)。StarRocks 将相应 Tablet 的主键索引加载到内存中。对于 Delete 操作,StarRocks 首先使用主键索引查找每个数据行的原始位置(数据文件和行号),在 DelVector 中将数据行标记为已删除(DelVector 存储和管理数据加载期间生成的删除标记)。对于 Update 操作,除了在 DelVector 中将原始数据行标记为已删除外,StarRocks 还会将最新的数据行写入新的数据文件,本质上将 Update 转换为 Delete+Insert(如下图所示)。主键索引也会更新以记录更改的数据行的新位置(数据文件和行号)。

    pk1

  • 在数据读取期间,由于各种数据文件中的历史重复记录在数据写入期间已被标记为已删除,因此只需要读取具有相同主键值的最新数据行。不再需要在网上读取多个版本的数据文件以对数据进行去重并查找最新数据。当扫描底层数据文件时,过滤器运算符和各种索引有助于减少扫描开销(如下图所示)。因此,可以显着提高查询性能。与 Unique Key 表的 Merge-On-Read 策略相比,主键表的 Delete+Insert 策略可以帮助将查询性能提高 3 到 10 倍。

    pk2

更多细节

如果您想更深入地了解如何将数据写入或从主键表中读取,您可以探索以下详细的数据写入和读取过程

StarRocks 是一个使用列式存储的分析数据库。具体来说,表中的 Tablet 通常包含多个 Rowset 文件,每个 Rowset 文件的数据实际上存储在段文件中。段文件以列式格式组织数据(类似于 Parquet),并且是不可变的。

当要写入的数据分布到 Executor BE 节点时,每个 Executor BE 节点都会执行 Loadjob。Loadjob 包括一批数据更改,可以被视为具有 ACID 属性的事务。Loadjob 可以分为两个阶段:写入和提交。

  1. 写入阶段:数据根据分区和 Bucket 信息分布到相应的 Tablet。当 Tablet 接收到数据时,数据以列式格式存储,然后形成一个新的 Rowset。
  2. 提交阶段:所有数据成功写入后,FE 会发起对所有涉及的 Tablet 的提交。每个提交都携带一个版本号,表示 Tablet 数据的最新版本。提交过程主要包括搜索和更新主键索引,将所有更改的数据标记为已删除,基于标记为已删除的数据创建 DelVector,以及为新版本生成元数据。

在数据读取期间,元数据用于根据最新的 Tablet 版本查找需要读取的 Rowset。当正在读取 Rowset 中的段文件时,还会检查其最新版本的 DelVector,这可以确保只需要读取最新的数据,并避免读取具有相同主键值的旧数据。此外,下推到 Scan 层的过滤器运算符可以直接利用各种索引来减少扫描开销。

  • Tablet:表根据分区和 Bucket 机制分为多个 Tablet。它是实际的物理存储单元,并作为副本分布在不同的 BE 上。

    pk3

  • 元数据:元数据存储 Tablet 的版本历史记录和有关每个版本的信息(例如,包括哪些 Rowset)。每个 Loadjob 或 Compaction 的提交阶段都会生成一个新版本。

    pk4

  • 主键索引:主键索引存储由这些主键值标识的数据行与这些数据行的位置之间的映射。它被实现为一个 HashMap,其中键表示编码的主键值,值表示数据行的位置(包括 rowset_idsegment_idrowid)。通常,主键索引仅在数据写入期间用于查找由特定主键值标识的每个数据行所在的 Rowset 和行。

  • DelVector:DelVector 存储每个 Rowset 中每个段文件(列式文件)的删除标记。

  • Rowset:Rowset 是一个逻辑概念,存储 Tablet 中一批数据更改中的数据集。

  • :Rowset 中的数据实际上被分段并存储在一个或多个段文件(列式文件)中。每个段文件包含列值和与列相关的索引信息。

用法

创建主键表

您只需要在 CREATE TABLE 语句中定义主键即可创建主键表。例子

CREATE TABLE orders1 (
order_id bigint NOT NULL,
dt date NOT NULL,
user_id INT NOT NULL,
good_id INT NOT NULL,
cnt int NOT NULL,
revenue int NOT NULL
)
PRIMARY KEY (order_id)
DISTRIBUTED BY HASH (order_id)
;
信息

由于主键表仅支持哈希分桶作为分桶策略,您还需要使用 DISTRIBUTED BY HASH () 定义哈希分桶键。

但是,在实际业务场景中,创建主键表时,通常会使用数据分布和排序键等附加功能来加速查询并更有效地管理数据。

例如,订单表中的 order_id 字段可以唯一标识数据行,因此 order_id 字段可以用作主键。

自 v3.0 起,主键表的排序键与表的主键分离。因此,您可以选择经常用作查询过滤条件的列来形成排序键。例如,如果您经常根据订单日期和商家这两个维度的组合来查询产品销售业绩,则可以使用 ORDER BY (dt,merchant_id) 子句将排序键指定为 dtmerchant_id

请注意,如果您使用数据分布策略,则主键表当前要求主键包含分区和 Bucket 列。例如,数据分布策略使用 dt 作为分区列,使用 merchant_id 作为哈希 Bucket 列。主键还需要包括 dtmerchant_id

总而言之,上述订单表的 CREATE TABLE 语句如下

CREATE TABLE orders2 (
order_id bigint NOT NULL,
dt date NOT NULL,
merchant_id int NOT NULL,
user_id int NOT NULL,
good_id int NOT NULL,
good_name string NOT NULL,
price int NOT NULL,
cnt int NOT NULL,
revenue int NOT NULL,
state tinyint NOT NULL
)
PRIMARY KEY (order_id,dt,merchant_id)
PARTITION BY date_trunc('day', dt)
DISTRIBUTED BY HASH (merchant_id)
ORDER BY (dt,merchant_id)
PROPERTIES (
"enable_persistent_index" = "true"
);

主键

表的主键用于唯一标识该表中的每一行。构成主键的一个或多个列在 PRIMARY KEY 中定义,并具有 UNIQUE 约束和 NOT NULL 约束。

请注意以下有关主键的注意事项

  • 在 CREATE TABLE 语句中,必须在其他列之前定义主键列。
  • 主键列必须包括分区和 Bucket 列。
  • 主键列支持以下数据类型:数字(包括整数和 BOOLEAN)、字符串和日期(DATE 和 DATETIME)。
  • 默认情况下,编码主键值的最大长度为 128 字节。
  • 创建表后无法修改主键。
  • 出于数据一致性的目的,无法更新主键值。

主键索引

主键索引用于存储主键值与由主键值标识的数据行的位置之间的映射。通常,仅在数据加载期间(涉及一批数据更改)才将相关 Tablet 的主键索引加载到内存中。您可以综合评估查询和更新的性能要求以及内存和磁盘后,考虑持久化主键索引。

enable_persistent_index 设置为 true(默认值)时,可以将主键索引持久化到磁盘。在加载期间,只有一小部分主键索引会加载到内存中,而大部分都存储在磁盘上,以避免占用过多内存。通常,具有持久主键索引的表的查询和更新性能与具有完全内存主键索引的表的查询和更新性能几乎相同。

如果磁盘是 SSD,建议将其设置为 true。如果磁盘是 HDD 并且加载频率不高,您也可以将其设置为 true

自 v3.1.4 起,在 StarRocks 共享数据集群中创建的主键表支持将索引持久化到本地磁盘。从 v3.3.2 开始,StarRocks 共享数据集群进一步支持将索引持久化到对象存储。您可以通过将表属性 persistent_index_type 设置为 CLOUD_NATIVE 来启用此功能。

排序键

从 v3.0 开始,主键表将排序键与主键分离。排序键由 ORDER BY 中定义的列组成,并且可以包含任何列组合,只要列的数据类型满足排序键的要求。

在数据加载期间,数据在按照排序键排序后存储。排序键还用于构建前缀索引以加速查询。建议适当地设计排序键以形成可以加速查询的前缀索引

信息
  • 如果指定了排序键,则前缀索引基于排序键构建。如果未指定排序键,则前缀索引基于主键构建。
  • 创建表后,您可以使用 ALTER TABLE ... ORDER BY ... 来更改排序键。不支持删除排序键,也不支持修改排序列的数据类型。

更多内容