StarRocks `dict_mapping` 函数详解:基于全局字典表在数据导入时自动完成键值映射
2026/9/18 6:12:34 网站建设 项目流程

StarRocksdict_mapping函数详解:基于全局字典表在数据导入时自动完成键值映射

【免费下载链接】starrocksThe world's fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRocks provides best-in-class performance for multi-dimensional analytics, real-time analytics, and ad-hoc queries. A Linux Foundation project.项目地址: https://gitcode.com/GitHub_Trending/st/starrocks

导读

dict_mapping是 StarRocks 自 v3.2.5 起提供的全局字典映射函数,它能在数据导入阶段自动从一张 Primary Key 全局字典表中查出指定键(Key)对应的映射值(Value),从而把字符串主键替换为紧凑的整数 ID,为后续的精确去重(COUNT(DISTINCT))和 JOIN 查询提供性能加速。本文以官方函数参考文档为主体,结合 FE 与 BE 的源码实现,完整讲解该函数的语法、参数语义、返回值、底层执行链路、五种实战场景以及性能调优注意事项,帮助你在一张表、一条导入语句内完成全局字典的"键值映射装载",彻底告别手工 JOIN 字典表取 ID 的繁琐流程。

一、为什么需要dict_mapping:全局字典的装载痛点

在电商、外卖等订单类场景中,订单号通常由UUID()等函数生成,以 32~36 字节的 STRING 形式存储。直接对 STRING 列做精确去重或 JOIN,无论计算还是存储开销都远高于整数列。StarRocks 的经典优化方案是引入全局字典:用一张字典表建立"字符串键 → AUTO_INCREMENT 整数 ID"的映射,业务表则只存整数 ID,以此加速精确去重与 JOIN。

在 v3.2.5 之前,把映射关系装载进目标表只有两种办法,且都比较笨重:

  • 借助外部表或内部中间表,先与字典表 JOIN拿到字典 ID,再导入;
  • 使用 Primary Key 表导入后,再执行UPDATE + JOIN回填字典 ID,约束多、流程繁琐。

dict_mapping的出现彻底改变了这一过程:它可以直接把目标表的映射列定义为generated column(生成列),让 StarRocks 在数据导入时自动完成"查字典表、取 ID、写入映射列"的全部动作。这一套完整方案的背景、场景与分阶段流程可参见 使用 AUTO_INCREMENT 和全局字典加速 COUNT(DISTINCT) 与 JOIN。

二、函数语法

dict_mapping("[<db_name>.]<dict_table>", key_column_expr_list [, <value_column> ] [, <null_if_not_exist>] ) key_column_expr_list ::= key_column_expr [, key_column_expr ... ] key_column_expr ::= <column_name> | <expr>

从语法可以看出,dict_mapping最少需要两个参数(字典表名 + 键表达式列表),最多五个参数(字典表名 + 键表达式列表 + 值列名 + 是否空值返回)。

三、参数说明

必选参数

参数说明
[<db_name>.]<dict_table>字典表名称,必须是一张 Primary Key 表,名称类型为 VARCHAR。可以只写表名(默认当前库),也可以写成库名.表名形式
key_column_expr_list字典表键列(Primary Key 列)的表达式列表,由一个或多个key_column_expr组成。每个key_column_expr可以是字典表中某个键列的列名,也可以是某个具体的键值或键表达式

关于key_column_expr_list,有三个必须遵守的约束:

  1. 必须覆盖字典表的全部主键列:表达式总数必须与字典表 Primary Key 列总数一致;
  2. 复合主键需按序对应:当字典表使用复合主键(Composite Primary Key)时,列表中的表达式须按表结构中主键列的定义顺序一一对应,多个表达式之间用逗号(,)分隔;
  3. 类型需匹配:若key_column_expr是具体键值或键表达式,其类型必须与字典表中对应主键列的类型一致。

可选参数

参数说明
<value_column>值列(即映射列)的名称。不指定时,默认值列为字典表的 AUTO_INCREMENT 列;也可以显式指定字典表中除自增列、主键列之外的任意列,该列的数据类型无任何限制
<null_if_not_exist>当键在字典表中不存在时是否返回。true:返回NULLfalse(默认值):抛出异常

上述语义在 FE 端分析器中都有严格的校验逻辑支撑。以 ExpressionAnalyzer.java 中的visitDictQueryExpr为例,参数数量被严格限制在主键列数 + 1主键列数 + 3之间,超出范围会直接报错:

ERROR 1064 (HY000): Getting analyzing error. Detail message: dict_mapping function param size should be 3 - 5.

同时,FE 会逐项校验:第一参数必须是字符串字面量、表名必须符合'db.tbl''tbl'格式、字典表必须是 OlapTable 且必须是 Primary Key 表、显式指定的值列必须真实存在于字典表中、null_if_not_found参数必须是布尔常量等,任何一项不满足都会抛出带明确提示的SemanticException

四、返回值

  • 返回值的数据类型与值列的数据类型保持一致
  • 若值列为字典表的自增列,则返回类型为BIGINT
  • 当指定键在字典表中未找到映射值时:
    • <null_if_not_exist>true:返回NULL
    • <null_if_not_exist>false(默认):返回错误query failed if record not exist in dict table

五、底层执行原理:从 SQL 到多键批量点查

dict_mapping并非普通标量函数,而是一条在 FE 规划、在 BE 执行引擎内以**批量点查(multi-get)**方式工作的特殊表达式。完整链路可拆解如下:

  1. FE 语法解析:SQL 中的dict_mapping(...)被解析为DictQueryExpr(见 DictQueryExpr.java),函数名在 FunctionSet.java 中注册为DICT_MAPPING = "dict_mapping"

  2. FE 语义校验与计划生成visitDictQueryExpr完成前文所述的各项校验后,会构造一个TDictQueryExpr并填入关键元信息:

    • db_name/tbl_name:字典库表名;
    • partition_version:记录字典表所有物理分区当前的可见版本号(见源码 L1940-L1942),保证查询读取的是同一版本的一致性数据;
    • key_fields:字典表全部主键列名列表;
    • value_field:映射值列名;
    • strict_mode:其取值等于!nullIfNotFound,即用户不要求空值返回时开启严格模式(见源码 L1948)。
  3. BE 执行:真正的取值逻辑位于 dict_query_expr.cpp。evaluate_checked会先把键表达式逐行求值、组装成一个key_chunk,再通过TableReader::multi_get对字典表底层存储执行一次批量读取,一次性取回全部键对应的值列数据。对于命中的键直接追加映射值;对于未命中的键:

    • 严格模式(null_if_not_existfalse)下返回Status::NotFound("query failed if record not exist in dict table.")(见源码 L92-L93),与文档描述的错误信息完全一致;
    • 非严格模式下追加一个NULL值。

这种"整批键一次性点查"的设计,使得dict_mapping即使在导入海量数据时,也能将查字典的开销压缩到最小。

六、使用限制与注意点

  • 版本要求:该函数自v3.2.5起支持;
  • 共享数据模式(shared-data mode)暂不支持:FE 源码中对此有显式拦截——dict_mapping function do not support shared data mode(见 ExpressionAnalyzer.java);
  • 字典表必须是 Primary Key 表,且不能是物化视图,必须是 OlapTable;
  • INSERT INTO不支持部分更新:向字典表装载数据时,务必保证键列的值不重复。若同一键值被多次插入,其在值列中映射的 ID 会随之改变,导致映射关系漂移;
  • 复合主键必须全部指定:字典表使用复合主键时,dict_mapping中必须按序给出全部主键的表达式,少给一个都会报参数数量错误。

七、实战示例

以下示例基于官方文档中的完整用例,可直接在 MySQL 客户端中执行验证。

示例一:直接从字典表查询键的映射值

  1. 创建字典表并装载模拟数据:
MySQL [test]> CREATE TABLE dict ( order_uuid STRING, order_id_int BIGINT AUTO_INCREMENT ) PRIMARY KEY (order_uuid) DISTRIBUTED BY HASH (order_uuid); Query OK, 0 rows affected (0.02 sec) MySQL [test]> INSERT INTO dict (order_uuid) VALUES ('a1'), ('a2'), ('a3'); Query OK, 3 rows affected (0.12 sec) {'label':'insert_9e60b0e4-89fa-11ee-a41f-b22a2c00f66b', 'status':'VISIBLE', 'txnId':'15029'} MySQL [test]> SELECT * FROM dict; +------------+--------------+ | order_uuid | order_id_int | +------------+--------------+ | a1 | 1 | | a3 | 3 | | a2 | 2 | +------------+--------------+ 3 rows in set (0.01 sec)

NOTICE

当前INSERT INTO语句不支持部分更新,因此请确保插入dict键列的值不重复;否则同一键值被多次插入会导致其在值列中的映射值发生变化。

  1. 查询键a1在字典表中的映射值:
MySQL [test]> SELECT dict_mapping('dict', 'a1'); +----------------------------+ | dict_mapping('dict', 'a1') | +----------------------------+ | 1 | +----------------------------+ 1 row in set (0.01 sec)

示例二:映射列配置为生成列,导入时自动取值

这是dict_mapping最推荐的用法:把目标表的映射列声明为 generated column,StarRocks 在导入时自动完成键值映射装载,业务侧零额外成本。

  1. 创建数据表,用dict_mapping('dict', order_uuid)配置映射列为生成列:
CREATE TABLE dest_table1 ( id BIGINT, -- 该列记录 STRING 类型的订单号,对应示例一中 dict 表的 order_uuid 列 order_uuid STRING, batch int comment 'used to distinguish different batch loading', -- 该列记录与 order_uuid 列映射的 BIGINT 类型订单号。 -- 由于该列是用 dict_mapping 配置的生成列,导入数据时其值会自动从示例一的 dict 表中获取。 -- 之后可直接使用该列进行去重和 JOIN 查询。 order_id_int BIGINT AS dict_mapping('dict', order_uuid) ) DUPLICATE KEY (id, order_uuid) DISTRIBUTED BY HASH(id);
  1. 向表中装载模拟数据。由于order_id_int列已配置为dict_mapping('dict', 'order_uuid'),StarRocks 会基于dict表的键值映射关系自动写入order_id_int列:
MySQL [test]> INSERT INTO dest_table1(id, order_uuid, batch) VALUES (1, 'a1', 1), (2, 'a1', 1), (3, 'a3', 1), (4, 'a3', 1); Query OK, 4 rows affected (0.05 sec) {'label':'insert_e191b9e4-8a98-11ee-b29c-00163e03897d', 'status':'VISIBLE', 'txnId':'72'} MySQL [test]> SELECT * FROM dest_table1; +------+------------+-------+--------------+ | id | order_uuid | batch | order_id_int | +------+------------+-------+--------------+ | 1 | a1 | 1 | 1 | | 4 | a3 | 1 | 3 | | 2 | a1 | 1 | 1 | | 3 | a3 | 1 | 3 | +------+------------+-------+--------------+ 4 rows in set (0.02 sec)

可以看到,即使order_uuid存在大量重复(如a1出现两次),每次导入都会正确映射为同一个整数 ID1,为后续去重与 JOIN 奠定了基础。

该用法可显著加速去重计算与 JOIN 查询。相比此前构建全局字典加速精确去重的方案,dict_mapping方案更灵活、更易用——因为映射值在"键值映射关系装载进表"这一阶段就直接从字典表获取,无需再写语句去 JOIN 字典表取映射值;同时该方案支持多种数据导入方式(INSERT、Stream Load、Broker Load 等均适用)。

示例三:映射列非生成列,导入时显式配置dict_mapping

NOTICE

示例三与示例二的区别在于:向数据表导入时,需要修改导入命令,为映射列显式配置dict_mapping表达式。

  1. 创建数据表:
CREATE TABLE dest_table2 ( id BIGINT, order_uuid STRING, order_id_int BIGINT NULL, batch int comment 'used to distinguish different batch loading' ) DUPLICATE KEY (id, order_uuid, order_id_int) DISTRIBUTED BY HASH(id);
  1. 导入模拟数据时,通过配置dict_mapping从字典表获取映射值:
MySQL [test]> INSERT INTO dest_table2 VALUES (1, 'a1', dict_mapping('dict', 'a1'), 1); Query OK, 1 row affected (0.35 sec) {'label':'insert_19872ab6-8a96-11ee-b29c-00163e03897d', 'status':'VISIBLE', 'txnId':'42'} MySQL [test]> SELECT * FROM dest_table2; +------+------------+--------------+-------+ | id | order_uuid | order_id_int | batch | +------+------------+--------------+-------+ | 1 | a1 | 1 | 1 | +------+------------+--------------+-------+ 1 row in set (0.02 sec)

示例四:开启null_if_not_exist模式

<null_if_not_exist>模式关闭(默认)时,如果查询字典表中不存在的键所对应的映射值,会返回错误而非NULL。这一默认行为确保了数据行的键先被装载进字典表并生成映射值(字典 ID),之后该数据行才能被装载进目标表,从而避免脏数据。

MySQL [test]> SELECT dict_mapping('dict', 'b1', true); ERROR 1064 (HY000): Query failed if record not exist in dict table.

注意:示例中的第三个参数true是布尔值字面量(对应<null_if_not_exist>),因此在键b1不存在时仍然返回了错误——这与文档第四、五节的语义描述一致:想要让不存在的键返回NULL,需要显式传入null_if_not_exist = true

示例五:复合主键字典表,查询时必须指定全部主键

  1. 创建带复合主键的字典表并装载模拟数据:
MySQL [test]> CREATE TABLE dict2 ( order_uuid STRING, order_date DATE, order_id_int BIGINT AUTO_INCREMENT ) PRIMARY KEY (order_uuid,order_date) -- Composite Primary Key DISTRIBUTED BY HASH (order_uuid,order_date) ; Query OK, 0 rows affected (0.02 sec) MySQL [test]> INSERT INTO dict2 VALUES ('a1','2023-11-22',default), ('a2','2023-11-22',default), ('a3','2023-11-22',default); Query OK, 3 rows affected (0.12 sec) {'label':'insert_9e60b0e4-89fa-11ee-a41f-b22a2c00f66b', 'status':'VISIBLE', 'txnId':'15029'} MySQL [test]> select * from dict2; +------------+------------+--------------+ | order_uuid | order_date | order_id_int | +------------+------------+--------------+ | a1 | 2023-11-22 | 1 | | a3 | 2023-11-22 | 3 | | a2 | 2023-11-22 | 2 | +------------+------------+--------------+ 3 rows in set (0.01 sec)
  1. 查询字典表中键的映射值。由于字典表是复合主键,dict_mapping中必须按序指定全部主键:
SELECT dict_mapping('dict2', 'a1', cast('2023-11-22' as DATE));

若只指定其中一个主键,则会报错:

MySQL [test]> SELECT dict_mapping('dict2', 'a1'); ERROR 1064 (HY000): Getting analyzing error. Detail message: dict_mapping function param size should be 3 - 5.

八、性能调优与注意事项

文档中的 Usage Notes 针对精确去重场景给出了明确的调优指引:

  • 默认情况下,系统会允许优化器自行选择COUNT(DISTINCT)的实现方式;
  • 低、中基数列做去重时,可以通过查询 Hint 将count_distinct_implementation设置为multi_count_distinct,以验证multi_distinct_count实现的精确计数效果;
  • 切勿在高基数列上使用multi_distinct_count实现:其 HashSet 状态与最终合并过程会带来过高的内存消耗,甚至引发 OOM。

实际使用时建议结合基数情况选择实现,并在目标表上基于dict_mapping生成的整数映射列建立后续的去重、位图(如bitmap_count)与 JOIN 查询路径,以获得最佳性能。

九、与dictionary_get的关系

在 dict-functions 函数族中,还有一个与dict_mapping定位不同的函数:dictionary_get。二者的核心区别在于:

  • dict_mapping面向字典表(Primary Key 表),作用于数据导入阶段,把字符串键自动映射为整数 ID 并装载进目标表;
  • dictionary_get面向字典对象(dictionary object),作用于查询阶段,从字典缓存中直接返回键对应的值列,返回值是 STRUCT 类型,可通过[N].<column_name>取特定列。

二者可以配合使用:dictionary_get文档中的示例即复用了本文示例一dict表的数据集,先由dict_mapping完成映射装载,再由dictionary_get在查询期按需取值。

十、总结

dict_mapping把"全局字典键值映射装载"从繁琐的多步 ETL 过程压缩为一个函数调用:配合生成列,导入时自动查字典、自动写 ID,天然支持各类导入方式,并保持字典表读取的一致性(分区版本快照)。理解它的参数语义(主键全覆盖、默认自增值列、null_if_not_exist严格模式)、底层批量点查实现以及"低/中基数才适合multi_distinct_count"的调优边界,是把它用好、用稳的关键。更完整的端到端实践,可继续阅读使用 AUTO_INCREMENT 和全局字典加速 COUNT(DISTINCT) 与 JOIN一文。

【免费下载链接】starrocksThe world's fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRocks provides best-in-class performance for multi-dimensional analytics, real-time analytics, and ad-hoc queries. A Linux Foundation project.项目地址: https://gitcode.com/GitHub_Trending/st/starrocks

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询