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、容错查询函数dictGetOrNull、simpleJSON*系列别名、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/toDateTime64、untuple子查询、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
原文要点:改进了
dictGetHierarchy、dictIsIn的性能;新增函数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):
FunctionDictGetChildrenStrategy:name = "dictGetChildren",default_level = 1,固定 2 个参数(非可变参数)——因为"子节点"就是一层,无需 level 参数;FunctionDictGetDescendantsStrategy:name = "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) | 返回NULL(Nullable类型) |
dictGetOrNull的价值在于:无需预先知道默认值,也无需在 SQL 中拼接if(dictHas(...), ...)三元判断,直接利用 SQL 的NULL语义与isNull/ifNull/ 聚合函数组合,简化"查不到就跳过/标记缺失"的查询逻辑。
源码实现
FunctionDictGetOrNull定义于 src/Functions/FunctionsExternalDictionaries.h(L987),其实现非常巧妙,注释(L1050-L1061)阐明了三步法:
- 先调用
dictHas(内部复用FunctionDictHas)判断每个 key 是否存在于字典,得到 0/1 掩码并取反,作为 null map 的候选; - 再调用
dictGet(内部复用FunctionDictGetNoType<get>)取值——按契约,未命中的 key 返回默认值; - 将取反后的掩码包装为
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)。本版本为其注册了更贴近语义的新名字:
| 旧名(保留为别名) | 新名(主函数名) | 语义 |
|---|---|---|
visitParamHas | simpleJSONHas | 判断 JSON 中是否存在指定字段,返回UInt8(1/0) |
visitParamExtractUInt | simpleJSONExtractUInt | 从字段值解析UInt64,失败返回 0 |
visitParamExtractInt | simpleJSONExtractInt | 解析Int64 |
visitParamExtractFloat | simpleJSONExtractFloat | 解析Float64 |
visitParamExtractBool | simpleJSONExtractBool | 解析布尔(true/false),返回UInt8 |
visitParamExtractRaw | simpleJSONExtractRaw | 返回字段值的原始子串 |
visitParamExtractString | simpleJSONExtractString | 返回字段值的字符串(含反转义) |
源码佐证
注册逻辑分散在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.xml中background_fetches_pool_size),在SETTINGS层面废弃(DEPRECATED_BY_SERVER_CONFIG),同类还包括background_pool_size、background_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) |
formatDateTime与toDateTime64 | 修复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 时建议关注三点:
- ATTACH 顺序变更:执行
ALTER TABLE ATTACH PART[ITION]前,确认副本本地detached/目录中的残留 part 不会与预期冲突——新逻辑会优先基于校验和匹配本地 part(相关实现见 src/Storages/StorageReplicatedMergeTree.cpp); - 新函数可用性:
dictGetChildren、dictGetDescendants、dictGetOrNull仅对声明了层次属性(hierarchy)的字典有效,使用前请确认字典配置;simpleJSON*是visitParam*的新主名,两者可互换使用; - 服务端配置迁移:
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),仅供参考