ClickHouse v25.8.20.4-lts 补丁版本解析:递归 CTE 崩溃、Patch Parts 列序与 O_DIRECT 读缓冲 seek 三大修复
【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse
本篇技术指南聚焦 ClickHouse 官方 LTS 分支的最新补丁版本v25.8.20.4-lts(提交2e1cd6354ae),逐条拆解该版本相对 v25.8.19.20-lts 引入的三项 Bug Fix:递归 CTE 搭配remote()与view()时的段错误(segfault)、MergeTree 增量 Patch Parts 列顺序不一致导致的LOGICAL_ERROR,以及O_DIRECT模式下异步文件读缓冲的错误 seek 定位。读完本文,你将理解每个缺陷的触发场景、根因所在,以及仓库中对应的底层实现与修复落点,从而能够在生产升级时准确评估影响面。
本文内容以仓库中的官方发布记录 docs/changelogs/v25.8.20.4-lts.md 为主体骨架,并结合src/下相关模块源码进行原理层面的印证与扩展。
版本背景与升级建议
v25.8.20.4-lts 属于 ClickHouse 25.8 系列的 LTS(长期支持)维护分支。从发布记录的版本对比行可以看出,它是 v25.8.19.20-lts 的直接后继版本,全部变更均为Bug Fix(用户可见的官方稳定版行为异常修复),未引入新特性或破坏性变更。因此对于运行 25.8 LTS 系列的集群,这是一个低风险、应尽快合入的补丁升级;修复涉及的三个模块分别为:
| 编号 | 修复内容 | 涉及模块 | 上游跟踪 |
|---|---|---|---|
| 1 | 递归 CTE +remote()+view()组合查询段错误 | Analyzer / 查询执行 | #99081(随 #99441 回溯) |
| 2 | Patch Parts 列顺序不匹配导致的LOGICAL_ERROR | MergeTree 变更转换 | #99164(随 #99989 回溯) |
| 3 | O_DIRECT下异步读缓冲 seek 定位错误 | IO 异步读 | #99678(随 #99950 回溯,关闭 #99358) |
说明:以上编号取自仓库 changelog 中的 PR/Issue 编号,便于在社区中交叉检索;本文不附外部链接,具体上下文请以仓库源码为准。
修复一:递归 CTE 与remote()、view()组合时的段错误
触发场景
在 ClickHouse 中,递归 CTE(Recursive Common Table Expression)通过WITH RECURSIVE定义,其执行依赖一个内部临时表来承载迭代过程的中间结果。当递归查询的初始化(非递归)子查询中同时出现remote()表函数与view()表函数时,v25.8.20.4-lts 之前的版本可能触发段错误(segfault)导致服务进程崩溃,属于用户可见的严重稳定性缺陷。
底层实现印证
递归 CTE 的解析与分析阶段实现在 src/Analyzer/RecursiveCTE.cpp:RecursiveCTETable包装了一个由TemporaryTableHolderPtr持有的临时表存储与列定义,供递归子查询在迭代中反复读写。而实际数据读取由查询计划层完成:
- src/Processors/QueryPlan/ReadFromRecursiveCTEStep.cpp 通过
pipeline.init(...)将RecursiveCTESource挂入执行管道; - 数据源实现在 src/Processors/Sources/RecursiveCTESource.cpp,其中会校验递归子查询的投影列与
recursive_cte_table->columns是否同构(columns.size() != recursive_query_projection_columns.size()时抛出异常)。
当递归 CTE 与非递归的remote()/view()子查询组合时,涉及远程表的列类型/名称解析与本地临时表列定义之间的对齐。从源码结构可以推断,段错误的根因在于这类组合路径下列描述(ColumnsDescription)或存储句柄的初始化存在未覆盖分支,导致空指针解引用。修复通过补全该组合下的初始化与校验逻辑,确保列投影在任何组合下都先校验、后访问,从而消除崩溃。
实操验证建议
升级后可用如下形态的查询做回归验证(注意:remote()参数需替换为你实际的集群地址与表):
WITH RECURSIVE r AS ( SELECT number AS n FROM remote('127.0.0.1:9000', system, numbers) LIMIT 1 UNION ALL SELECT n + 1 FROM r WHERE n < 5 ) SELECT * FROM r;以及在view()包装场景下重复执行,确认不再出现段错误且结果正确。相关查询计划与递归数据源的实现,可继续阅读 src/Analyzer/UnionNode.cpp、src/Analyzer/RecursiveCTE.h 与 src/Processors/Sources/RecursiveCTESource.h。
修复二:Patch Parts 列顺序不匹配导致的 LOGICAL_ERROR
触发场景
Patch Parts 是 MergeTree 引擎为实现轻量级增量修改而引入的数据部件类型:对已存在 part 的少量列进行修改时,系统以“补丁部件”形式记录增量,读取时在内存中将其与原始 part 合并。当某个 part 上同时存在多个待应用的 patch part,且这些 patch part 与常规 part 的列顺序不一致时,旧版本会抛出LOGICAL_ERROR(“It is a bug”级别异常),导致读取失败。
底层实现印证
列序合并与校验逻辑集中在 src/Storages/MergeTree/AlterConversions.cpp 与 src/Storages/MergeTree/AlterConversions.h:
AlterConversions构造时同时接收MutationCommands与PatchPartsForReader,并通过addPatchPart(patch)将每个 patch 追加进patch_parts;hasPatches()/getAllPatches()暴露 patch 集合状态;- 构造器会对“超过一个 on-fly ALTER MODIFY”的组合抛出不支持异常(见
AlterConversions.cpp中number_of_alter_mutations > 1的检查分支),说明该模块对多变更叠加的一致性极其敏感。
修复前,patch part 合并时直接以 patch 自身列顺序为准进行拼接,未与常规 part 的列顺序对齐;修复后(PR #99164)在读路径中统一按目标 part 的列顺序对 patch 部件做重排/对齐,从根上消除了LOGICAL_ERROR。相关合并谓词对 patch 版本跨越的约束可进一步参考 src/Storages/MergeTree/Compaction/MergePredicates/MergeTreeMergePredicate.cpp 中“合并不得跨越既有 part 数据版本”的检查逻辑。
影响面与建议
该问题主要影响高频执行ALTER TABLE ... UPDATE/DELETE(生成 patch parts)并在短时间窗口内叠加读取的表。升级后建议对曾触发LOGICAL_ERROR的分区执行一次校验性查询或OPTIMIZE TABLE ... FINAL,确认数据可正常读取。
修复三:O_DIRECT 模式下异步读缓冲的 seek 定位错误
触发场景
ClickHouse 在磁盘 IO 中大量使用异步预读(prefetch)机制,并支持O_DIRECT直通模式(绕过页缓存,要求文件偏移与内存地址按块对齐)。此前在O_DIRECT开启且缓冲区发生过 seek 的场景下,会因偏移计算错误读到错误位置的数据(PR #99678 同时关闭了 issue #99358),影响查询结果正确性。
底层实现印证
相关实现在 src/IO/AsynchronousReadBufferFromFileDescriptor.h 与 src/IO/AsynchronousReadBufferFromFileDescriptor.cpp:
- 类注释明确说明其“使用现成文件描述符,不负责打开/关闭文件”,内部维护
prefetch_buffer、internal_buffer、memory三块等长缓冲并在预读后交换; required_alignment成员专门服务于O_DIRECT场景(头文件注释:"For O_DIRECT both file offsets and memory addresses have to be aligned"),其值由构造参数alignment传入;seek()实现(src/IO/AsynchronousReadBufferFromFileDescriptor.cpp的seek函数)中,若目标位置仍在缓冲区内则仅移动pos指针;否则执行真实 seek,并存在关键分支:off_t seek_pos = required_alignment > 1 ? new_pos / required_alignment * required_alignment : new_pos;即
O_DIRECT下将目标偏移向下取整到对齐边界,差额记入bytes_to_ignore;- 修复的另一处关键语义在 seek 的返回值:方法最终返回的是原始
new_pos而非对齐后的seek_pos,注释指出返回seek_pos会破坏依赖返回值的调用方(如ReadBufferFromEncryptedFile)。
修复前,O_DIRECT路径下 seek 后缓冲区状态与file_offset_of_buffer_end/bytes_to_ignore的记账存在不一致,导致后续getPosition()(其实现为file_offset_of_buffer_end - (working_buffer.end() - pos) + bytes_to_ignore)与实际读位置偏差,从而读到错位数据。修复通过补齐对齐偏移的记账逻辑与返回语义,确保O_DIRECT下 seek 结果准确。
相关配置提示
O_DIRECT行为通常由存储配置(如磁盘的direct_io相关设置)或表级min_bytes_to_use_direct_io类参数控制(请以 tests/config 与 src/Disks 中的实际配置项为准)。若你的环境启用了直通 IO,本修复直接关系到读正确性,建议优先升级并针对大文件读路径做回归。
总结
v25.8.20.4-lts 是一次典型的 LTS 稳定性补丁发布:三项修复分别覆盖查询执行层(递归 CTE)、MergeTree 存储层(Patch Parts)与IO 基础层(O_DIRECT 异步读),均为用户可见的错误行为修复。对于运行 25.8 LTS 系列的集群,建议按变更窗口安排升级;若涉及远程分布式查询、高频轻量变更或直通 IO 场景,可结合本文给出的源码路径做针对性回归验证。
- 发布记录原始文档:docs/changelogs/v25.8.20.4-lts.md
- 递归 CTE 相关源码:src/Analyzer/RecursiveCTE.cpp、src/Processors/Sources/RecursiveCTESource.cpp、src/Processors/QueryPlan/ReadFromRecursiveCTEStep.cpp
- Patch Parts 变更转换源码:src/Storages/MergeTree/AlterConversions.cpp、src/Storages/MergeTree/AlterConversions.h
- O_DIRECT 异步读缓冲源码:src/IO/AsynchronousReadBufferFromFileDescriptor.cpp、src/IO/AsynchronousReadBufferFromFileDescriptor.h
【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考