ClickHouse v20.9.1.4585-prestable 版本深度解析:TTL 合并控制、view 表函数与 20.9 新特性全览
【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse
本文基于 ClickHouse 仓库中的 v20.9.1.4585-prestable 发布说明 展开,逐条拆解这一预发布版本中引入的向后不兼容变更、新特性、性能优化、改进与缺陷修复,并结合仓库源码验证其底层实现。读完本文,你将掌握 20.9 中 TTL 合并并发控制、
view表函数、apply语法、微秒级查询起始时间等关键能力的用法、原理与升级注意事项,可用于实际的版本评估与平滑升级规划。
该版本发布说明以 v20.8.1.4513-prestable 为对比基线,是 20.9 系列早期的 prestable(预稳定)构建。虽然其中部分条目仍以 "FIXME" 标注、个别条目内容待补充,但整体变更清单已经完整覆盖存储引擎、表函数、日志系统、聚合函数、集成组件与构建体系等多个层面,是观察 20.9 系列演进方向的良好切入点。
一、向后不兼容变更:TTL 合并的并发控制
20.9 系列最重要的兼容性信号,是新增了两个用于控制后台 TTL 合并并发度的 MergeTree 设置:
max_replicated_merges_with_ttl_in_queue:ReplicatedMergeTree 复制队列中允许同时存在的 TTL 合并任务数。max_number_of_merges_with_ttl_in_pool:后台合并池中 TTL 合并条目数超过该值时,不再分配新的 TTL 合并,以便为常规合并保留空闲线程、避免 "Too many parts" 问题。
这两个设置定义在 MergeTreeSettings.cpp 中,类型均为UInt64,默认值分别为1与2。从源码注释可以确认其设计意图:前者约束 "How many tasks of merging parts with TTL are allowed simultaneously in ReplicatedMergeTree queue"(复制队列中同时允许的 TTL 合并任务数),后者则说明 "When there is more than specified number of merges with TTL entries in pool, do not assign new merge with TTL"(当池中 TTL 合并条目超过指定数量时不再分配新的 TTL 合并)。
在 StorageReplicatedMergeTree.cpp 中可以看到实际判断逻辑:只有当前队列中已排队的 TTL 合并数小于max_replicated_merges_with_ttl_in_queue时,复制副本才会允许新的 TTL 合并任务入队,从而在复制场景下对 TTL 合并的并发度做统一收敛。
兼容性影响与升级建议
发布说明明确指出:仅当使用 delete TTL(删除型 TTL)时,该变更才会与旧版本产生复制协议不兼容;否则复制保持兼容。换句话说,如果表只使用 TTL 做数据过期删除,那么新老版本之间可能出现复制队列中的 TTL 合并条目无法被旧版本识别/执行的情况。官方给出的规避手段有两种:
- 一次性升级全部分片副本,使集群内所有节点处于同一协议版本;
- 无法一次性升级时,先在所有副本上执行
SYSTEM STOP TTL MERGES暂停 TTL 合并,直到全部副本升级完成。
如果在升级过程中复制队列中已经出现不兼容的 TTL 合并条目,发布说明给出的恢复步骤是:
SYSTEM STOP TTL MERGES; ALTER TABLE ... DETACH PARTITION ...; -- 摘除被分配了不兼容 TTL 合并的分区 -- 在单个副本上重新 ATTACH 该分区 ALTER TABLE ... ATTACH PARTITION ...;即先停掉 TTL 合并,将包含不兼容 TTL 合并任务的分区 DETACH 后,在单个副本上 ATTACH 回来,以消除队列中的坏条目。这条操作路径在实际生产升级中具有直接参考价值。
二、新特性:从查询书写到统计能力的全面扩展
1.view表函数:把子查询变成可传递的表对象
20.9 新增表函数view,其作用是把一个子查询包装成"表"参与后续查询,便于把查询当作对象四处传递。发布说明给出的典型用法是嵌套在remote/cluster表函数中使用:
SELECT * FROM cluster('cluster_name', view(SELECT ...));从源码看,该功能注册于 TableFunctionView.cpp 与 registerTableFunctions.cpp(registerTableFunctionView与registerTableFunctionViewIfPermitted被同时注册)。TableFunctionView::parseArguments通过ASTFunction::tryGetQueryArgument()从函数参数中取出SELECT子查询并克隆保存;若缺少查询参数,则抛出BAD_ARGUMENTS异常("Table function 'view' requires a query argument")。getActualTableStructure则根据是否启用新分析器,分别调用InterpreterSelectQueryAnalyzer::getSampleBlock或对应的旧解释器路径推导视图的结果结构,从而让view(...)在任何需要表对象的位置都可以完成列结构推导。
在 StorageDistributed.cpp 中也能看到该表函数被用于构建分布式查询引用,说明view从一开始就面向remote/cluster/distributed等跨节点查询场景设计。
2.apply语法:对整列集合批量应用函数
发布说明引入了apply高阶语法,允许对多列统一应用某个函数,并支持链式叠加。原文示例:
SELECT * apply(length) apply(max) FROM wide_string_table;该语句会先对wide_string_table的所有字符串列逐一执行length,再对结果集做max,从而一次性求出所有字符串列的最大长度。发布说明同时提示该语法还提供另外两种变体形式(可通过后续文档与测试进一步确认具体形式),这一语法显著简化了"对宽表所有列做同一种变换"的书写成本,也减少了需要手写多列表达式的样板代码。
3.system.query_log与system.query_thread_log新增微秒级起始时间
system.query_log与system.query_thread_log系统表新增query_start_time_microseconds字段,将查询起始时间精确到微秒。在 QueryLog.cpp 中可以看到该列被定义为DateTime64(6)类型("Start time of query execution with microsecond precision"),并在 QueryLogElement.h 中与time_t query_start_time(秒级)并列存储Decimal64 query_start_time_microseconds。日志落盘时,QueryLog.cpp 会将秒级起始时间写入ColumnUInt32、微秒级时间写入ColumnDateTime64。
这一字段的价值在于:当同一秒内并发查询较多、需要精确排序或进行亚秒级耗时分析时,微秒级时间戳可以避免秒级精度造成的排序歧义;查询线程日志(query_thread_log)同步获得该字段,也便于将线程级事件与查询级时间线精确对齐。
4. 聚合函数RankCorrelationSpearman:斯皮尔曼秩相关系数
新增聚合函数RankCorrelationSpearman,用于计算两列样本的斯皮尔曼秩相关系数,是 Issue #11769,内部继承StatisticalSample<Float64, Float64>,核心算法流程为:
- 分别对两组样本调用
computeRanksAndTieCorrection计算秩(rank)及并列校正; - 取两样本数量的较小值作为有效样本量
size(因为 NaN 会被跳过,两侧样本量可能不等); - 计算秩差平方和
d^2,套用公式1 - 6 * Σd² / (n * (n² - 1))得到最终系数。
该函数在 registerAggregateFunctions.cpp 中完成注册,适用于相关性分析、特征筛选等统计场景,与既有的corr(皮尔逊)形成互补。
5. 按查询生成数据库的工具
发布说明还提到新增了"database generation by query util"(按查询生成数据库的工具),这是 Issue #10973 的延续。该工具面向需要依据查询模式反推/构造数据库结构的测试与基准场景,与仓库中的查询生成、模糊测试设施配合使用。
三、性能优化:分布式查询的 LIMIT / ORDER BY 下推
本次性能改进聚焦分布式查询:在同时开启optimize_skip_unused_shards与optimize_distributed_group_by_sharding_key的前提下,对使用GROUP BY sharding_key的分布式查询优化了LIMIT、LIMIT BY、ORDER BY的处理,使得这类查询可以更早地在分片侧完成排序与裁剪,减少传输到协调节点的数据量。这一优化对基于分片键做聚合 + 排序 + 限量取数的典型报表查询有明显收益。
四、改进项:从集成组件到内部机制
1. 流式/集成引擎的健壮性与能力增强
- StorageRabbitMQ:大幅重构,包括连接与通道故障处理、正确的提交(commit)语义、插入失败处理、更完善的 exchange 配置、队列持久化与队列恢复(resume)能力,并新增队列相关设置,同时修复了相应测试。
- Kafka 引擎:为每个 consumer 提供独立线程,并为 Kafka 等流式引擎引入独立的线程池,以提升消费吞吐与并行度。
- Redis 引擎/字典:新增 Redis
requirepass授权支持,允许连接启用密码保护的 Redis 实例。
2. 数据类型与类型系统
- DateTime 精度参数:为
DateTime类型增加精度(precision)参数支持,与DateTime64对齐了声明方式,便于统一表达亚秒精度时间。 - 宽整数实现替换:将 wide integers 从 boost multiprecision 替换为 cerevra/int 的实现(对应 PR #14229),减少对 boost 的依赖并改善 Int128/Int256 等类型的编译与运行效率。
- MaterializeMySQL 主键隐式非空:与 MySQL 行为保持一致,隐式将主键列转换为 NOT NULL,修复 Issue #14114 中主键可为空导致的问题。
3. 系统表与观测性
system.part_log新增default_compression_codec列:记录 data part 的默认压缩编码(codec),便于排查各 part 实际使用的压缩配置。- 新设置
system_events_show_zero_values:控制system.events(以及相关事件表)是否展示值为 0 的事件,默认关闭可保持输出精简,开启后可完整看到全部事件计数。该设置已在 StorageSystemEvents.cpp 与 Settings.cpp 中实现。 - obfuscator 支持 UUID 类型:脱敏工具(programs/obfuscator)现在可以处理 UUID 类型数据,解决 Issue #13163。
4. 查询执行细节
- 并行构建多组 JOIN / IN 集合:为包含多个不同
IN subquery的查询并行创建集合,可略微提升此类查询的性能。 - TTL 在合并时物化:如果 TTL 此前未被物化(materialize),现在会在合并过程中应用 TTL,减少后台独立 TTL 任务的排队压力,也与第一节的 TTL 并发控制形成配套。
- MySQL 协议处理
SET @@var = value:MySQL 处理端对SET @@var = value语句直接返回OK并忽略执行。这是因为部分 MySQL 驱动在握手后会用SET @@查询做环境初始化(Issue #9336),此前会导致兼容性问题。 - 禁止
toStartOf*系列函数使用空时区参数:空time_zone参数现在会直接报错,避免静默产生错误结果。 - S3 请求关闭加速:存在进行中的 S3 请求时,服务关闭流程提速,缩短停机时间。
- ConfigProcessor 路径拼接改用
std::filesystem::path:跨平台拼接配置文件路径更稳健(PR #14558)。
五、Bug 修复亮点
20.9.1.4585-prestable 修复了一批涉及执行引擎与存储安全的问题,其中几个值得特别关注:
arrayJoin()在 lambda 中被捕获导致 LOGICAL_ERROR:修复了arrayJoin在 lambda 表达式内部使用时的错误捕获问题。GRANT ALL非全局层级执行错误:修复了在非全局(non-global)层级执行GRANT ALL时的行为,涉及权限系统 PR #13987。- 禁止在
ALIAS列上声明CODEC:明确拒绝在 ALIAS 列上指定压缩编码,避免无效配置(修复 Issue #13911)。 - SSD cache 复合键外部字典的元组大小校验:对 SSD cache 场景下 complex key 外部字典的 tuple 大小做更严格的检查,修复 Issue #13981。
EXPLAIN PIPELINE graph=1的 QueryPlan 生命周期:修复嵌套解释器场景下 QueryPlan 提前析构导致的问题。ALTER LIVE VIEW ... REFRESH异常:修复执行该语句时抛出的异常。- 对
AS table_function创建的表执行ALTER崩溃:修复对由表函数创建的表执行 ALTER 时的崩溃(修复 Issue #14212)。 PipelineExecutor自身异常导致查询挂起:当异常发生在 PipelineExecutor 内部时立即停止查询执行,防止偶发的查询挂起(PR #14334 及后续补全 PR #14402)。- 单 part 分区的错误合并分配:修复表存在单 part 分区时合并任务分配错误的问题,避免产生错误的数据 part 组合。
- SysVinit 服务指令代理到 systemd:若系统使用 systemd,则 SysVinit 的 restart/start/stop/reload 会代理到 systemd,保证服务管理行为一致。
topK聚合函数数组大小溢出检查:增加topK的数组大小溢出校验。发布说明特别提示,此前用户可构造精心设计的参数导致服务崩溃(Issue #14452),这是一个安全隐患修复。
六、构建 / 测试 / 打包改进
- 集成测试改用默认基础配置:集成测试不再依赖隐式配置,所有配置变更通过
main_configs、user_configs、dictionaries参数显式声明,测试环境与生产环境的行为差异更可控。 - backport 脚本逻辑修正:修复了此前对任何 100% 红色 label 都会触发的异常逻辑。
- 修复缺失的
#include <atomic>:消除特定编译环境下因头文件缺失导致的编译失败。 - 为 clang 11 构建做准备:提前适配新编译器。
- 降低 debug 构建体积:移除
Functions目录的调试信息以减小 debug 二进制体积(该调整主要服务于使用旧链接器的内部项目)。 - cmake 默认启用 ccache:系统检测到 ccache 时默认开启,显著加速重复构建。
七、升级与验证建议
综合该发布说明,升级到 20.9 系列(从 20.8 基线)时建议按以下顺序规划:
- 确认 TTL 使用情况:若存在 delete TTL 表,优先安排全分片一次性升级;否则先在升级窗口执行
SYSTEM STOP TTL MERGES,升级完成后恢复。 - 验证新字段与设置:升级后抽查
system.query_log.query_start_time_microseconds是否为合法的DateTime64(6)数据,确认system_events_show_zero_values、system.part_log.default_compression_codec等新观测能力按预期工作。 - 回归流式引擎:若使用 Kafka/RabbitMQ/Redis 集成,重点回归消费、提交与断线重连路径,因为这些组件在 20.9 中改动较大(Kafka 独立消费者线程、RabbitMQ 连接/提交重构、Redis 授权)。
- 关注安全性修复:
topK溢出、GRANT ALL权限、PipelineExecutor挂起等修复涉及安全与稳定性,建议优先跟进。
八、参考资源
- v20.9.1.4585-prestable 发布说明原文
- MergeTree 设置定义(含 TTL 合并并发设置)
- ReplicatedMergeTree TTL 合并入队判断
- view 表函数实现 与 表函数注册
- query_log 微秒级起始时间字段
- RankCorrelationSpearman 实现
- system_events_show_zero_values 相关实现
说明:发布说明中标注为 "FIXME" 的标题与以 "..." 占位的条目(如 PR #14523 与 PR #14368)在正式版发布时会有更完整的描述;本文所引用的设置默认值、字段类型与算法细节均以当前仓库源码为准。
【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考