
StarRocks dictionary_get 函数详解从字典缓存中高效查询键值映射【免费下载链接】starrocksThe worlds 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/starrocksdictionary_get是 StarRocks 中与全局字典Dictionary配套的查询函数用于在 SQL 中直接读取字典对象dictionary object内键key所映射的值value。它主要服务于以dict_mapping为核心的全局字典加速方案帮助你在查询阶段按字典主键快速取回映射结果再配合[N]或.column_name语法灵活取用某一列的值。读完本文你将掌握dictionary_get的完整语法、参数语义、STRUCT 返回值的使用技巧并能从 BE 端源码理解字典缓存探测cache probe的底层执行原理。背景字典对象与字典缓存在讲解dictionary_get之前有必要先明确字典对象这一概念。StarRocks 的全局字典方案详见 dict_mapping 文档 与 使用 AUTO_INCREMENT 和全局字典加速 COUNT(DISTINCT) 与 JOIN通常包含两个环节加载阶段通过dict_mapping函数在数据导入目标表时自动从字典表中取得指定键对应的映射值例如把 STRING 类型的订单号映射为 BIGINT 类型的自增 ID查询阶段dictionary_get负责从字典缓存dictionary cache中按 key 查询 value将映射关系再次取回用于查询计算。从源码结构看dictionary_get属于exprs_ext/dict下的扩展表达式与dictmapping_expr一同被注册在 expr_factory_dict_exprs_extension.cpp 中二者的执行都依赖 BE 节点的ComputeEnv所提供的DictionaryCacheManager参见 compute_env.cpp。也就是说dictionary_get查询的不是任意一张普通表而是 BE 内存中的字典缓存对象。语法dictionary_get(dictionary_object_name, key_expression_list, [NULL_IF_NOT_EXIST]) key_expression_list :: key_expression [, ...] key_expression :: column_name | const_value参数说明参数是否必选说明dictionary_object_name必选字典对象的名称字符串常量。key_expression_list必选所有 key 列的表达式列表可以是列名column name列表也可以是常量值const value列表多个表达式之间用逗号分隔。NULL_IF_NOT_EXIST可选当 key 在字典缓存中不存在时的行为取值为布尔型-truekey 不存在时返回NULL-false默认值key 不存在时抛出异常。需要特别留意的是当以列名作为 key 表达式时该列的数据会被逐行作为 key 去探测字典缓存当以常量作为 key 表达式时则是单次常量查询。key_expression_list中的表达式数量与顺序必须与字典对象的 key 列一一对应复合主键场景下尤为关键。返回值dictionary_get返回STRUCT 类型结构体中包含该 key 映射到的所有 value 列。因此可以通过两种方式取用其中某一列的值dictionary_get(...)[N]按下标取列N表示 value 列的位置从 1 开始计数dictionary_get(...).column_name按列名取列。使用示例以下示例均使用 dict_mapping 文档示例中的数据集字典表dictkey 列order_uuidvalue 列order_id_int映射关系为a1→1、a2→2、a3→3以及多 value 列的维度字典dimension_objkey 为1value 列为ProductName、Category、SubCategory、Brand、Color、Size。示例 1以列为 key批量查询整行映射查询字典对象dict_obj中order_uuid列每一行所映射的 value 列值MySQL SELECT dictionary_get(dict_obj, order_uuid) FROM dict; -------------------- | DICTIONARY_GET | -------------------- | {order_id_int:1} | | {order_id_int:3} | | {order_id_int:2} | -------------------- 3 rows in set (0.02 sec)可见函数按行执行每一行返回一个 STRUCT其中包含 value 列order_id_int及其映射值。示例 2以常量 key 查询查询字典对象dict_obj中 key 为a1映射的 value 列值MySQL SELECT dictionary_get(dict_obj, a1); -------------------- | DICTIONARY_GET | -------------------- | {order_id_int:1} | -------------------- 1 row in set (0.01 sec)示例 3查询多 value 列字典的完整映射查询字典对象dimension_obj中 key 为1映射的全部 value 列MySQL SELECT dictionary_get(dimension_obj, 1); ----------------------------------------------------------------------------------------------------------------- | DICTIONARY_GET | ----------------------------------------------------------------------------------------------------------------- | {ProductName:T-Shirt,Category:Apparel,SubCategory:Shirts,Brand:BrandA,Color:Red,Size:M} | ----------------------------------------------------------------------------------------------------------------- 1 row in set (0.01 sec)返回值是一个包含 6 个字段的 STRUCT。示例 4按下标取第一个 value 列查询字典对象dimension_obj中 key 为1映射的第 1 个 value 列MySQL SELECT dictionary_get(dimension_obj, 1)[1]; ------------------- | DICTIONARY_GET[1] | ------------------- | T-Shirt | ------------------- 1 row in set (0.01 sec)示例 5按下标取第二个 value 列查询字典对象dimension_obj中 key 为1映射的第 2 个 value 列MySQL SELECT dictionary_get(dimension_obj, 1)[2]; ------------------- | DICTIONARY_GET[2] | ------------------- | Apparel | ------------------- 1 row in set (0.01 sec)示例 6按列名取 value 列查询字典对象dimension_obj中 key 为1映射的ProductName列MySQL SELECT dictionary_get(dimension_obj, 1).ProductName; ---------------------------- | DICTIONARY_GET.ProductName | ---------------------------- | T-Shirt | ---------------------------- 1 row in set (0.01 sec)从源码看 dictionary_get 的执行原理FE 端生成 DICTIONARY_GET_EXPR 表达式节点在前端FE侧dictionary_get调用会被翻译为执行计划中的字典查询表达式。在 ExecDictionaryGet.java 中可以看到该表达式携带四类关键信息dict_id字典对象在 FE 侧分配的全局唯一 IDtxn_id字典缓存的事务版本号DictionaryCacheTxnId用于定位指定版本的缓存key_sizekey 列的数量供 BE 拆分 key 与 value 列使用null_if_not_exist即参数NULL_IF_NOT_EXIST。序列化时它被写成TExprNodeType.DICTIONARY_GET_EXPRtoThrift方法随查询计划下发给 BE。BE 端prepare 阶段加载字典缓存在 dictionary_get_expr.cpp 的DictionaryGetExpr::prepare中表达式初始化时会从RuntimeState的exec_env()-compute_env()获取DictionaryCacheManager若缺失则报错open dictionary expression failed, missing compute dictionary cache manager通过get_dictionary_schema_by_id(dict_id)取得字典的完整列 schemakey 列 value 列通过get_dictionary_by_version(dict_id, txn_id)取出指定事务版本的字典缓存DictionaryCachePtr依据key_size将 schema 拆分为_key_chunk与_value_chunk两个模板 chunk并预先构建一个可空的 STRUCT 列_nullable_struct_column作为输出模板。这里体现了字典缓存的版本一致性设计DictionaryCacheTxnId是由 FE 单调递增分配的事务号用于标识每一次字典缓存刷新任务同一字典在同一事务内所有 BE 上的读取保持一致详见 dictionary_cache_manager.h 中对DictionaryCacheManager的注释。BE 端evaluate 阶段探测缓存并组装 STRUCTDictionaryGetExpr::evaluate_checked是核心执行路径其流程如下逐一计算所有子表达式children得到 key 列数据参数校验若 key 列存在NULL值直接返回错误invalid parameter for dictionary_get function: get NULL paramenter即不允许用 NULL 作为 key将常量列展开unpack_and_duplicate_const_column为与输入行数一致的大小并剥离可空包装调用DictionaryCacheManager::probe_given_dictionary_cache对字典缓存进行批量探测得到 value chunk将 value chunk 的每一列追加到预构建的 STRUCT 列的各字段列中同时根据null_column标记整体 STRUCT 是否为空最终返回NullableColumnStructColumn。probe_given_dictionary_cache的实现同样位于 dictionary_cache_manager.h先通过DictionaryCacheUtil::encode_columns对 key schema 与 value schema 做主键编码PK_ENCODE类型使用PrimaryKeyEncoder的 V1 编码把多列 key 编码为单列紧凑表示再调用dictionary-lookup(...)在哈希表中查找若null_column nullptr即NULL_IF_NOT_EXISTfalse且 key 不存在则返回Status::NotFound(key not found in dictionary cache)异常若传入了null_column即NULL_IF_NOT_EXISTtrue则对该行标记为 NULL 并继续最后通过DictionaryCacheUtil::decode_columns解码回原始 value 列。底层存储与查找优化字典缓存的底层容器是phmap::parallel_flat_hash_map参见 dictionary_cache_manager.h 中DictionaryCacheImpl的定义并针对查询路径做了两类关键优化哈希策略数值型 key 使用带种子PhmapSeed1的标准哈希字符串VARCHAR/Slicekey 使用XXH3_64bits计算 64 位哈希预取prefetch加速lookup中以PREFETCHN 8为粒度做交错式预取——先预取 8 个 key、再预取 8 个哈希桶、最后执行实际查找从而隐藏访存延迟对于 Slice 类型的 value查找时先只保存指针待整批查找完成后再统一拷贝追加避免大字符串写入污染 CPU 缓存代码注释中明确说明了这一设计意图。此外value 为字符串类型时每条记录的内存布局会额外携带一个 1 字节的fast decode flag用于标记该值是否可以采用快速解码路径PRIMARY_KEY_DECODE_FAST/PRIMARY_KEY_DECODE_NORMAL/PRIMARY_KEY_DECODE_SKIP。DictionaryCacheUtil::precheck_value_encode会预先扫描字符串值中是否包含\0字节若包含则回退为普通解码方式保证解码正确性。与 dict_mapping 的搭配完整闭环dictionary_get与dict_mapping是一对配套函数前者负责写入侧加载时取映射后者负责读取侧查询时取映射dict_mapping面向字典表Primary Key 表在数据加载阶段把 key 映射为 ID 写入目标表适合导入即完成映射的场景dictionary_get面向字典缓存对象在查询阶段直接按 key 取回完整 valueSTRUCT适合需要实时引用映射关系的场景例如在查询中把维度字典的多个属性列拼装到结果中。两者的执行都依赖同一套DictionaryCacheManager缓存体系BE 侧实现在 dictionary_cache/dictionary_cache_manager.cpp共享相同的键值编码、版本管理与预取优化。在 FE 的单元测试 DictQueryFunctionTest.java 中可以看到对字典函数参数合法性的系统校验字典表名必须为db.tbl或tbl格式、字典表必须是 Primary Key 表、null_if_not_exist参数必须是布尔常量等这些约束同样适用于dictionary_get的参数解析与错误提示。使用注意事项key 不允许为 NULL从源码看任何 key 列包含 NULL 时dictionary_get都会直接报错请先通过过滤或 COALESCE 处理数据NULL_IF_NOT_EXIST 默认关闭默认情况下falsekey 不在字典缓存中会抛出异常而非返回 NULL这可以避免静默产生错误映射但也意味着查询前应确保 key 已存在于字典中需要容错时可显式传入trueSTRUCT 取列两种方式等价[N]按下标从 1 起、.column_name按列名选择哪种取决于你更关心列的顺序还是列名可读性复合 key 场景当字典对象包含多个 key 列时key_expression_list必须与 key 列一一对应参考dict_mapping对复合主键的处理所有主键都必须指定否则报参数数量错误缓存版本一致性dictionary_get读取的是 BE 内存中指定事务版本的字典缓存txn_id由 FE 分配因此其可见性与缓存刷新事务强相关属于内存态查询不直接读取底层字典表。【免费下载链接】starrocksThe worlds 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),仅供参考