让一条看板查询,少扫 230 倍的数据

一个你能自己跑的 ClickHouse 调优 demo:同一张 5000 万行的事件表,朴素 schema 和调优后的 schema 对比,用数据库自己的 query_log 度量。一条带过滤的看板查询,扫描量从 1.9 GiB 降到约 8 MiB,少扫约 230 倍数据,快 16 倍;一个日聚合用 projection 后快了 54 倍。一条命令就能从空库重建,CI 每次推送都会重跑,调优一旦退化,构建直接失败。

问题

分析表涨到几千万、上亿行,上面的看板就慢得像蜗牛。每条查询读的数据远超实际需要,延迟越来越高,在按量计费的数仓上,账单也跟着涨。常见的做法是加机器,也就是花钱把同样多余的数据扫得更快,而不是少扫一些。

约束:用数字证明,不靠感觉

调优建议好说难信。所以整件事都为“可度量、可复现”而建:两个 schema 上用完全相同的数据和查询,提升直接从 query_log 读出(读取字节数、读取行数、耗时),再用一条命令从空库重建整个基准,让任何人都能自己核对结果。

是什么在推动这些数字

  • **和查询方式匹配的排序键。**合适的 ORDER BY 加上分区,让 ClickHouse 能整块跳过数据,而不是逐块读。少扫 230 倍就是这么来的。
  • **给重聚合加 projection。**projection 预先算好日聚合,周期性报表读的是一个很小的物化结果,不用再扫基表,于是快了 54 倍。
  • 合适的列类型。LowCardinality 和更紧凑的列类型,从源头减少要读取和放进内存的数据量。
  • **回归检查。**基准测试在 CI 里每次推送都跑,如果哪次 schema 改动让查询又多扫了数据,构建就会失败,优化成果不会悄悄丢掉。

为什么这正是我擅长的问题

数据平台的可靠性是我最擅长的。2019 年以来的后端工作里,大部分时间都在负载下搭建和运维 ETL 与存储:一次七个微服务的跨库迁移,把吞吐从 50 提到 5000 QPS,还有一次 binlog 级恢复,零停机找回 100% 丢失的数据。这里的做法也一样:不争论性能,先测量,用数据库自己的数字证明改进,再加一道检查,让它不会悄悄变差。

证明

还能用在哪

任何查询扫得比该扫的多的分析或 OLAP 场景:产品分析、事件管道、报表数仓、数据一多就变慢的看板。如果一条查询很慢,却没人说得清原因,第一步是测出它实际读了多少,然后让它少读很多。

什么情况下这招不适用

  • 如果慢在网络、并发排队或客户端反序列化,重排物理布局帮不上忙。先量:read_bytes 正常,就不是这个问题。
  • 如果每条查询的过滤条件都不一样(真正的临时分析),一种排列没法同时伺候所有查询,收益会大幅缩水。
  • 如果你的瓶颈在写入而不在查询,那是另一个问题,用的是另一套工具。

要转述的话,就这一句

“他用数据库自己的日志量出扫描量降到 1/230,然后写了个 CI 门控,让它回不去。”