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,有三个必须遵守的约束:
- 必须覆盖字典表的全部主键列:表达式总数必须与字典表 Primary Key 列总数一致;
- 复合主键需按序对应:当字典表使用复合主键(Composite Primary Key)时,列表中的表达式须按表结构中主键列的定义顺序一一对应,多个表达式之间用逗号(
,)分隔; - 类型需匹配:若
key_column_expr是具体键值或键表达式,其类型必须与字典表中对应主键列的类型一致。
可选参数
| 参数 | 说明 |
|---|---|
<value_column> | 值列(即映射列)的名称。不指定时,默认值列为字典表的 AUTO_INCREMENT 列;也可以显式指定字典表中除自增列、主键列之外的任意列,该列的数据类型无任何限制 |
<null_if_not_exist> | 当键在字典表中不存在时是否返回。true:返回NULL;false(默认值):抛出异常 |
上述语义在 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)**方式工作的特殊表达式。完整链路可拆解如下:
FE 语法解析:SQL 中的
dict_mapping(...)被解析为DictQueryExpr(见 DictQueryExpr.java),函数名在 FunctionSet.java 中注册为DICT_MAPPING = "dict_mapping"。FE 语义校验与计划生成:
visitDictQueryExpr完成前文所述的各项校验后,会构造一个TDictQueryExpr并填入关键元信息:db_name/tbl_name:字典库表名;partition_version:记录字典表所有物理分区当前的可见版本号(见源码 L1940-L1942),保证查询读取的是同一版本的一致性数据;key_fields:字典表全部主键列名列表;value_field:映射值列名;strict_mode:其取值等于!nullIfNotFound,即用户不要求空值返回时开启严格模式(见源码 L1948)。
BE 执行:真正的取值逻辑位于 dict_query_expr.cpp。
evaluate_checked会先把键表达式逐行求值、组装成一个key_chunk,再通过TableReader::multi_get对字典表底层存储执行一次批量读取,一次性取回全部键对应的值列数据。对于命中的键直接追加映射值;对于未命中的键:- 严格模式(
null_if_not_exist为false)下返回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 客户端中执行验证。
示例一:直接从字典表查询键的映射值
- 创建字典表并装载模拟数据:
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键列的值不重复;否则同一键值被多次插入会导致其在值列中的映射值发生变化。
- 查询键
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 在导入时自动完成键值映射装载,业务侧零额外成本。
- 创建数据表,用
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);- 向表中装载模拟数据。由于
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表达式。
- 创建数据表:
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);- 导入模拟数据时,通过配置
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。
示例五:复合主键字典表,查询时必须指定全部主键
- 创建带复合主键的字典表并装载模拟数据:
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)- 查询字典表中键的映射值。由于字典表是复合主键,
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),仅供参考