ClickHouse v20.12.5.14-stable 修复盘点:从 TTL 移除、时间解析到合并安全性的源码级解读

ClickHouse v20.12.5.14-stable 修复盘点:从 TTL 移除、时间解析到合并安全性的源码级解读 ClickHouse v20.12.5.14-stable 修复盘点从 TTL 移除、时间解析到合并安全性的源码级解读【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse本篇技术指南聚焦 ClickHouse 20.12 分支的稳定版补丁v20.12.5.14-stable逐条拆解该版本相比v20.12.4.5-stable修复的 7 项 Bug 与 1 项构建改进。你将掌握每项修复触发的真实场景、背后的源码机制TTL 元数据变更、两级聚合、部分合并格式、时间字符串解析等并了解如何在MergeTree家族引擎中规避这些历史坑位。文中所有实现细节均可在当前仓库源码中溯源验证。版本背景稳定版补丁的定位在 ClickHouse 的版本体系中v20.12.5.14-stable属于 20.12 主版本之下的一个稳定分支补丁Patch版本。这类版本通常不会引入新特性而是聚焦于修复上一个小版本v20.12.4.5-stable遗留的缺陷并回退移植Backport主线上的修复成果。从发布说明的结构看它仅包含Bug Fix缺陷修复与Build/Testing/Packaging Improvement构建与测试改进两大类符合稳定补丁“求稳不求新”的定位。发布说明中每条记录都以Backported in #issue开头表明这些修复并非在 20.12 分支上重新开发而是将主线master上已经验证过的修复代码移植到稳定分支——这是 ClickHouse 对长期支持版本进行安全维护的典型方式。修复一MODIFY COLUMN ... REMOVE TTL未真正移除列级 TTL修复记录修复执行MODIFY COLUMN ... REMOVE TTL时并未真正移除列 TTL 的问题#18130。问题现象在 MergeTree 系表中用户可以通过TTL子句为整张表或某一列设置过期策略。当需要取消某一列的自动过期能力时标准语法是ALTER TABLE my_table MODIFY COLUMN col_name REMOVE TTL;修复前的行为是该语句执行成功、不报错但列上的 TTL 实际上仍然生效——过期数据依然会被清理与用户预期严重不符属于“静默失效”类 Bug。源码机制列级 TTL 的存储与修改贯穿 ALTER 命令的处理链路。在 AlterCommands.cpp 中ALTER TABLE ... MODIFY COLUMN命令的 AST 会被解析并携带新的列定义第 217 行与第 284 行command.ttl ast_col_decl.getTTL();把列声明中的 TTL AST 赋值给命令在应用变更时第 706 行column.ttl ttl;将解析后的 TTL 写入列元数据而REMOVE TTL路径则通过第 773 行column.ttl.reset();清空列对象上的 TTL 信息。同时MergeTreeData.cpp 中有一段关键注释列级 TTL 原本是通过ADD/MODIFY COLUMN即command.ttl写入的而REMOVE TTL是被允许的——它只做清理操作。修复前的问题在于清理逻辑没有完整贯穿元数据读写路径导致列 TTL 在部分场景下残留。验证与规避修复后的行为是REMOVE TTL能彻底清空目标列的 TTL 元数据。作为使用方建议在变更后用SELECT * FROM system.columns WHERE table my_table或system.tables检查 TTL 相关字段确认清理生效同时在批量执行MODIFY COLUMN与REMOVE TTL时注意语句顺序避免 TTL 定义被二次覆盖。修复二Distinct组合子 两级聚合的崩溃修复记录修复聚合函数使用Distinct组合子且启用两级聚合two-level aggregation时可能发生的崩溃#18365。问题场景ClickHouse 允许为聚合函数叠加Distinct组合子例如SELECT uniqExactDistinct(user_id), sumDistinct(amount) FROM events;当数据量较大、聚合状态采用两级聚合two-level aggregation即先按桶划分再合并时Distinct组合子的状态处理存在缺陷可能触发服务端崩溃。这类问题通常在并行度较高的分布式查询中偶现排查成本高。原理说明聚合函数组合子由源码中的组合子机制实现Distinct组合子要求每个聚合状态内部维护一份“已见值”集合以保证去重语义。从源码结构可以推断修复的核心在于确保Distinct组合子的状态在两级聚合的桶合并阶段能够被正确序列化与合并避免对无效状态的访问导致崩溃对应 issue #17682。实践建议对于大结果集查询可先通过EXPLAIN PIPELINE观察是否走了 two-level aggregation 路径升级后如仍怀疑与聚合相关可在system.query_log中比对exception字段与版本号确认是否命中该缺陷。修复三system.settings_profile_elements表填充缺失修复记录修复system.settings_profile_elements系统表填充错误的问题#18379对应 issue #18231。问题背景system.settings_profile_elements是 ClickHouse 面向 RBAC基于角色的访问控制提供的系统表用于展示“设置配置文件settings profile”与“角色role”“用户user”“设置settings”“约束constraints”之间的映射关系。它解决了system.settings_profiles只展示配置文件本身、无法反映继承与引用关系的局限。修复前该表在特定场景下无法正确填充数据表现为查询结果为空或缺失部分元素导致基于该系统表做权限审计、配置下发校验的自动化脚本拿到不完整数据。实践用法修复后可通过如下查询核对某个 profile 的完整构成SELECT * FROM system.settings_profile_elements WHERE profile_name readonly_profile;该表在 RBAC 管理链路中与system.roles、system.users、system.settings_profiles配套使用是排查“设置为何未生效”时的重要诊断入口。修复四限制 wide 部分到 compact 部分的合并修复记录限制从 wide parts 合并到 compact parts因为在垂直合并vertical merge场景下会导致结果 part 损坏#18381。背景知识MergeTree 家族支持两种 parts 存储布局compact parts所有列存放在一个文件中适合小批量插入减少文件句柄与元数据开销wide parts每列独立文件适合大数据量场景便于列式读取与部分列扫描。min_bytes_for_wide_part等设置控制 part 何时从 compact 切换为 wide 形态。问题在于合并merge过程中如果允许“宽 part 合并成紧凑 part”在垂直合并算法下会破坏结果 part 的结构产生损坏数据。修复方式从修复描述看此次修复在合并选型逻辑中加入了约束禁止产生“由 wide part 合并且输出为 compact part”的合并任务。这对运维的直接影响是通过ALTER TABLE ... MOVE PARTITION、OPTIMIZE或后台自动合并时合并结果不再出现结构损坏的 part对依赖system.parts中part_type字段做监控告警的场景修复后的part_type转换行为更可预期。验证手段升级后可使用system.parts检查合并产物SELECT name, part_type, rows FROM system.parts WHERE table my_table AND active 1;并配合OPTIMIZE TABLE my_table FINAL触发全量合并后做数据校验如CHECK TABLE确认结果 part 完好。修复五toType(...)系列函数对Nullable(String)的错误处理修复记录修复toDate、toUInt32等toType(...)转换函数接收Nullable(String)参数时抛出value is too short异常的问题。修复后解析失败时返回NULL而非抛异常#18445。问题现象在修复前如下查询在部分输入下会直接报错中断SELECT toDate(nullable_str_col) FROM tbl; -- 存在不可解析值时抛异常对于包含脏数据空串、格式错误的Nullable(String)列转换函数本应遵循 SQL 语义返回NULL却错误地抛出了value is too short异常导致整条查询失败。源码定位这类转换函数定义在 FunctionsConversion.h通过FunctionConvertFromString模板统一实现并在 FunctionsConversion_reg.cpp 中注册。从修复描述可以推断缺陷在于错误处理路径没有感知参数类型的外层Nullable当底层字符串解析返回长度不足错误时转换逻辑直接将其作为硬错误抛出而不是按Nullable语义吞掉并产出NULL。修复后的行为修复后toDate(Nullable(String))等转换遵循标准语义可解析值正常转换不可解析值返回NULL不再中断整条查询。这一变化让 ETL 中“先转换、后过滤脏数据”的写法更安全SELECT id, toDate(ts_str) AS ts FROM raw_events WHERE toDate(ts_str) IS NOT NULL; -- 脏数据安全过滤注意若业务此前依赖“转换失败抛异常”来中断任务升级后需要改用toDateOrNull/toDateOrZero显式控制降级行为或检查应用层对异常的依赖。修复六parseDateTimeBestEffort对 12AM 的正确支持修复记录为parseDateTimeBestEffort函数补齐对 12AM凌晨 0 点的 12 小时制写法的解析支持#18449修复 issue #18402。函数背景parseDateTimeBestEffort是 ClickHouse 的“尽力解析”时间函数它能自动识别多种常见日期时间字符串格式如23/10/2025 12:12:57、Sat, 18 Aug 2025 07:22:16 GMT、Unix 时间戳1735689600等并支持第二个可选参数指定时区SELECT parseDateTimeBestEffort(23/10/2025 12:12:57) AS dt; SELECT parseDateTimeBestEffort(Sat, 18 Aug 2025 07:22:16 GMT, Asia/Istanbul) AS dt; SELECT parseDateTimeBestEffort(1735689600) AS dt;相关使用说明与示例可参见 FunctionsConversion_reg.cpp 中内嵌的函数文档。其变体parseDateTimeBestEffortOrZero与parseDateTimeBestEffortOrNull在解析失败时分别返回零值日期或NULL适合容忍脏数据的场景。12AM 修复的技术细节12 小时制中的“12AM”表示午夜零点而“12PM”表示正午 12 点。修复前parseDateTimeBestEffort对带 AM/PM 标记的输入中的12小时处理不正确导致 12AM 被错误解析。在底层解析实现 parseDateTime.cpp 中小时字段由一组状态位共同约束hour字段取值范围由hour_starts_at_1小时是否从 1 开始计数与is_hour_of_half_day是否为半天制小时两个标志组合决定见第 180-192 行第 190 行is_am标志记录 AM/PM 语义注释明确说明当is_hour_of_half_day true且is_am false即 PM时需要为结果 DateTime 增加 12 小时第 404-406 行的取模逻辑max_hour 12; new_hour hour_ % 12;处理半天制小时的归一化。从这些结构可以推断12AM 修复的关键在于当解析到半天制12且为 AM 时小时值应归零对应午夜而不是保持为 12对应正午同时12 PM应保持为 12 点。相关的时间输出侧如formatDateTime的%I/%l格式符在 formatDateTime.cpp 中已有同类换算逻辑本次修复使“尽力解析”输入端与输出端在 12 小时制语义上保持一致。验证示例-- 修复前12AM 可能被解析为 12:00 正午 -- 修复后12AM 正确对应当天 00:00 SELECT parseDateTimeBestEffort(2025-10-23 12:00:00 AM) AS midnight; -- 2025-10-23 00:00:00 SELECT parseDateTimeBestEffort(2025-10-23 12:00:00 PM) AS noon; -- 2025-10-23 12:00:00修复七合并期间禁用 AIO 写入规避主键列数据损坏修复记录禁用合并merge过程中的 AIO异步 I/O写入因为它可能导致合并期间主键列出现极其罕见的数据损坏#18481。问题背景Linux 原生异步 I/Oio_uring/libaio能显著提升高并发读写吞吐ClickHouse 在部分 I/O 路径上支持 AIO。然而合并操作涉及“读取多个源 part → 生成新 part → 写回磁盘”的复杂流程AIO 写路径与合并任务的交互存在竞态极端情况下会造成主键列数据损坏——这种问题几乎无法在常规测试中复现但一旦发生即是数据完整性的严重事故。修复策略修复采用了保守策略在合并写路径上直接禁用 AIO回退到同步/缓冲写。代价是合并吞吐可能略有下降但换取了数据安全性的确定性。这也体现了数据库工程中“正确性优先于性能”的取舍原则。运维启示如果你的环境曾经通过自定义配置或系统级参数放宽过 I/O 相关设置如allow_aio或存储后端的相关选项升级到本版本后合并路径将自动不再使用 AIO 写入对性能敏感的生产集群可在升级后观察合并耗时指标system.merges中的elapsed若合并成为瓶颈可通过调整max_bytes_to_merge_at_min_space_in_pool、number_of_free_entries_in_pool_to_lower_max_size_of_merge等合并参数间接缓解该修复同时印证了 ClickHouse 对“合并阶段主键列正确性”的强保证合并产物必须与源 part 逻辑等价任何可能破坏该保证的优化都会被禁用。构建与测试改进时区数据更新至 2020e修复记录更新时区timezone信息至 2020e 版本#18531。背景与影响ClickHouse 内置完整的 IANA 时区数据库所有toTimeZone、toDateTime(..., TZ)、parseDateTimeBestEffort(..., TZ)等时区相关运算都依赖该数据。时区规则会随各国政策调整夏令时变更、取消夏令时、时区偏移修改等若不及时同步历史时间换算可能产生错误结果。本次更新将内置时区数据推进到 IANA2020e版本保证 20.12 稳定分支在 2020 年末的时区规则与官方数据库一致。验证方式升级后可抽查受影响的时区-- 例如检查莫斯科2020e 前后夏令时规则曾变更的换算结果 SELECT toDateTime(2020-10-25 01:30:00, Europe/Moscow) AS ts;结语v20.12.5.14-stable虽是一个小型稳定补丁但七个修复横跨了 ClickHouse 的三个核心命题元数据正确性TTL 移除、settings_profile_elements 填充、查询语义正确性Nullable 转换、12AM 解析、Distinct 两级聚合、以及存储与合并的数据安全性wide→compact 合并限制、合并 AIO 禁用。对仍运行 20.12 分支的生产集群本版本值得尽快升级对使用更新版本的团队这些修复的历史原因与源码机制同样具有参考价值——它们揭示了 ClickHouse 在边界场景下的设计取舍宁可牺牲一点性能禁用合并 AIO也绝不允许数据损坏或“静默失效”的语义错误。读者可在 AlterCommands.cpp、parseDateTime.cpp、FunctionsConversion_reg.cpp 等源码中继续深入验证本文涉及的全部实现细节。【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考