ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ClickHouse 26.10 的『读更少』:三条性能线怎么量化

ClickHouse 26.10 的『读更少』:三条性能线怎么量化

一句话结论:这个版本最值钱的不是 104 项新特性,而是五组「优化前 → 优化后」的数字;把绝对值换成倍率与单位口径之后,你会发现其中 67 倍那条收益是有前提的——前提写在你的 SQL 形态里,不写在版本号里。

项

内容

摘要

ClickHouse 26.10 是官方说的「第一次突破 100 项新特性」的版本。本文把发布会上给出的五组前后对比数字拆成倍率与单位口径,用零依赖脚本建立 Top-N 下推的收益上下界模型,说明哪些场景拿得到 67 倍、哪些场景只能拿到 1 倍,并给出灰度验证的具体方法。

前置知识

知道 GROUP BY / JOIN 会用哈希表;知道 MergeTree 的 granule 与 skip index;读过一次system.query_log

阅读时间

约 15 分钟

代码环境

Python 3.9+,零第三方依赖,不联网,输出确定(无随机源);脚本见_脚本/clickhouse_pushdown_model.py

读完你能做什么

能把公告数字换算成自己数据规模下的估计;能判断自己那条ORDER BY … LIMIT查询到底能不能吃到下推;能写出一条不依赖厂商数字的性能验证方案

一、26.10 是什么量级的版本

官方在 release call 上给出的三个计数:

计数项

数量

新特性

104(官方称这是第一次突破 100)

性能优化

225

Bug 修复

739

按 endoflife 类站点与新发布追踪的记录,26.10 是在 2026 年 10 月 22 日前后的 release call 上发布,随后滚动出26.10.2.103-stable这类补丁版本。也就是说,这是一次主版本级更新,不是小版本。

对使用者来说,104 项新特性里真正会改变日常写法的没那么多。下面按「能不能省资源、省多少」排了序。

二、官方给的五组数字

发布给出了五组明确的「前 → 后」对比(下表数字均出自官方 release call):

条目

优化前

优化后

倍率

官方测试口径

uniqExact 峰值内存

13.5 GB

4.6 GB

2.93x

1 亿个去重值

uniqExact 耗时

1.33 s

0.31 s

4.29x

同一条查询

Top-K 动态过滤读取量

9.45 GB

144 MB

67.20x

ORDER BY … LIMIT场景

LIKE / startsWith 峰值内存

275 MB

5 MB

55.00x

字符串过滤场景

merge join 耗时

9.10 s

0.78 s

11.67x

并行切分主键区间

这五组里藏着两条完全不同的线索:

  • 内存减半(2.93x)来自数据结构变化——大哈希表切换到 Robin Hood 哈希,官方表述为「GROUP BY、uniqExact、DISTINCT 和 JOIN 的内存可以减半」。
  • 读取量暴降(67.20x)来自执行策略变化——把ORDER BY … LIMIT K的第 K 名值下推到 MergeTree 扫描阶段当过滤器用,读完一批就能丢掉大量不可能入选的数据。

第三类是一条 string filtering 的实现改进:LIKE和startsWith在字符串读取时就地判定,不用先把整列物化出来,峰值内存从 275 MB 降到 5 MB。

三、机制讲清楚:三条优化了什么

3.1 Robin Hood 哈希为什么省内存

开放寻址哈希表在冲突变多时,探测序列会拉得很长,uniqExact这类高基数聚合要维持亿级的槽位,每个槽位的额外开销被放大到 GB 量级。Robin Hood 的做法是在插入时把「离理想槽位更远」的元素往前挪,让探测距离的分布变窄;分布窄意味着可以在更高的负载因子下工作,同样的元素数需要更少的槽位。

这解释了为什么这项改动同时省内存和省时间——1.33 s 到 0.31 s 那 4.29 倍不是巧合,探测距离变短直接减少了随机访存。

3.2 Top-K 下推为什么能把读取量砍掉两个数量级

关键不在「少读」,而在过滤器是动态的。执行过程中引擎已经知道当前排在第 K 位的值是多少,于是把这个值变成一把尺子:任何 granule,如果它的上界(或 skip index 的极值)够不到这把尺子,整块就不用读。

这条路径成立的前提有两个:数据有一定程度的排序可言,以及granule 上有可供判断的极值统计。两者缺一,下推就退化成全扫。

3.3 还有一处容易被忽略:列缓存

官方提到一个实验性质的 columns cache,把解压、反序列化之后的列直接留在内存里,服务于 MergeTree 与 Parquet。这条线的收益模式跟前两条不同——它不降低单次开销,而是降低重复开销:同一份列在多个查询里被反复解压,是 BI 类负载的常见形态。标注了 experimental,意味着生产启用前要在灰度实例上验一致性。

四、动手验证:把数字换成能用的口径

脚本_脚本/clickhouse_pushdown_model.py做三件事,本机实跑(Windows,Python 3.13.12)输出节选如下。

第 1 段:倍率与单位口径

条目 | 优化前 | 优化后 | 倍率 | 官方口径 -------------------------------------------------------------------------------------------- uniqExact 峰值内存 | 13.50 GiB | 4.60 GiB | 2.93 | 1 亿个去重值 uniqExact 耗时 | 1.33 s | 0.31 s | 4.29 | 同一查询 Top-K 动态过滤读取量 | 9.45 GiB | 144.00 MiB | 67.20 | ORDER BY … LIMIT 场景 LIKE / startsWith 峰值内存 | 275.00 MiB | 5.00 MiB | 55.00 | 字符串过滤场景 merge join 耗时 | 9.10 s | 0.78 s | 11.67 | 并行切分主键区间

第 2 段:换成单位口径

基数 | 原始每值字节 | 优化后每值字节 | 节省 -------------------------------------------------------------------------- 10,000,000 | 145.0 B → 1.35 GiB | 49.4 B → 471.04 MiB | 911.36 MiB 100,000,000 | 145.0 B → 13.50 GiB | 49.4 B → 4.60 GiB | 8.90 GiB 1,000,000,000 | 145.0 B → 135.00 GiB | 49.4 B → 46.00 GiB | 89.00 GiB

把「少了 8.9 GB」换成「每个值从 145 字节降到 49.4 字节」,才能拿去估自己那张表。这一步是全文最有价值的操作:公告的绝对值绑定在厂商的数据集上,单位口径不绑定。

第 3 段:Top-N 下推的收益区间

候选行数 N | LIMIT K | 不可跳过 s | 下推前读取 | 下推后读取 | 倍率 -------------------------------------------------------------------------------------------- 10,000,000 | 100 | 0.02 有序且有统计 | 10,000,000 行 | 208,292 行 | 48.0x 10,000,000 | 100 | 0.20 部分列要先读 | 10,000,000 行 | 2,008,292 行 | 5.0x 10,000,000 | 100 | 1.00 无序且无统计 | 10,000,000 行 | 10,008,292 行 | 1.0x 100,000,000 | 100 | 0.02 有序且有统计 | 100,000,000 行 | 2,008,292 行 | 49.8x 100,000,000 | 100 | 0.20 部分列要先读 | 100,000,000 行 | 20,008,292 行 | 5.0x 100,000,000 | 100 | 1.00 无序且无统计 | 100,000,000 行 | 100,008,292 行 | 1.0x

模型写在脚本注释里,这里复述一次:下推前要读满 N 行;下推后能跳过的部分约为N × (1 - s),其中 s 为「不可跳过占比」——要先读才能判断的列、完全乱序的数据段、缺min/max统计的 granule 都算在里面;剩下必须读的行数下界约为K + G(G 为 granule 行数,MergeTree 默认 8192)。

结论很直接:倍率的区间是 1 倍到 50 倍左右,落在哪儿由s决定,而s是可以测的,不需要猜。官方那 67.2 倍对应的s大约在千分之一量级——也就是几乎完全有序、统计齐全的那类表。

五、哪些还需要先看 SQL 形态

场景

为什么

预期

查询没有ORDER BY … LIMIT

没有 Top-K 目标值可下推

收益接近 0

GROUP BY基数低于十万

哈希表本身不是瓶颈

省内存不省时间

单分区的 merge join

并行切分主键区间的前提不存在

11.67 倍不成立

数据常驻页缓存

瓶颈从 IO 转到 CPU

要按缓存状态重测

六、另外三件会改变写法的事

除了性能线,26.10 里还有三项值得进技术评估:

  1. JOIN ON支持任意条件(包括范围 join 这种没有等值的场景),此前这类查询会直接失败;同时 merge join 可以按主键范围做并行切分。
  1. 数据湖形态更完整:Iceberg v3 的 deletion vector、Unity Catalog v2 同时覆盖 Delta Lake 与 Iceberg、以及内置实验性的 Iceberg REST catalog server。
  1. 内置 Web UI 里的 notebook 模式(查询 cell 与 Markdown cell),这在内部数据平台上会直接影响交付方式——不再必须外挂 Jupyter。

还有一项和 AI 场景直接相关:bit-packing 函数用于嵌入向量量化。做 RAG 或向量检索时,把 float 嵌入压几位带来的内存与带宽收益,往往比换一次索引结构更省钱,代价是召回率的微小变化。这类取舍必须自己实测,不能照搬别人的百分比。

七、灰度验证方案

要落到自己环境的口径只有一个:升级前在同一批查询上取数。

步骤

动作

1

选定 10–20 条代表性查询,固定参数,跑三遍去掉冷启动

2

在system.query_log里记录memory_usage与read_bytes

3

灰度实例升到 26.10,同参数重跑三遍

4

比较同查询的两列差值,而不是比较总耗时

5

特别注意ORDER BY … LIMIT类查询,看read_rows有没有明显下降

如果第 5 步没有任何变化,先别急着怀疑版本——回头看你的s。

八、适用边界与产业坐标

官方所有数字都是厂商在特定数据集上的结果,没有给出独立的第三方复现。columns cache 官方标注 experimental;notebook 模式、Trino 与 Kusto 方言处于 beta 阶段。这三者在生产上的取舍要各自评估,不能因为同处一个版本号而一起启用。

把视角从版本说明拉回工程:一个分析平台真正卡住的地方,经常不在引擎内部,而在引擎两侧——上游数据怎么稳定地进去,下游展示层怎么稳定地取走。尤其在 AI 场景里,向量数据与结构化数据通常存在两个系统里,检索要在两者之间来回跳;这时交界面上的可靠投递与重复消除,比引擎单次查询快多少更影响体感。

回到国内落地环境的约束:数据平台要部署在给定的信创软硬件生态里,存量系统往往还要保留原有调用方式。金蝶天燕 Apusic 这条线上,ADMQ 分布式消息队列承接数据链路的接入与投递语义,ACP 中间件云平台承接上层部署编排与纳管;两者要满足的不是单次查询的峰值数字,而是链路在国产化软硬件上能不能稳、能不能被运维接手。具体能力边界以各自的官方文档为准。

九、结论

  • 26.10 给出 104 项新特性、225 项性能优化、739 项 bug 修复;五组前后对比数字中,倍率集中在 2.93x(内存)到 67.20x(读取量)之间。
  • 「每值 145 字节 → 49.4 字节」这类单位口径,才是能跨环境迁移的信息;绝对值是厂商数据绑定值。
  • Top-K 下推的收益区间是 1 倍到约 50 倍,由「不可跳过占比 s」决定;官方的 67.2 倍对应 s 在千分之一量级。
  • 自己的s可测:灰度实例跑同一批查询,比system.query_log的read_rows与memory_usage。
  • columns cache、notebook 模式、Trino 与 Kusto 方言分属 experimental 与 beta,生产启用各自评估。

问一句:你们那条最慢的ORDER BY … LIMIT查询,升级前后的read_rows差了多少?如果没量过,先量一遍再决定要不要写进季度汇报。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进