ClickHouse v20.8.15.11-lts 版本详解:分布式连接、Mutation 约束与复制表并发修复全解析

原创2026-09-09 18:48:301,918 阅读
文章标签:数据库列式数据库OLAP大数据数据分析实时分析

ClickHouse v20.8.15.11-lts 版本详解:分布式连接、Mutation 约束与复制表并发修复全解析

v20.8.15.11-lts 是 ClickHouse 20.8 LTS(长期支持)分支的又一个 Bugfix 版本,相比前序 v20.8.14.4-lts,本版本没有引入新功能,而是集中修复了分布式查询连接数控制、Mutation 权限边界、ReplicatedMergeTree 并发操作、Poco SSL 竞态以及 Docker 启动脚本等一批在生产环境中真实影响稳定性的缺陷。本文以 docs/changelogs/archive/v20.8.15.11-lts.md 为骨架,结合当前仓库源码,逐条剖析每个修复背后的根因、涉及代码路径与升级验证方法,帮助使用 20.8 LTS 分支的团队理解本次升级的价值与风险。

版本背景:LTS 分支的补丁发布节奏

ClickHouse 的版本号采用 MAJOR.MINOR.PATCH-build-<channel> 形式。20.8 是当时的 LTS 主版本,15 为补丁序号,11 为构建号,后缀 -lts 表明该构建属于长期支持通道,只包含经过精选的 Bugfix(通过 backport 机制从主分支移植回来)。这一点在变更日志的标题中体现得很直接:

### ClickHouse release v20.8.15.11-lts FIXME as compared to v20.8.14.4-lts

标题中残留的 FIXME 说明该文件是发布工程脚本自动生成的草稿标记,随后由维护者替换为正式差异描述。整个变更日志分为两个章节:Bug Fix(9 条精选修复)与 NO CL ENTRY(1 条无变更记录的回滚项),没有 New Feature、Improvement 或 Performance Improvement 章节——这正是 LTS 补丁版的典型特征。

修复一:max_distributed_connections 并发控制修正

问题现象

变更日志首条修复(backport 自 [PR #17848])针对的是分布式查询的连接并发控制:

Fix max_distributed_connections (affects prefer_localhost_replica=1 and max_threads!=max_distributed_connections).

在修复前,当同时满足两个条件时会出现连接数失控或不足:一是 prefer_localhost_replica=1(默认开启,优先把查询派发给本机副本),二是 max_threads 与 max_distributed_connections 不相等。

参数语义

在 src/Core/Settings.cpp 中,该参数的官方定义如下:

DECLARE(UInt64, max_distributed_connections, 1024, R"(
The maximum number of simultaneous connections with remote servers for distributed processing of a single query to a single Distributed table.
We recommend setting a value no less than the number of servers in the cluster.
)", 0)

要点:

  • 默认值 1024,含义是"单条查询访问单个 Distributed 表时,与远端服务器建立的最大并发连接数";
  • 官方建议该值 不小于集群中服务器的数量,否则分布式扇出会被连接数瓶颈卡住;
  • 该设置与 max_threads 是独立的:max_threads 控制本机查询处理线程数,而本机线程中排除"从远端服务器拉取数据的线程",后者正是由 max_distributed_connections 约束(参见 src/Core/Settings.cpp 中 max_threads 的注释)。

底层机制:连接池按 min(max_distributed_connections, jobs_count) 收缩

在写入路径 src/Storages/Distributed/DistributedSink.cpp 中可以观察到该参数对线程池的实际约束:

size_t jobs_count = random_shard_insert ? 1 : (remote_jobs_count + local_jobs_count);
size_t max_threads = std::min<size_t>(settings[Setting::max_distributed_connections], jobs_count);
pool.emplace(
    CurrentMetrics::DistributedInsertThreads,
    CurrentMetrics::DistributedInsertThreadsActive,
    CurrentMetrics::DistributedInsertThreadsScheduled,
    max_threads, max_threads, jobs_count);

即连接线程池的实际规模是 min(max_distributed_connections, 任务数)。当 prefer_localhost_replica=1 时,本机副本被优先选择,本地任务与远端任务的比例会改变 jobs_count 的构成;若 max_threads 与 max_distributed_connections 不一致,旧逻辑在计算"还需要建立多少远端连接"时会出现偏差——这正是本次修复的核心。从当前源码结构看,修复后的逻辑统一以该设置为唯一上限对连接池做收缩,避免超额建连。

一个重要的后续:该 backport 被回滚

值得特别注意的是,变更日志的 NO CL ENTRY 章节记录了本次 backport 的命运:

NO CL ENTRY: 'Revert "Backport #17848 to 20.8: Fix max_distributed_connections"'([PR #21940],Maksim Kita)

也就是说,max_distributed_connections 的修复虽然进入 20.8 分支,但随后又被回滚(通常意味着该修复在 20.8 的代码基线上引发了新的回归,或因依赖的其他修复未同步而无法独立生效)。这是 LTS 补丁发布中常见的"先试后撤"现象:升级到 v20.8.15.11-lts 的读者不应期待该连接数问题已被最终解决,而应结合后续版本(如 20.8.16 及以后的补丁)确认最终状态。

修复二:Mutation 操作的表引擎白名单

问题现象

Now mutations allowed only for table engines that support them (MergeTree family, Memory, MaterializedView). Other engines will report a more clear error.

修复前,对不支持 Mutation 的表引擎执行 ALTER TABLE ... DELETE/UPDATE 时,报错信息不明确甚至行为异常。修复后,Mutation 被明确限定在 MergeTree 家族、Memory、MaterializedView 三类引擎内,其他引擎会给出清晰错误。

源码证据:默认实现直接抛出 NOT_IMPLEMENTED

在 src/Storages/IStorage.cpp 中,存储引擎基类的默认 mutate 实现如下:

void IStorage::mutate(const MutationCommands &, ContextPtr)
{
    throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Mutations are not supported by storage {}", getName());
}

同理,killMutation、waitForMutation、setMutationCSN 等配套接口在基类中全部默认抛 NOT_IMPLEMENTED(见 src/Storages/IStorage.cpp)。这构成了白名单机制的骨架:只有覆写了这些接口的引擎(MergeTree 家族及其派生、Memory、MaterializedView)才能执行 Mutation,其余引擎直接落入基类的清晰报错路径。

这类报错在仓库中还有类似形态,例如 MergeTree 对不可变磁盘的防护(src/Storages/MergeTree/MergeTreeData.cpp):"Mutations are not supported for immutable disk '{}'"——说明引擎层对"是否允许 Mutation"有系统性的多级校验。

修复三:ALTER DELETE 自引用谓词的死锁

Fix a deadlock in ALTER DELETE mutations for non replicated MergeTree table engines when the predicate contains the table itself.

触发场景:对非复制型 MergeTree(非 Replicated)表执行 ALTER TABLE t DELETE WHERE ...,且删除谓词(predicate)中引用了表自身(例如子查询 WHERE id IN (SELECT id FROM t WHERE ...))。修复前会导致 Mutation 任务死锁(对应 issue #20558)。

根因推断:从源码结构看,非复制 MergeTree 的 Mutation 依赖在表数据上获取锁与执行 DELETE 子查询两个动作。当谓词引用表自身时,执行子查询需要读取同一张表,而该表又处于"正在被 Mutation 修改"的锁定状态,两者互相等待即形成死锁。修复([PR #21477])通过调整谓词求值阶段对表自身的访问方式/锁顺序打破了这个循环等待。

实战建议:如果业务中需要基于"表自身内容"做删除,优先把筛选结果物化到临时表或先 SELECT 出 ID 列表再分步执行,避免单条 ALTER DELETE 的自引用子查询。

修复四:ReplicatedMergeTree 的 OPTIMIZE/ALTER 等待逻辑

Fix waiting for OPTIMIZE and ALTER queries for ReplicatedMergeTree table engines. Now the query will not hang when the table was detached or restarted.

修复前,对 ReplicatedMergeTree 执行 OPTIMIZE TABLE 或 ALTER TABLE 并等待完成时,如果操作过程中表被 detach(卸载)或重启,查询会无限期挂起。修复([PR #22118])让等待逻辑能感知表生命周期变化并及时返回,不再"卡死"。

这类等待语义在运维脚本中非常常见(例如发布流程里执行 OPTIMIZE ... FINAL 后等待其完成),因此该修复直接提升了自动化运维的健壮性:表被摘除或实例重启后,等待中的查询应快速失败而非挂起。

修复五:ReplicatedMerge 表的 Decimal 列 MODIFY COLUMN

Fix bug for ReplicatedMerge table engines when ALTER MODIFY COLUMN query doesn't change the type of decimal column if its size (32 bit or 64 bit) doesn't change.

触发场景:对 ReplicatedMerge 家族表执行 ALTER TABLE t MODIFY COLUMN c Decimal(18, 2),将列从一种 Decimal 类型改为另一种 同字节宽度 的 Decimal 类型(例如 32 位宽度内的精度调整,或 64 位宽度内的精度调整),修复前元数据变更不会真正生效。

根因推断:从实现细节看,Decimal 类型的物理宽度由总位数决定(Decimal32/Decimal64/Decimal128),而精度 P 与标度 S 的变化未必改变底层存储大小。旧逻辑在判断"类型是否变化"时按存储宽度比较,导致同宽度的 Decimal 精度/标度调整被误判为"无变化"而跳过 ALTER 记录,最终副本间元数据不一致。修复([PR #21728])在类型等价性判断中纳入了完整的 Decimal 参数(precision/scale)。

验证方法:升级后可执行:

CREATE TABLE t (d Decimal(10, 2)) ENGINE = ReplicatedMergeTree('/clickhouse/tables/t', 'r1') ORDER BY tuple();
ALTER TABLE t MODIFY COLUMN d Decimal(10, 4);
SHOW CREATE TABLE t;   -- 期望显示 Decimal(10, 4)

观察 SHOW CREATE TABLE 输出中该列的精度/标度是否同步更新。

修复六:不再对已覆盖数据分片执行 Mutation

Now clickhouse will not throw LOGICAL_ERROR exception when we try to mutate the already covered part.

在分区合并(merge)或 Mutation 迭代过程中,某个数据分片(part)可能已经被后续 Mutation 覆盖或并入更大的 part。旧逻辑再次尝试对其执行 Mutation 时会抛出 LOGICAL_ERROR 内部错误(issue #22013),导致 Mutation 任务异常中断。修复([PR #22291])让执行器跳过已覆盖的分片,保证任务顺利完成。

背景说明:LOGICAL_ERROR 是 ClickHouse 中表示"内部状态不一致"的错误级别,正常运行时不应出现。该修复消除了此类虚假错误,让 Mutation 在执行期间发生并发 merge 时依然可收敛。

修复七:Join 常量列物化问题

Join tries to materialize const columns, but our code waits for them in other places.

当 JOIN 操作试图物化(materialize)常量列(const columns)时,代码的其他部分仍在按"常量列"的形态等待处理,导致 JOIN 结果行为异常。修复([PR #18982])统一了 JOIN 流程中对常量列物化与消费的时序。

这类问题在"常量与普通列混合参与 JOIN 键/投影"的场景下出现,例如 SELECT ... FROM a JOIN b ON a.k = 1 或使用 constant 表达式作为 JOIN 条件的查询。

修复八:Poco SecureSocket 的 SSL 对象竞态

Fixed race on SSL object inside SecureSocket in Poco.

该修复位于 ClickHouse 内置的 Poco 网络库(base/poco 目录下的 Foundation/Net/NetSSL_OpenSSL 等模块)中。SecureSocket 在多线程并发读写时对底层 SSL 对象存在竞态,可能引发连接异常或崩溃。修复([PR #21456])通过为 SSL 对象增加同步保护消除了该竞态。

影响面:所有使用 TLS/SSL 的网络通道——包括 HTTPS 端口上的 Native/HTTP 协议连接、与外部系统的 TLS 交互等。在多线程高并发访问 HTTPS 端口的场景中尤其值得关注。

修复九:Docker entrypoint 在 LOG_PATH 为空时避免 chown "."

Docker entrypoint: avoid chown of . in case when LOG_PATH is empty.

修复([PR #22102])针对 Docker 启动脚本 docker/server 目录下的 entrypoint 逻辑:当配置中未设置 LOG_PATH(日志路径为空)时,旧脚本会对当前目录 . 执行 chown,可能导致文件属主被意外修改或启动失败。修复后仅在 LOG_PATH 非空时才执行目录属主调整。

部署影响:使用 Docker 镜像并自定义了不含 LOG_PATH 的配置的部署方式,升级后可避免容器启动时的属主异常。

变更日志中的元信息与后续跟进建议

NO CL ENTRY 章节的阅读方式

NO CL ENTRY 表示该 PR 没有附带的变更日志条目(changelog entry),通常是回滚类提交或纯内部提交。本版本中的回滚对象正是第一条 max_distributed_connections 修复,建议升级前确认:

-- 查看当前版本
SELECT version();

并对照官方发布说明,确认后续补丁版本(20.8.16+)中该修复是否被重新引入及最终形态。

升级验证清单

结合本次 9 条修复,建议在生产环境升级后依次验证:

验证点 对应修复 验证方式
分布式查询连接数 max_distributed_connections 设置 max_distributed_connections 与 max_threads 为不同值,观察 system.processes 与日志中的远端连接数
Mutation 白名单 引擎约束 对 Log、TinyLog 等引擎执行 ALTER TABLE ... DELETE,确认收到清晰的 NOT_IMPLEMENTED 错误而非内部异常
自引用谓词删除 死锁修复 执行谓词引用表自身的 ALTER DELETE,确认可正常完成而非挂起
Replicated 表等待 等待逻辑 在 OPTIMIZE TABLE ... FINAL 等待期间 detach 表,确认查询及时返回而非挂起
Decimal 类型变更 MODIFY COLUMN 同宽度 Decimal 精度调整后执行 SHOW CREATE TABLE 核对
SSL 并发 Poco 竞态 对 HTTPS 端口执行高并发压力测试,观察连接稳定性

总结

v20.8.15.11-lts 是一个典型的 LTS 补丁版本:没有新功能,全部精力用于消除 20.8 分支上的稳定性缺陷。其中 Mutation 白名单化 与 ReplicatedMerge 等待逻辑 的修复直接改善了运维可观测性与自动化脚本的健壮性;Decimal 列 MODIFY COLUMN 修复避免了副本间元数据漂移;而 max_distributed_connections 修复的"进入后又回滚"则提醒我们:LTS 分支的每个 backport 都需要在目标基线上独立验证,变更日志中的 NO CL ENTRY 章节同样值得仔细阅读。对于仍在 20.8 LTS 分支上的团队,建议在升级到本版本后按上文清单做一轮针对性的回归验证,并持续跟踪后续补丁对回滚项的最终处理。

登录后查看全文
ClickHouse