ClickHouse v20.10.6.27-stable 发布解读:修复日志、性能改进与源码级详解
【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse
本文基于 ClickHouse 官方发布说明 docs/changelogs/archive/v20.10.6.27-stable.md 编写,梳理 v20.10.6.27-stable 相对 v20.10.5.10-stable 的 1 项性能改进、18 项缺陷修复与 1 项构建/测试改进,并结合当前仓库源码深入解析关键修复的底层原理与受影响配置项。读完本文,你将能够:判断该版本修复是否影响自己的业务场景;理解optimize_aggregators_of_group_by_keys、optimize_redundant_functions_in_order_by等优化开关的取舍;掌握fuzzBits函数的正确用法与设计意图;以及了解in_memory_parts_enable_wal等历史设置在现代版本中的状态演变。
版本概览
v20.10.6.27-stable 是 ClickHouse 20.10 系列的一个稳定补丁版本,属于典型的“小版本修复”节奏——不引入新功能,而是聚焦于稳定性、正确性与性能回归修复。这类补丁版本通常通过 backport(回溯移植)机制,将主线分支上已验证的修复合入稳定分支。
从发布说明的结构看,该版本包含三个部分:
| 分类 | 数量 | 主题 |
|---|---|---|
| Performance Improvement | 1 | 修复Merge表读取大量MergeTree表的性能 |
| Bug Fix | 18 | 涵盖查询优化器、聚合、存储引擎、客户端协议、复制等模块 |
| Build/Testing/Packaging | 1 | 更新嵌入式时区数据至 2020d |
下文将按照这三个分类,结合源码逐项解析。
性能改进:Merge 表读取大量 MergeTree 表的性能修复
本次唯一的性能改进项是针对Merge表的:修复了从覆盖大量MergeTree表的Merge表读取数据的性能问题(对应问题 #7748)。在修复前,当Merge表下挂数千张甚至更多MergeTree表时,查询开销会显著放大,影响正常使用。
从原理上看,Merge表引擎本身不存储数据,而是在查询时将请求路由到下挂的多个物理表(如MergeTree系列表)。当表数量巨大时,元数据遍历、分区裁剪和流(stream)编排的开销会急剧上升。该修复通过优化读取路径上对这些底层表的处理方式,显著降低了这种场景下的查询开销。
对于大量使用Merge表聚合多张历史分表、或使用Merge表作为视图层的部署场景,升级到该版本后应能直接感受到查询性能的提升。相关实现位于 src/Storages 目录下的 Merge 表引擎代码中。
缺陷修复详解(18 项)
聚合与 ORDER BY 优化器相关
1. GROUP BY 键聚合器优化与 JOIN 的兼容性问题
修复了开启optimize_aggregators_of_group_by_keys设置时,与 JOIN 一起使用导致结果错误的问题(对应问题 #12604)。
该设置的定义位于 src/Core/Settings.cpp:
Eliminates min/max/any/anyLast aggregators of GROUP BY keys in SELECT section(在 SELECT 部分消除针对 GROUP BY 键的 min/max/any/anyLast 聚合器)
这是一个默认开启(true)的优化:既然某个列已经是 GROUP BY 的键,那么对它的min、max、any、anyLast聚合结果必然等于该键本身,因此可以直接消除聚合器、直接取键值,避免无谓的聚合计算。然而在引入 JOIN 之后,情况变得复杂——当聚合键来自 JOIN 的另一侧、或 JOIN 会改变行的对应关系时,直接消除聚合器可能产生错误结果。本次修复即针对这一组合场景。
2. 单调函数的 ORDER BY 优化缺陷
修复了使用单调函数(monotonous functions)进行ORDER BY优化时的错误(对应问题 #16107)。单调函数优化是 ClickHouse 查询优化器的一项经典技巧:如果排序列经过一个单调函数变换后仍然保持顺序,则可以重写排序表达式以利用索引或减少排序开销。但当优化逻辑对单调性的判定不准确时,会导致排序结果错误。修复后,只有在函数确实保持单调性的前提下才会应用该优化。
3.optimize_redundant_functions_in_order_by相关修复
修复了开启optimize_redundant_functions_in_order_by设置时ORDER BY的缺陷。该设置同样定义于 src/Core/Settings.cpp,默认开启:
Remove functions from ORDER BY if its argument is also in ORDER BY(如果 ORDER BY 中同时存在函数及其参数,则移除该函数)
举例说明:若查询为ORDER BY x, f(x),由于x已确定顺序,f(x)的顺序也随之确定(对同一x值,f(x)结果相同),因此f(x)是冗余的,可以被移除,从而减少排序工作量。修复前的缺陷在于某些情况下移除逻辑判断有误,可能导致排序语义变化。
4. DISTINCT 后出现重复行
修复了因错误优化导致DISTINCT之后仍出现重复数据的问题(对应问题 #17294)。DISTINCT的去重正确性依赖于优化器在重写查询时保持语义不变;该修复确保相关优化(涉及表达式提升、常量折叠等)不再破坏去重语义。涉及两次 backport(PR #17296 与 PR #17439),说明该问题在修复过程中经历了额外的验证。
存储引擎与内存管理相关
5. TDigest 无界增长
修复了TDigest(分位数聚合)内存无界增长的问题(PR #16680)。TDigest 是一种用于高效计算近似分位数(如quantile、quantiles系列聚合函数)的紧凑数据结构,其核心优势是内存占用可控。但当数据分布或合并路径触发特定条件时,旧实现可能出现 centroid(质心)数量持续增长、内存占用失控的情况。修复通过更严格地控制合并与压缩策略,恢复了“有界内存”的保证。
6. mark cache 大小被低估
修复了 ClickHouse 低估 mark cache 大小的问题(PR #17496)。mark 文件记录着数据块在列文件中的偏移量,用于加速读取定位。当存在大量 tiny files(小文件,每个只含少量 mark)时,mark_cache_size的估算会失准,导致实际缓存占用远超配置,可能引发内存压力或缓存频繁失效。该修复改进了 mark 缓存占用量的统计口径,使缓存管理更准确。
7.in_memory_parts_enable_wal与 WAL 恢复
修复了当in_memory_parts_enable_wal被禁用时,ClickHouse 仍会从 WAL(Write-Ahead Log)恢复 part 的问题(PR #17802)。该设置在旧版本中用于控制 MergeTree 内存 part 是否启用 WAL 持久化。
值得特别说明的是:在当前仓库的 src/Storages/MergeTree/MergeTreeSettings.cpp 中,该设置已通过MAKE_OBSOLETE_MERGE_TREE_SETTING标记为废弃设置,其原因是内存 part 机制在现代版本中已被整体移除(数据 part 不再常驻内存),相关配置自然失去意义。这提醒使用者:查阅历史版本的修复时,需注意该设置在现代 ClickHouse 中已不可用。
8. ALTER 查询挂起
修复了当对应 mutation 在另一个副本上被 kill 时,ALTER查询可能挂起的问题(对应问题 #16953)。mutation(如ALTER TABLE ... UPDATE/DELETE)在副本间协调执行,如果某个副本上的 mutation 被终止,而其他副本仍在等待其完成信号,就可能出现死锁式挂起。修复确保副本间能正确感知 mutation 的终止状态并及时解除等待。
9. 非分区表的 clickhouse-copier 段错误
修复了clickhouse-copier处理非分区表(non-partitioned table)时抛出段错误(segfault)的问题(对应问题 #15235)。clickhouse-copier是用于在集群间复制数据的工具,其复制任务按分区粒度设计;当目标/源表没有分区时,旧的实现缺少对“无分区”这一特殊状态的防御,导致空指针解引用崩溃。修复补充了非分区表场景下的空值检查与容错逻辑。
查询执行与表达式相关
10. fuzzBits 函数缺陷
修复了fuzzBits函数(PR #17051,对应问题 #16980)。该函数用于按概率翻转字符串中每个二进制位,常用于测试、模糊测试(fuzzing)或制造近似数据。其实现位于 src/Functions/fuzzBits.cpp,核心逻辑为逐位以概率p翻转输入字节(src/Functions/fuzzBits.cpp)。
函数签名为fuzzBits(s, p):
s:String或FixedString类型的输入;p:翻转每个 bit 的概率,取值范围0.0到1.0(Float*类型);- 返回值:与
s同类型的“被模糊化”字符串。
官方文档示例(src/Functions/fuzzBits.cpp):
SELECT fuzzBits(materialize('abacaba'), 0.1) FROM numbers(3)输出示例(每次执行结果随机):
┌─fuzzBits(materialize('abacaba'), 0.1)─┐ │ abaaaja │ │ a*cjab+ │ │ aeca2A │ └───────────────────────────────────────┘该函数在 src/Functions/fuzzBits.cpp 中标注于 v20.5 引入,归类为随机数类函数(RandomNumber)。修复主要针对特定输入下翻转逻辑的边界错误。
11.CREATE TABLE ... AS表函数崩溃
修复了CREATE TABLE ... AS some_table查询的崩溃:当some_table本身是通过AS table_function()创建时(对应问题 #16944)。这种“以表函数创建的表为模板再建表”的链式场景在旧实现中对表元数据的解析存在遗漏,导致崩溃;修复后能够正确处理该嵌套场景。
12. ColumnConst 比较崩溃
修复了ColumnConst(常量列)比较导致的崩溃(对应问题 #17088)。常量列在 ClickHouse 内部是特殊的列表示形式(单值广播),当两个常量列参与比较操作且走特定代码路径时,旧实现可能对存储布局的假设不成立而崩溃。修复确保常量列比较走正确的短路逻辑。
13. JOIN 表与 LowCardinality 读取崩溃
修复了从JOIN表读取LowCardinality类型数据时的崩溃(对应问题 #17228)。JOIN表引擎将数据驻留内存用于连接,LowCardinality类型通过字典编码压缩基数。两者组合时,若内存连接期间字典与列数据生命周期管理不当,会触发崩溃。修复后LowCardinality列在JOIN表中可正常读取。
14. 集合索引(set index)失效
修复了子查询中存在常量列(const columns)时 set index 失效的问题(对应问题 #17246)。SET类型跳数索引(skip index)用于在查询条件命中集合时跳过不满足条件的数据块。当子查询返回常量列时,旧的索引维护逻辑会错误地使索引失效,导致索引功能异常或结果错误。修复确保常量列场景下索引仍能正确构建与使用。
网络、客户端与复制相关
15. 可取消远程查询的无关网络错误
修复了远程查询(如带LIMIT的查询)在执行中被取消时产生不必要网络错误的问题(PR #17006)。分布式查询中,协调节点在收到足够结果后(如LIMIT已满足)会取消远端查询;旧实现中这种正常的取消可能被记录为网络错误甚至干扰查询结果。修复将“主动取消”与“异常中断”正确区分,避免误报。
16. clickhouse-client 多行模式下的单行注释
修复了交互式多行查询模式下,单行注释(--或#)被错误地延伸到整个查询末尾的问题(对应问题 #13654)。例如多行输入中某行出现-- comment,旧实现会将其后的所有内容(包括后续行)全部当作注释,导致查询被截断。修复后注释仅作用于其所在行。
17.ON CLUSTER与环形(交叉)复制检测的竞态条件
修复了在pool_size > 1时,因竞态条件导致ON CLUSTER查询错误判定集群是否为环形(cross-)复制的问题(PR #17640)。ClickHouse 通过查询系统表判断集群复制拓扑,多个并发连接(pool_size > 1)下,读取的拓扑信息可能来自不同连接、相互不一致,从而误判。修复确保判定过程基于一致的集群快照。
18. 其他修复
- WSL 下 Atomic 数据库的 RENAME:修复了 ClickHouse 在 Windows Subsystem for Linux(WSL)上运行时,
Atomic数据库中执行RENAME查询报Function not implemented错误的问题(对应问题 #17661)。该问题源于 WSL 文件系统对某些 rename 语义的支持差异。 - MySQL 连接恢复失败:修复了 ClickHouse 无法恢复与 MySQL 服务器连接的问题(PR #17681)。该修复影响
mysql表函数与 MySQL 数据库引擎,此前连接中断后重连可能持续失败。 - MySQL 协议 INSERT 返回受影响行数:修复了通过 MySQL 协议执行
INSERT时总是返回 0 的问题(对应问题 #16605),修复后正确返回受影响(插入)的行数。这对于使用 MySQL 客户端/驱动接入 ClickHouse、且依赖 affected rows 统计的应用程序尤为重要。
构建/测试/打包改进:时区数据更新
本版本的打包改进为:将嵌入式时区数据更新至 2020d 版本,同时将 cctz 库更新到最新的 master 分支(PR #17204)。
时区数据由 IANA 每年随各地政策(夏令时规则调整、时区合并等)多次发布。ClickHouse 内置时区数据,供toDateTime、toTimeZone等函数及DateTime类型使用。更新至 2020d 意味着使用内置时区的用户无需依赖操作系统时区库即可获得正确的转换结果。当前仓库中时区数据的加载与处理逻辑可参见 base/base/time.h 及 cctz 相关实现(contrib 下的 cctz 依赖)。
升级建议与实操要点
综合以上修复,以下是针对该版本的实践建议:
- 重度使用
Merge表聚合大量MergeTree表的用户应优先升级,这是本版本唯一的性能改进点,收益直接。 - 通过 MySQL 协议写入且依赖 affected rows 的应用程序,升级后 INSERT 返回值行为会变化(由 0 变为真实行数),需确认应用侧是否对该值有依赖或断言。
- 开启默认优化开关的默认配置无需调整:
optimize_aggregators_of_group_by_keys、optimize_redundant_functions_in_order_by均为默认开启(true)的优化,本版本修复了它们在特定组合(JOIN、单调函数)下的正确性问题。若曾因这些缺陷手动关闭过上述设置,升级后可重新评估是否恢复默认开启以提升查询性能。两者的完整定义与语义可查阅 src/Core/Settings.cpp 与 src/Core/Settings.cpp。 in_memory_parts_enable_wal已是废弃设置:虽然本版本修复了该设置禁用时仍从 WAL 恢复 part 的问题,但现代 ClickHouse 已通过MAKE_OBSOLETE_MERGE_TREE_SETTING将其标记为废弃(src/Storages/MergeTree/MergeTreeSettings.cpp),配置文件中如仍包含该设置,建议在升级主版本时移除。- 使用
fuzzBits做数据模糊化或测试时,注意其概率参数p需在[0.0, 1.0]范围内,且返回结果具有随机性,不适合用于需要确定性的场景。
小结
v20.10.6.27-stable 是 ClickHouse 20.10 稳定分支的一次扎实的“维稳”更新:修复了查询优化器与 JOIN、DISTINCT、ORDER BY 组合下的正确性问题,处理了 TDigest 内存增长、mark cache 估算、WAL 恢复等存储层缺陷,并完善了 MySQL 协议、客户端交互、复制协调等外围行为。对于生产环境,此类补丁版本通常建议在充分回归测试后尽快跟进;对于已升级到更新主版本的用户,本文对相关设置与函数行为的源码解析仍可作为理解 ClickHouse 内部机制的参考。
<system_warning>友情提示:由于 v20.10.6.27-stable 距今时间较久,且当前仓库代码已大幅演进,文中涉及的源码行号与设置定义均以当前仓库 src/Core/Settings.cpp、src/Functions/fuzzBits.cpp、src/Storages/MergeTree/MergeTreeSettings.cpp 的实际内容为准;如需验证该版本的精确行为,建议以对应版本的发布包与官方 changelog 为准。</system_warning>
【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考