ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ClickHouse v21.6.2.7-prestable 变更日志深度解析:分区键、内存表变更与复制协调的 Bug 修复全解

2026/9/12 15:24:40 拓冰建站 浏览量
ClickHouse v21.6.2.7-prestable 变更日志深度解析:分区键、内存表变更与复制协调的 Bug 修复全解 ClickHouse v21.6.2.7-prestable 变更日志深度解析分区键、内存表变更与复制协调的 Bug 修复全解【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse导读本文围绕 ClickHouse 官方变更日志 docs/changelogs/archive/v21.6.2.7-prestable.md 展开逐条剖析 v21.6.2.7-prestable 相对 v21.6.1.6891-prestable 的 9 项 Bug Fix 与 3 项次要修复。文章不仅还原每个问题的现象与修复思路还结合当前仓库源码StorageMemory、PODArray、DateTimeTransforms、MemorySettings等给出实现级证据帮助读者理解分区键求值、内存表变更、复制副本协调、物化视图迁移等底层机制并将其转化为可落地的运维与开发经验。版本背景与文档结构该文档是一份标准的 ClickHouse prestable预发布版本变更日志。v21.6.2.7-prestable 是 21.6 系列的一个补丁级候选版本内容完全聚焦于稳定化所有条目均为 Bug FixBug 修复或 NOT FOR CHANGELOG / INSIGNIFICANT无需进入正式变更日志的微小修复没有新功能条目体现了 ClickHouse 发布流程中prestable 版本只收 bugfix、不加功能的纪律。文档共包含 9 个被标记为 Backported向后移植的修复项意味着这些修复同时被合入较早的稳定分支另有 3 个次要修复不进入正式变更日志。每个条目都附有对应的 GitHub issue/PR 编号与贡献者署名这是 ClickHouse 变更日志的标准格式便于追溯问题背景与实现细节。一、分区键中的取模函数保留旧版求值语义修复描述在分区键partition key中使用取模函数时改用旧版本取模函数实现。该修复解决了 issue #23508 描述的问题——分区键求值结果与数据写入时不一致导致无法命中正确分区。问题本质ClickHouse 对分区键表达式的求值结果决定了数据落入哪个分区目录。如果某一版本对modulo/%的求值行为发生改变例如参数顺序、类型提升或对负数/大整数的处理方式那么旧数据所在分区与按新语义计算出的分区就会错位表现为查询走错分区或分区无法裁剪。修复策略在分区键上下文中有意保留旧版取模语义Use old modulo function version when used in partition key保证写入时如何算、查询时也如何算维持分区路由的一致性。这体现了 ClickHouse 对分区键求值的稳定性承诺——分区键表达式的语义一旦上线就不可随意变更否则会造成物理数据布局错乱。实践启示生产环境中应避免在分区键里使用语义可能演进的内置函数如取模、时间截断类函数升级大版本后建议对带分区键的表执行SELECT DISTINCT partition_id FROM system.parts与新建数据的partition_id进行一致性比对若确有变更需求应通过表级ALTER TABLE ... MODIFY PARTITION BY配合数据重写在可控窗口内完成。二、StorageMemory 变更失败将 max_threads 固定为 1修复描述为修复 StorageMemory 的 mutation变更即ALTER TABLE ... UPDATE/DELETE失败问题在变更执行时设置max_threads 1关闭 issue #24274。源码证据在 src/Storages/StorageMemory.cpp#L318-L322 中可以看到修复的落点/// When max_threads 1, the order of returning blocks is uncertain, /// which will lead to inconsistency after updateBlockData. auto new_context Context::createCopy(context); new_context-setSetting(max_streams_to_max_threads_ratio, 1); new_context-setSetting(max_threads, 1);问题原理Memory 引擎将数据以 Block 形式保存在内存中data容器每次 mutation 通过MutationsInterpreter生成查询管线再用updateBlockData将结果块回写。当max_threads 1时并行执行返回块的顺序不确定而updateBlockData依赖块的顺序与原始数据一一对应按位置替换列顺序错乱会直接导致数据错位或写入失败。修复方式在 mutation 专用的 context 副本上把max_streams_to_max_threads_ratio与max_threads同时置为 1强制单线程按序产出结果块从根上消除顺序不确定性。该改动只影响 Memory 表的变更执行不影响查询路径。相关设置佐证Memory 引擎自身还支持一组表级设置定义于 src/Storages/MemorySettings.cpp#L18-L23compressBool默认false内存中压缩数据min_rows_to_keep/max_rows_to_keepUInt64默认0内存表缓冲保留的最小/最大行数min_bytes_to_keep/max_bytes_to_keepUInt64默认0保留的最小/最大字节数。这些设置在sanityCheck()中会校验min_* max_*见 src/Storages/MemorySettings.cpp#L71-L87约束违规会抛出SETTING_CONSTRAINT_VIOLATION。从源码结构看compress生效时 mutation 结果块也会在回写前调用elem.column-compress()src/Storages/StorageMemory.cpp#L333-L335。实践启示当 Memory 表执行ALTER ... UPDATE/DELETE报错或结果异常时优先检查是否依赖多线程执行此修复已默认规避该问题但若自行构造复杂 mutation仍应遵循变更路径串行化的原则。三、允许空 HTTP 头修复报头解析修复描述允许空 HTTP 头Allow empty HTTP headers修复 issue #23901。问题现象部分 HTTP 客户端或代理、负载均衡器会发送值为空的头部字段例如X-ClickHouse-Key:或空白自定义头旧版解析逻辑会将这类空头视为非法并直接拒绝请求导致查询被错误拦截。修复方式调整 HTTP 头部解析器将空值头部视为合法输入而非解析错误使 ClickHouse HTTP 接口能兼容更广泛的客户端行为。该改动位于 HTTP 接口层的请求解析路径涉及 programs/server 下的 HTTP handler 相关实现。实践启示在 ClickHouse 与各类网关nginx、haproxy、云负载均衡之间排障时若出现空头导致 400可先升级至包含此修复的版本自定义客户端发送 HTTP 头时应避免无值字段但服务端侧修复后已具备宽容性。四、物化视图跨库重命名内表随视图一起迁移修复描述修复将 Materialized View物化视图从 Ordinary 数据库迁移到 Atomic 数据库即执行RENAME TABLE查询时的 bug。此前内表inner table不会随物化视图一起移动到新数据库修复后两者一起迁移解决 issue #23926。背景知识ClickHouse 的物化视图由视图本身 存储目标数据的 inner table两部分构成。数据库有两种语义Ordinary 数据库目录结构与表一一对应RENAME语义直接Atomic 数据库使用 UUID 目录 元数据存储重命名涉及RENAME TABLE的级联处理。当物化视图跨越两种数据库语义时旧实现只搬移了视图定义内表仍留在原库造成视图指向失效的存储位置写入或查询随即失败。修复方式在RENAME TABLE的级联重命名逻辑中将物化视图的 inner table 一并纳入迁移集合。相关逻辑位于 src/Databases 下的数据库实现与 src/Interpreters 的RenameQuery处理路径。实践启示迁移物化视图前应确认源库与目标库的数据库引擎类型迁移后通过SHOW TABLES同时确认视图与内表命名通常为.inner_id.uuid均已出现在新库中。五、带交叉假分区的 DROP PARTITION 修复修复描述修复带 intersect fake parts交叉假分区场景下的DROP PARTITION。在极少数情况下可能存在 mutation version 大于当前 block number 的 part旧逻辑无法正确处理此类 part导致分区删除失败或残留。背景知识在 MergeTree 家族的并发变更与合并中系统会创建fake parts占位 part来跟踪变更进度intersect 场景指 part 之间存在范围交叉。当DROP PARTITION遍历 part 列表并试图删除目标分区时若遇到 mutation version 与 block number 不一致的 part旧的匹配逻辑可能跳过或误判。修复方式调整DROP PARTITION对 fake part 的匹配与处理逻辑使其能正确识别并删除这类交叉 part。相关代码位于 src/Storages/MergeTree 的分区操作实现MergeTreeDataPart与分区命令处理路径。实践启示若线上出现DROP PARTITION偶发失败且日志中出现 fake part 关键字可升级至修复版本同时建议在执行大范围分区操作前先SYSTEM FLUSH LOGS并检查system.mutations是否有进行中的变更任务。六、toWeek 函数单调性误判更智能分区裁剪暴露的隐患修复描述修复toWeek函数不正确的单调性monotonicity标记解决 issue #24422。该 bug 由 PR #5212 引入后被更智能的分区裁剪器partition pruner暴露。问题原理ClickHouse 的查询优化器会利用函数单调性来裁剪分区/索引区间若f(x)在给定区间内单调递增则f(x) IN [a,b]可以反推出x的候选区间从而跳过大量不必要的数据读取。toWeek将日期映射到周序号但周序号在跨年边界会回绕例如 12 月底 ISO 第 52 周之后是次年 1 周因此该函数并不单调。源码证据在当前仓库 src/Functions/DateTimeTransforms.h#L846-L849 中保留了明确的修复注释/// toWeek() is not monotonic because week numbers can wrap at year boundaries /// (e.g. ISO week 52 - week 1 in late December), depending on the week_mode. static constexpr bool hasMonotonicity() { return false; }ToWeekImpl通过time_zone.toYearWeek(time_zone.toDayNum(t), week_mode)计算周号src/Functions/DateTimeTransforms.h#L824-L844week_mode决定周一/周日作为一周起点等规则进一步加剧了跨年回绕的复杂性。修复方式将toWeek的hasMonotonicity()从true修正为false告知优化器不可对该函数做单调性假设从而避免分区裁剪器基于错误单调性得出错误的裁剪区间导致漏查数据。实践启示在分区键或索引表达式中使用周相关函数时务必警惕跨年边界若必须按周分区优先使用toStartOfWeek这类返回区间起点的单调函数而非返回周序号的toWeek。七、SYSTEM RESTART/SYNC REPLICA 无限执行修复修复描述修复SYSTEM RESTART REPLICA或SYSTEM SYNC REPLICA被无限处理一直不返回的问题。该问题在内存极小的服务器上被发现PR #24457。问题原理复制副本ReplicatedMergeTree的启动与同步依赖 zookeeper 会话、队列加载与后台任务调度。在内存极低的环境中资源分配或重试逻辑可能陷入重试-失败-重试的死循环或等待队列条件永远无法满足导致系统命令挂起。修复方式为SYSTEM RESTART REPLICA/SYSTEM SYNC REPLICA增加可终止/可超时路径避免无限阻塞。相关实现位于 src/Storages/ReplicatedStorageReplicatedMergeTree与 src/Coordinationzookeeper 会话管理。实践启示在内存受限的容器环境中应同时关注max_server_memory_usage与 zookeeper 会话超时配置若SYSTEM RESTART REPLICA长时间不返回可结合system.replicas表与服务器日志中的 zookeeper 操作超时信息定位根因修复后建议为副本同步类操作设置外部超时如客户端侧receive_timeout。八、CREATE TABLE AS SELECT 中的元组支持修复描述修复CREATE ... AS SELECT查询中元组tuple的使用问题PR #24464。问题现象CREATE TABLE t AS SELECT (1, 2) AS tup这类语句在旧版本中可能报错或生成错误列类型。原因在于CREATE AS SELECT需要先根据SELECT子句的结果类型推断新表结构而元组字面量的类型推断路径与常规列表达式存在差异。修复方式修正CREATE AS SELECT场景下对元组表达式的类型解析使其正确生成Tuple类型列。相关逻辑位于 src/Interpreters/InterpreterCreateQuery.cpp 的建表类型推导部分以及 src/DataTypes 的DataTypeTuple实现。实践启示在使用CREATE ... AS SELECT创建含复合类型列的表后建议用DESCRIBE TABLE校验列类型是否符合预期尤其是嵌套元组与命名元组named tuple场景。九、分布式表子列读取启用修复描述为分布式表启用子列subcolumns读取PR #24472。背景知识子列指复合类型如Map、Array(Tuple)、JSON的内部组件列例如map.keys、arr.size0、json.a.b等。此前分布式表StorageDistributed在读取远程分片时未透传子列信息导致针对子列的查询SELECT map.keys FROM distributed_table失败或回退到全列读取。修复方式在分布式查询的列选择与远程分片通信中启用子列传递使本地表可用的子列优化在分布式表上同样生效。相关实现位于 src/Storages/Distributed/DistributedAsyncInsert* 与 StorageDistributed.cpp。实践启示若分布式表上使用map.、.size0等子列语法报错除升级版本外还可确认本地表的allow_experimental_map_type等开关是否开启。十、NOT FOR CHANGELOG 的次要修复文档末尾的 3 项修复不进入正式变更日志但同样值得了解PODArray::insert 使用 memmove 处理内存重叠PR #24271PODArray是 ClickHouse 最核心的动态数组容器见 src/Common/PODArray.h。当向数组中部insert一段来自数组自身的区间时insertPrepare可能触发reallocPowerOfTwoElements扩容使源迭代器指向旧内存而失效。修复后在移动既有元素时改用memmove而非memcpy因为memcpy不允许源与目标重叠src/Common/PODArray.h#L521-L541if (unlikely(bytes_to_move)) memmove(this-c_end bytes_to_copy - bytes_to_move, this-c_end - bytes_to_move, bytes_to_move);该修复避免了对重叠内存使用memcpy导致的未定义行为是典型的底层内存安全修正。clickhouse-server.init 的 CLI 参数修复PR #24449 修复 SysV init 脚本 programs/serverclickhouse-server.init中命令行参数处理错误保证守护进程启动参数被正确传递。cast 运算符若干场景修复PR #24471 修正cast在若干边界场景下的行为涉及 src/Functions 的cast实现与 src/DataTypes 的类型转换路径。十一、总结从补丁看 ClickHouse 的工程实践v21.6.2.7-prestable 这一份小型补丁日志浓缩了 ClickHouse 的几条核心工程原则原则本版本体现分区语义稳定优先分区键中的modulo保留旧版语义避免数据布局错位执行顺序可确定性StorageMemorymutation 强制max_threads 1保证块回写有序优化器必须诚实toWeek放弃单调性标记防止分区裁剪漏数据内存安全零容忍PODArray::insert以memmove替换memcpy运维命令不挂死SYSTEM RESTART/SYNC REPLICA增加可终止路径兼容性优先允许空 HTTP 头物化视图跨库迁移内表随行对于使用 21.6 系列的用户该版本是值得升级的稳定性补丁对于阅读源码的开发者每个条目都能在 src 目录找到对应的实现锚点是理解 ClickHouse 存储引擎与查询优化器设计取舍的优秀案例。说明本文事实均以当前仓库中的变更日志文档与源码实现为依据文档中引用的 GitHub issue/PR 编号仅用于还原问题上下文具体行为请以实际部署版本的官方发布说明为准。【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考