关于 ClickHouse v20.8.13.15-lts 变更日志的技术解读
2026/9/10 18:20:41 网站建设 项目流程

关于 ClickHouse v20.8.13.15-lts 变更日志的技术解读

【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse

<output_article>

ClickHouse v20.8.13.15-lts 补丁版本深度解析:TTL 兼容性收紧、Merge 表读取性能优化与 32 项关键修复

本篇文章以 docs/changelogs/archive/v20.8.13.15-lts.md 为核心依据,系统解读 ClickHouse 20.8 LTS 分支补丁版本 v20.8.13.15(相对 v20.8.12.2)包含的 1 项向后不兼容变更、1 项性能改进与 32 项 Bug 修复。文章结合当前仓库源码(src/programs/)逐一验证每项变更的底层实现与影响范围,帮助 20.8 LTS 用户评估升级风险、理解修复原理,并为低版本升级到 v20.8.13.15 提供自查清单。

版本背景与发布定位

ClickHouse 采用稳定版(LTS,Long Term Support)与预发布版并行的版本策略。v20.8.13.15-lts属于 20.8 LTS 系列的一个补丁版本,其变更内容并非新功能开发,而是集中于稳定性、数据正确性与安全性

  • 1 项 Backward Incompatible Change(向后不兼容变更):涉及 MergeTree 旧建表语法下 Table TTL 的禁用;
  • 1 项 Performance Improvement(性能改进)Merge表引擎在覆盖海量MergeTree表时的读取性能;
  • 32 项 Bug Fix(缺陷修复):覆盖存储引擎、复制与 Mutation、查询优化器、字典、格式解析、MySQL 协议、ODBC 桥接、内存安全等多个子系统。

所有修复均以"Backported"(回溯移植)形式合入,即先在上游 master 分支修复,再以补丁形式移植到 20.8 LTS 分支,确保 LTS 用户在不升级大版本的前提下获得关键修复。以下按分类逐一解读,并给出当前仓库中的源码佐证。

一、向后不兼容变更:MergeTree 旧语法下 Table TTL 被正式禁止

变更内容

本次版本开始,不再允许使用旧语法创建带 Table TTL 的 MergeTree 表。此前这种行为虽然能成功执行,但 Table TTL 实际上被静默忽略,并不会产生任何过期删除/合并效果,属于"看似成功、实则无效"的误导性行为。本次变更将该场景从"静默无效"改为"显式报错"。

与此同时,ATTACH(附加)通过旧语法创建的此类表仍然是允许的,以保证已有数据的可访问性与平滑迁移。

什么是"旧语法"

MergeTree 家族在建表时有两种语法形态。旧语法将index_granularity等参数作为引擎参数直接传入,例如:

-- 旧语法:index_granularity 作为引擎构造参数 CREATE TABLE old_syntax_t ENGINE = MergeTree('2020-01-01', (CounterID, EventDate), (CounterID, Date), 8192) SETTINGS index_granularity = 8192;

新语法则将这些内容统一放入SETTINGSORDER BY/PARTITION BY等子句中:

-- 新语法 CREATE TABLE new_syntax_t ENGINE = MergeTree PARTITION BY toYYYYMM(EventDate) ORDER BY (CounterID, Date) SETTINGS index_granularity = 8192;

在 src/Storages/MergeTree/registerStorageMergeTree.cpp 中可以看到旧语法参数的解析逻辑:当第 4 个引擎参数是UInt64字面量时,它被识别为index_granularity并写回存储设置;紧接着对旧语法强制校验 Table TTL:

if (args.storage_def->ttl_table && args.mode <= LoadingStrictnessLevel::CREATE) throw Exception(ErrorCodes::BAD_ARGUMENTS, "Table TTL is not allowed for MergeTree in old syntax");

即:只有在CREATE(而非ATTACH)且带 Table TTL 时才抛出BAD_ARGUMENTS,与变更日志中"Attach of old tables is still possible"完全对应。此外,同一批次还收紧了MODIFY TTL:对旧语法创建的 MergeTree 表执行MODIFY TTL此前"查询成功但无实际效果",本次起同样被禁止(参见该版本的对应修复条目)。

升级影响与处理建议

  1. 如果线上存在用旧语法创建且声明了 Table TTL 的表,升级后这些表仍可正常读取与附加,但建表语句会在新版本被拒绝;
  2. 如需让 Table TTL 真正生效,建议用新语法重新创建表(例如CREATE ... ENGINE = MergeTree PARTITION BY ... ORDER BY ... TTL ...),再迁移数据;
  3. 该变更同时涉及ALTER TABLE ... MODIFY TTL的限制,运维脚本中若包含对旧语法表的此类操作,需要先重建表。

二、性能改进:Merge 表引擎读取海量 MergeTree 表时的性能修复

问题背景

Merge表引擎(StorageMerge)本身不存储数据,而是将同一数据库内(或通过正则匹配的)多个表聚合为一个逻辑视图,供查询统一访问。当被合并的表数量达到数千、数万级别时,每次读取都需要在所有源表上枚举并定位数据部分,开销随源表数量线性膨胀,性能出现明显劣化(原 issue #7748)。

修复与源码对照

修复思路围绕StorageMerge对源表枚举、读取器创建路径上的冗余工作进行优化。从 src/Storages/StorageMerge.h 可以看到其核心结构:

class StorageMerge final : public IStorage, WithContext { using DBToTableSetMap = std::map<String, std::set<String>>; ...

DBToTableSetMap维护"数据库 → 表集合"的映射,而 src/Storages/StorageMerge.cpp 的getDatabaseIterators则负责按数据库/正则获取迭代器:

StorageMerge::DatabaseTablesIterators StorageMerge::getDatabaseIterators(ContextPtr context_) const { return database_name_or_regexp.getDatabaseIterators(context_); }

本次优化减少了在为Merge表构造读取计划时对每个源表重复进行的元数据获取与校验,将原本与源表数量强相关的开销显著降低。对于"一个 Merge 视图下挂几百上千张分区表/日志表"的经典运维场景(如按天分表的日志查询),该修复能直接改善查询延迟。

三、Bug Fix 深度解读:按子系统分组

3.1 MySQL 协议:INSERT 返回真实受影响行数

现象:通过 MySQL 协议执行 INSERT 时,客户端收到的影响行数恒为 0,与预期不符(issue #16605)。

修复:在 src/Server/MySQLHandler.cpp 中,服务器在处理查询时注册了一个进度回调,累计Progress::written_rows

std::atomic<size_t> affected_rows {0}; auto prev = query_context->getProgressCallback(); query_context->setProgressCallback(&, my_prev = prev { if (my_prev) my_prev(progress); affected_rows += progress.written_rows; });

查询结束后(无结果集输出时)通过OKPacket将该计数返回给 MySQL 客户端(src/Server/MySQLHandler.cpp):

if (!with_output) packet_endpoint->sendPacket(OKPacket(0x00, client_capabilities, affected_rows, 0, 0));

影响:对使用 MySQL 客户端/驱动(JDBC、Python、ORM 等)接入 ClickHouse 的应用,affected_rows语义与 MySQL 一致,写入类应用的状态反馈从此可信。

3.2 Mutation / ALTER 挂起类修复(两项)

本版本集中修复了两个与 Mutation 生命周期相关的挂起问题:

  1. ALTER查询在对应 mutation 于其他副本被 kill 后挂起:复制表上,一个副本上KILL MUTATION后,另一个副本正在执行的ALTER可能永久等待,本次修复补全了该状态下的唤醒与终止路径(issue #16953);
  2. ALTER在 mutation 被 kill 后挂起(由线程 fuzzer 发现):进一步覆盖了与上述问题相关的竞态窗口,避免ALTERKILL MUTATION交错时出现死锁(issue #17244 之外的另一条路径)。

另外还有一项极罕见的 mutation 挂起修复:在DROP / DETACH / REPLACE / MOVE PARTITION之后,mutation 可能因 partition 状态变化而停滞,该问题此前已由 #15537 部分修复,本次将其余场景补齐(issue #19443 相关)。

运维意义:对于依赖ALTER ... UPDATE / DELETE进行数据订正、并在异常时执行KILL MUTATION的团队,这三项修复显著降低了"清理任务卡死、system.mutations长期处于 waiting"的概率。

3.3 内存安全与崩溃修复(四类关键漏洞)

  • topK聚合函数可能的段错误topK在特定数据分布下可能发生非法内存访问导致服务崩溃(issue #17404);
  • bitmapAndnot函数段错误bitmapAndnot(a, b)对特定输入触发 segmentation fault(issue #19668);
  • arrayEnumerateUniq崩溃或死循环:传入非常规参数时可能导致进程崩溃或无限循环(issue #19787);
  • H3 库缓冲区溢出:修复了上游 Uber H3 库中一个可能的 buffer overflow(内存读取),ClickHouse 侧随之修复(issue #19219,上游见 uber/h3#392);
  • addMonth缓冲区溢出(内存读取)addMonth(date, n)以精心构造的参数调用时可能发生越界读取(issues #19441 / #19413)。

其中arrayEnumerateUniqaddMonth均由 ClickHouse 维护者 Alexey Milovidov 直接修复,体现了 20.8 分支对外部输入可触发的内存安全问题的重视——这类问题在多租户、公网暴露的 ClickHouse 部署中属于必须优先升级的范畴。

3.4 查询正确性类修复

  • 谓词优化器与不确定函数:开启 predicate optimizer 时,含不确定函数(如随机数、时间函数)的过滤条件下推/重排导致结果不确定(issue #17244);
  • groupUniqArray对 Enum 参数的返回类型:此前对Enum类型参数可能返回错误的列类型(issue #17875);
  • JOIN 中非零默认值:当被连接类型的默认值非零(如部分Enum值)时,JOIN 结果中的默认填充值错误(issue #18197);
  • neighbor函数与LowCardinalityneighbor(column, offset)在参数为LowCardinality列时返回错误结果(issue #10333)。

neighbor的修复非常典型。在 src/Functions/neighbor.cpp 中,FunctionNeighbor声明不启用 LowCardinality 的默认实现,注释明确说明了原因:

/// We do not use default implementation for LowCardinality because this is not a pure function. /// If used, optimization for LC may execute function only for dictionary, which gives wrong result. bool useDefaultImplementationForLowCardinalityColumns() const override { return false; }

即:neighbor有状态、非纯函数(返回结果依赖行的遍历顺序),若按 LowCardinality 字典去重执行,将只对字典值求值而得到错误结果。该修复同时将函数标记为 deprecated(见 src/Functions/neighbor.cpp),建议改用规范窗口函数或显式开启allow_deprecated_error_prone_window_functions

  • Date溢出行为统一:将Date类型的严格上限固定为2106-02-07,超过该值强制转换为 0,消除"同一年份不同溢出值"的不一致;
  • CREATE DICTIONARY 的 id 表达式:修复了字典创建语句中 id 表达式解析/生成错误(PR #19571);
  • RANGE_HASHED 字典非法解引用RANGE_HASHED()字典在特定边界输入下可能发生无效内存解引用(PR #20345);
  • 字典查询缺失键的行为不一致:修复了查询字典中不存在键时结果不稳定的问题(PR #20578)。

3.5 格式与解析类修复

  • ORC 格式无限读取:从 ORC 文件读取时可能陷入无限循环,该缺陷由早期 #10580 引入,本次修复(issue #19095);
  • 损坏 JSON 导致内存异常:遇到损坏的 JSON 时,旧代码试图将整个文件读入内存,可能触发分配器异常(issue #19719)。修复后不再整体加载,避免大文件撑爆内存;
  • S3 URL 解析std::out_of_range:解析特定 S3 URL 时抛出basic_string越界异常(PR #18059)。

3.6 系统表与日志类修复

  • system.stack_trace在守护进程模式下为空:服务器以 daemon 方式运行时,该表无法获取线程栈信息,本次修复(PR #17630);
  • system.settings_profile_elements填充错误:该表未能正确列出设置配置文件的元素(issue #18231);
  • Logger 参数数量不匹配:修复日志器在参数个数不一致时可能崩溃的问题(PR #18717)。

3.7 复制与后台线程类修复

  • ON CLUSTER后台线程挂起:执行ON CLUSTER查询的后台线程可能在已删除的复制表上等待,本次修复避免其永久阻塞(PR #19684);
  • 复制表ALTER在 mutation 被其他副本 kill 后挂起:见 3.2 节。

3.8 表引擎与存储行为类修复

  • MergeTree 合并期禁用 AIO 写入:合并过程中使用异步 I/O(AIO)写入存在极小概率导致主键列数据损坏,本次默认禁用合并期间的 AIO 写路径。相关的 Direct I/O 行为可通过min_merge_bytes_to_use_direct_io设置控制(src/Storages/MergeTree/MergeTreeSettings.cpp):

    合并数据时,若参与合并的数据总量超过min_merge_bytes_to_use_direct_io(默认 10 GB),则使用O_DIRECT直读直写;设为 0 则禁用直接 I/O。

    该项属于"以极小的性能代价换取数据完整性保障"的取舍,对追求数据正确性的生产环境是重要改进;

  • Distributed表插入空间不足时段错误:向Distributed表写入时若磁盘空间不足,可能触发段错误而非优雅报错(PR #17737);

  • *CollapsingMergeTree/ReplacingMergeTree的版本列保护:禁止对version列执行DROP/RENAME,此前操作可能破坏折叠/去重语义(PR #20300);

  • MongoDB 表引擎懒连接MongoDB表引擎改为"真正读数据时才建连",ATTACH TABLE不再尝试连接 MongoDB,避免启动时因 MongoDB 不可用而失败(PR #20110);

  • mutation 中转义文本序列化错误ALTER ... UPDATE col = CAST('foo', 'Enum8(\'foo\' = 1')这类含转义字符的 mutation 序列化不正确,导致执行失败或结果错误(issue #18878);

  • MergeTree 旧语法 dedup 提示:与 TTL 变更同源,非复制 MergeTree 在旧语法下不支持去重,相关逻辑在 src/Storages/StorageMergeTree.cpp 有对应校验。

3.9 ODBC 桥接修复

clickhouse-odbc-bridge两项问题被一并修复:

  1. 在同时支持 IPv4/IPv6 双栈的主机上,服务器可能无法访问 ODBC bridge 进程;
  2. ODBC 字典更新可能使用格式错误的查询执行,或直接导致崩溃(关联 issue #14489)。

对依赖 ODBC 字典接入外部数据库(如 SQL Server、其他 MySQL 实例)的部署,建议确认升级后 ODBC 字典的更新与查询行为恢复正常。

四、升级建议与验证清单

对于运行在 20.8.x 的实例,升级到 v20.8.13.15-lts 前建议按以下清单自查:

  1. 扫描建表 DDL:找出所有使用旧语法ENGINE = MergeTree(partition_key, order_by, index_granularity, ...)且带有TTL子句的表,升级前先用新语法重建(旧表升级后仍可 ATTACH 读取);
  2. 检查MODIFY TTL脚本:涉及旧语法表的ALTER TABLE ... MODIFY TTL操作在新版本会被拒绝;
  3. 确认 AIO 配置:若自定义过与 merge 直接 I/O 相关的设置(min_merge_bytes_to_use_direct_io),确认与新的 AIO 写禁用策略兼容;
  4. 回归高风险函数:针对涉及neighbortopKgroupUniqArraybitmapAndnotarrayEnumerateUniqaddMonth的报表查询做回归,重点验证LowCardinalityEnum参数场景;
  5. 验证 MySQL 协议客户端:检查依赖affected_rows的应用行为是否与预期一致;
  6. 监控 mutation 队列:升级后观察system.mutations,确认历史遗留 mutation 均能正常推进。

总结

v20.8.13.15-lts 是 20.8 LTS 分支中一次以"正确性、稳定性、安全性"为主旨的重要补丁:它通过禁用旧语法 Table TTL消除了"静默无效"的隐患;通过Merge 表读取优化改善了海量分表场景的查询体验;并通过 32 项修复覆盖了内存安全、Mutation 生命周期、字典一致性、格式解析、MySQL 协议等多个子系统。结合 docs/changelogs/archive/v20.8.13.15-lts.md 与 src/Storages/MergeTree/registerStorageMergeTree.cpp、src/Server/MySQLHandler.cpp、src/Storages/StorageMerge.cpp、src/Functions/neighbor.cpp 等源码,可以确认每一项变更都有明确的实现与校验逻辑支撑。对于仍在 20.8 LTS 线上的用户,本版本是值得尽快跟进的关键补丁;对于高版本用户,文中涉及的语义收紧(如neighbor弃用)与安全修复思路同样具有参考价值。 </output_article>

【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询