news 2026/9/12 2:22:50

ClickHouse v21.4.4.30-stable 版本解析:层次字典新函数、simpleJSON 别名与复制 ATTACH 行为变更

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ClickHouse v21.4.4.30-stable 版本解析:层次字典新函数、simpleJSON 别名与复制 ATTACH 行为变更

ClickHouse v21.4.4.30-stable 版本解析:层次字典新函数、simpleJSON 别名与复制 ATTACH 行为变更

【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse

本篇技术指南围绕 ClickHouse 官方 changelog(docs/changelogs/archive/v21.4.4.30-stable.md)展开,逐条解读 v21.4.4.30-stable 相对 v21.4.3.21-stable 的变更:包括新增的层次字典函数dictGetChildren/dictGetDescendants、容错查询函数dictGetOrNullsimpleJSON*系列别名、background_fetches_pool_size默认值调整,以及ATTACH PART[ITION]查找顺序的向后不兼容变更。读者阅读后可掌握这些新函数的语义、用法与源码级实现原理,并了解升级该补丁版本时需要留意的行为差异。

版本概览与定位

v21.4.4.30-stable 是 ClickHouse 21.4 系列的一个补丁(patch)版本,属于stable分支,其变更全部为"backport"(从主分支回移植到稳定分支)而来。本版不含重大架构改动,聚焦三类内容:

  • 新功能(New Feature):围绕外部字典(External Dictionary)扩展了三个函数;
  • 改进(Improvement):JSON 解析函数别名、复制 fetch 线程池默认值、正则匹配阈值;
  • Bug Fix:AWS 请求超时、formatDateTime/toDateTime64untuple子查询、Materialized View、Markdown 输出格式等修复。

该文件位于仓库 docs/changelogs/archive/ 目录,属于 2022 年归档的 2021 年发布记录(标题归档为 2022 Changelog)。以下按 changelog 原始分类逐节解读,并给出对应源码位置佐证。

向后不兼容变更:ATTACH PART 优先在本地 detached/ 目录查找

原文要点:现在,处理ALTER TABLE ATTACH PART[ITION]命令的副本会先在自身的detached/目录中查找,然后再从其他副本拉取数据。作为实现细节,复制日志(replicated log)中引入了一条新命令ATTACH_PART。数据 part 通过其**校验和(checksums)**进行搜索与比对(PR #18978)。

行为变化

旧行为下,执行ALTER TABLE ATTACH PART时副本会直接向其他副本发起 fetch;新行为下,每个副本优先检查本地detached/目录中是否存在与目标 part 匹配(分区 ID 相同、名称合法)的候选 part,若存在且校验和一致则直接本地挂载,避免跨网络拷贝,显著降低网络开销与 ZooKeeper 往返。

源码佐证

复制日志条目类型在 src/Storages/MergeTree/ReplicatedMergeTreeLogEntry.cpp 中定义,ATTACH_PART被映射为字符串"ATTACH_PART",并在解析日志条目时赋给type(见该文件 L44、L97、L265)。

本地查找逻辑位于 src/Storages/StorageReplicatedMergeTree.cpp 的attachPartHelperFoundValidPart方法(L2467 起):它会列出detached/目录下的所有 part(getDetachedParts()),过滤掉名称非法、带前缀或带_tryN后缀的残留文件,以及不属于目标分区的 part,再按目标 part 名构建数据 part 并比对校验和,只有匹配才返回。

⚠️ 升级注意:这是本版本唯一标记为Backward Incompatible的变更。从源码可推断,若本地detached/中存在校验和不一致的残留 part,新逻辑不会误用它(校验和不一致会被拒绝),因此主要影响是执行顺序而非数据安全性;但运维脚本若依赖"ATTACH 总是从其他副本拉取",需要感知这一顺序变化。

新功能一:层次字典查询函数 dictGetChildren 与 dictGetDescendants

原文要点:改进了dictGetHierarchydictIsIn的性能;新增函数dictGetChildren(dictionary, key)dictGetDescendants(dictionary, key, level)dictGetChildren返回所有子节点组成的索引数组,是dictGetHierarchy的逆变换;dictGetDescendants返回所有后代,相当于对dictGetChildren递归应用level次,level为 0 时等价于无穷大(PR #22096)。

函数语义

在层次字典(hierarchical dictionary)中,dictGetHierarchy(dict, key)返回从 key 自身到根节点的祖先链(含 key 自身),dictIsIn(dict, child, parent)判断 child 是否为 parent 的后代。v21.4.4.30 补齐了"下行"方向的能力:

  • dictGetChildren(dictionary, key):返回指定 key 的直接子节点ID 数组(Array),即dictGetHierarchy的反向映射;
  • dictGetDescendants(dictionary, key, level):返回 key 的全部后代ID 数组;level表示递归层数,level = 0表示不限深度(无穷),level = 1等价于直接子节点。

三者配合可实现完整的树形结构双向遍历:向上查祖先用dictGetHierarchy,向下查子孙用dictGetDescendants

源码实现

实现位于 src/Functions/FunctionsExternalDictionaries.h,核心是两个策略类(L1444-L1458):

  • FunctionDictGetChildrenStrategyname = "dictGetChildren"default_level = 1,固定 2 个参数(非可变参数)——因为"子节点"就是一层,无需 level 参数;
  • FunctionDictGetDescendantsStrategyname = "dictGetDescendants"default_level = 0(即默认无限深度),可变参数(2 或 3 个)。

两者共用FunctionDictGetDescendantsOverloadResolverImpl(L1461)这一重载解析器:其buildImpl中,若用户显式传入第三个参数,则校验其为常量无符号整数(负值会抛ILLEGAL_TYPE_OF_ARGUMENT),并将其解析为level;随后通过dictionary->getHierarchicalIndex()获取父→子索引(DictionaryHierarchicalParentToChildIndexPtr),最终在FunctionDictGetDescendantsExecutable::executeImpl(L1385)中调用dictionary->getDescendants(...)完成递归查询。返回类型固定为Array(层次属性类型)(会移除 Nullable 包装),见getReturnTypeImpl(L1520)。

值得注意的前提约束:FunctionDictHelper::checkDictionaryHierarchySupport(L119)要求字典必须声明了层次属性(hierarchical_attribute_index)且支持层次结构,否则会抛UNSUPPORTED_METHOD异常。

使用示例

-- 假设字典 region 的层次属性指向父区域 ID -- 返回 key=1 的所有直接子区域 SELECT dictGetChildren('region', 1); -- 返回 key=1 的三层以内的所有后代 SELECT dictGetDescendants('region', 1, 3); -- 返回 key=1 的所有后代(不限制深度) SELECT dictGetDescendants('region', 1, 0);

新功能二:容错查询函数 dictGetOrNull

原文要点:新增dictGetOrNull,用法与dictGet相同,但在字典中找不到 key 时返回Null(PR #22413)。

与 dictGet / dictGetOrDefault 的差异

函数key 未命中时的行为
dictGet返回字典属性声明的默认值(或字典配置中指定的默认值)
dictGetOrDefault(dict, attr, key, default)返回用户显式指定的默认表达式
dictGetOrNull(dict, attr, key)返回NULLNullable类型)

dictGetOrNull的价值在于:无需预先知道默认值,也无需在 SQL 中拼接if(dictHas(...), ...)三元判断,直接利用 SQL 的NULL语义与isNull/ifNull/ 聚合函数组合,简化"查不到就跳过/标记缺失"的查询逻辑。

源码实现

FunctionDictGetOrNull定义于 src/Functions/FunctionsExternalDictionaries.h(L987),其实现非常巧妙,注释(L1050-L1061)阐明了三步法:

  1. 先调用dictHas(内部复用FunctionDictHas)判断每个 key 是否存在于字典,得到 0/1 掩码并取反,作为 null map 的候选;
  2. 再调用dictGet(内部复用FunctionDictGetNoType<get>)取值——按契约,未命中的 key 返回默认值;
  3. 将取反后的掩码包装为ColumnNullable的 null map:未命中的行显示为NULL

实现细节上还处理了两种特例:当查询多个属性(结果类型为Tuple)时,对每个元组元素分别包一层 Nullable(L1089-L1115);当属性本身已是 Nullable 时,通过addNullMap合并两张 null map(L1118-L1128)。返回类型由getReturnTypeImpl(L1025)决定:单属性返回Nullable(属性类型),多属性返回元素均为 Nullable 的Tuple

使用示例

-- 未命中的 key 返回 NULL 而非默认值 SELECT dictGetOrNull('region', 'region_name', toUInt64(id)) AS name FROM user_ids; -- 与 ifNull 组合,自定义缺失回退 SELECT ifNull(dictGetOrNull('region', 'region_name', id), '未知区域') FROM user_ids;

改进一:simpleJSON* 函数别名统一 JSON 解析命名

原文要点:为visitParam / visitParamExtract{UInt, Int, Bool, Float, Raw, String}添加别名simpleJSONExtract / simpleJSONHas(PR #21519)。

别名映射

visitParam*系列是 ClickHouse 传统的"简单 JSON"解析函数(基于字符串搜索而非完整 JSON 解析器,要求 JSON 中字段以"field":形式出现且每行一个 JSON)。本版本为其注册了更贴近语义的新名字:

旧名(保留为别名)新名(主函数名)语义
visitParamHassimpleJSONHas判断 JSON 中是否存在指定字段,返回UInt8(1/0)
visitParamExtractUIntsimpleJSONExtractUInt从字段值解析UInt64,失败返回 0
visitParamExtractIntsimpleJSONExtractInt解析Int64
visitParamExtractFloatsimpleJSONExtractFloat解析Float64
visitParamExtractBoolsimpleJSONExtractBool解析布尔(true/false),返回UInt8
visitParamExtractRawsimpleJSONExtractRaw返回字段值的原始子串
visitParamExtractStringsimpleJSONExtractString返回字段值的字符串(含反转义)

源码佐证

注册逻辑分散在src/Functions/下的visitParam*.cpp文件中,模式统一:先registerFunction<FunctionSimpleJSONXxx>(documentation)注册主函数名,再registerAlias("visitParamXxx", "simpleJSONXxx")注册旧名。例如:

  • src/Functions/visitParamExtractUInt.cpp(L58-L59):factory.registerFunction<FunctionSimpleJSONExtractUInt>(documentation); factory.registerAlias("visitParamExtractUInt", "simpleJSONExtractUInt");
  • src/Functions/visitParamHas.cpp(L59-L60):factory.registerFunction<FunctionSimpleJSONHas>(documentation); factory.registerAlias("visitParamHas", "simpleJSONHas");

两份文件的文档元数据均标注IntroducedIn = {21, 4}(21.4 引入),与本 changelog 吻合;函数分类为Category::JSON。函数本体为FunctionsStringSearch<ExtractParamImpl<...>>模板(见 src/Functions/visitParamExtractUInt.cpp 的FunctionSimpleJSONExtractUInt定义),底层复用了字符串搜索与提取的通用实现。

使用示例

CREATE TABLE jsons (json String) ENGINE = MergeTree ORDER BY tuple(); INSERT INTO jsons VALUES ('{"foo":"4e3"}'), ('{"foo":3.4}'), ('{"foo":5}'), ('{"baz":2}'); -- 解析 UInt:字符串 "4e3" 从开头解析出 4;3.4 截断为 3;"not1number" 解析失败返回 0 SELECT simpleJSONExtractUInt(json, 'foo') FROM jsons ORDER BY json; -- 0 / 3 / 4 / 5 -- 判断字段是否存在 SELECT simpleJSONHas(json, 'foo') FROM jsons; -- 存在返回 1 SELECT simpleJSONHas(json, 'bar') FROM jsons; -- 不存在返回 0

说明:simpleJSONExtractUInt(json, 'foo')"4e3"这类字符串字段的开头尝试解析数字得到 4,而3.4会按无符号整数截断为 3,"not1number"与缺失字段均返回 0——此行为与官方文档示例(src/Functions/visitParamExtractUInt.cpp 中REGISTER_FUNCTION内的示例)一致。

改进二:background_fetches_pool_size 默认值调整为 8

原文要点:将background_fetches_pool_size设置为 8,更适合生产环境中频繁小批量插入ZooKeeper 集群较慢的场景(PR #22945)。

背景与影响

复制表(ReplicatedMergeTree)在副本间同步数据 part 时使用独立的"后台拉取"线程池。在 21.4 之前该池默认较小,若副本需要大量并发 fetch(例如频繁小批量插入导致产生大量小 part,或 ZooKeeper 响应慢导致拉取任务积压),容易出现 fetch 饥饿(pool starving)。调大到 8 后,单副本可并行拉取的 part 数更多,吞吐与恢复速度更优。

在 src/Storages/MergeTree/registerStorageMergeTree.cpp(L4508 附近)的引擎文档中明确说明:ReplicatedMergeTree引擎为复制 fetch 使用独立线程池,池大小由background_fetches_pool_size服务端设置限制,可通过重启服务器调整

源码佐证

在 src/Core/Settings.cpp(L9427)中,该设置被标记为:

MAKE_DEPRECATED_BY_SERVER_CONFIG(M, UInt64, background_fetches_pool_size, 8)

即默认值为8,且已迁移为服务端配置(config.xmlbackground_fetches_pool_size),在SETTINGS层面废弃(DEPRECATED_BY_SERVER_CONFIG),同类还包括background_pool_sizebackground_schedule_pool_size等(见 src/Core/Settings.cpp)。该设置的位置与命名还被复制相关任务的报错信息引用——当 fetch 池饥饿时会提示用户检查该参数,例如 src/Storages/MergeTree/MergeFromLogEntryTask.cpp(L157)。

配置方式

在服务器config.xml<merge_tree>(或顶层)中显式覆盖:

<background_fetches_pool_size>8</background_fetches_pool_size>

修改后需要重启clickhouse-server生效。对慢 ZooKeeper 或高 part 数环境,可进一步调大,但需权衡内存与 ZooKeeper 会话负担。

改进三:extractAllGroupsHorizontal 匹配数量阈值提高

原文要点:提高了函数extractAllGroupsHorizontal结果中最大匹配数量的阈值(PR #23036)。

extractAllGroupsHorizontal(s, regexp)使用正则表达式的捕获组,将一行输入按组织成二维字符串数组(横向布局:外层数组按组 id,内层数组为该组的所有匹配);其纵向版本extractAllGroupsVertical则按匹配出现顺序组织。实现见 src/Functions/extractAllGroups.h,其中对每行匹配数有保护性上限,由设置regexp_max_matches_per_row(src/Functions/extractAllGroups.h)控制,超限会抛TOO_LARGE_ARRAY_SIZE异常(L198-L201)。本版本提高的即此默认阈值,降低了大文本多匹配场景下误报"Too many matches per row"的概率。

-- 示例(源码注释中原样给出) SELECT extractAllGroupsHorizontal('abc=111, def=222, ghi=333', '("[^"]+"|\w+)=("[^"]+"|\w+)'); -- 返回 [['abc', 'def', 'ghi'], ['111', '222', '333']]

Bug Fix 修复清单

本版本包含 6 项 bug 修复,均为 backport:

修复内容说明
AWS 辅助请求无限等待修复 S3/对象存储相关辅助请求可能无限阻塞的问题(PR #22594)
formatDateTimetoDateTime64修复formatDateTime()处理DateTime64%C(世纪)格式符的问题;修复toDateTime64()处理大数值与非零 scale 的问题(PR #22937)
untuple子查询报错修复子查询使用untuple时可能出现Cannot find column in ActionsDAG result错误(PR #22991)
clickhouse-client 建议信息移除客户端交互模式建议(suggestions)中非必要细节(PR #23040)
Materialized View 报错修复物化视图从 Atomic 数据库 detach 后再 attach 时出现Table .inner_id... doesn't exist错误(PR #23047)
Markdown 格式对齐修复Markdown输出格式中表格单元格值被居中对齐的问题,现改为默认对齐(PR #23096)

其中untuple相关修复对应 src/Functions 目录下的untuple函数族;Markdown 格式修复对应 src/Formats 目录下的Markdown输出格式实现,涉及 src/Formats/OutputFormats 中的格式写入逻辑。两类修复对依赖FORMAT Markdown导出表格的报表场景有实际影响。

Build / Testing / Packaging 改进

  • ppc64le 平台 openldap:在 ppc64le(PowerPC 64-bit Little Endian)架构上启用捆绑(bundled)的 openldap(PR #22487)。这属于构建/打包层面的可移植性改进,仅影响在 ppc64le 平台自行编译的用户;LDAP 认证功能(src/Access中基于 openldap 的 LDAP 身份认证)此前在该架构上默认不可用,本版本起随源码构建可用。

升级建议与总结

综合本 changelog,升级到 v21.4.4.30-stable 时建议关注三点:

  1. ATTACH 顺序变更:执行ALTER TABLE ATTACH PART[ITION]前,确认副本本地detached/目录中的残留 part 不会与预期冲突——新逻辑会优先基于校验和匹配本地 part(相关实现见 src/Storages/StorageReplicatedMergeTree.cpp);
  2. 新函数可用性dictGetChildrendictGetDescendantsdictGetOrNull仅对声明了层次属性(hierarchy)的字典有效,使用前请确认字典配置;simpleJSON*visitParam*的新主名,两者可互换使用;
  3. 服务端配置迁移background_fetches_pool_size等一批后台线程池设置已标记为DEPRECATED_BY_SERVER_CONFIG,建议统一迁移到服务器配置文件中配置(默认值已上调为 8)。

该版本整体属于低风险补丁升级:仅一处向后不兼容(ATTACH 查找顺序),其余为新函数与默认参数优化,适合在生产环境按正常发布节奏滚动升级。

【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 2:18:59

VMD与小波融合的信号去噪方法及Python实现

做信号去噪这行时间长了&#xff0c;你会发现一个挺尴尬的事实&#xff1a;没有任何一种单一算法能通吃所有场景。前阵子我处理一组非线性非平稳的振动信号&#xff0c;数据量大&#xff0c;噪声还混着随机脉冲和白噪声&#xff0c;先后试了VMD&#xff08;变分模态分解&#x…

作者头像 李华
网站建设 2026/9/12 2:18:44

2026企业级AI Agent落地实践:从LangGraph到MCP协议的关键指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 2:18:21

PyQt5二手房价格预测系统:从数据清洗到GUI交付

简介&#xff1a;本资源是一套完整的二手房价格分析与预测系统实战项目&#xff0c;面向Python初学者及数据分析入门者&#xff0c;聚焦真实业务场景下的数据清洗、特征工程、模型训练与可视化呈现全流程。项目基于PyQt5构建图形界面&#xff0c;集成Matplotlib图表展示、Sciki…

作者头像 李华
网站建设 2026/9/12 2:15:21

FD6818_MAIN驱动深度解析:射频时序敏感型状态机设计与GB28181对讲集成

简介&#xff1a;本资源是一份面向嵌入式开发工程师与无线通信系统学习者的FD6818射频芯片驱动代码实现&#xff0c;聚焦对讲机等短距无线设备的底层通信开发。核心解决射频芯片初始化、频点配置、收发控制及抗干扰策略等关键问题&#xff0c;适用于基于ARM或8051类MCU的硬件平…

作者头像 李华
网站建设 2026/9/12 2:10:10

AI技术栈解析:从机器学习到智能体的演进与应用

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华