查询缓存与预热:缓存策略、热点治理与查询加速

系统讲解 ClickHouse 查询缓存与预热实践:查询结果缓存(Query Cache)的配置与适用场景、profile events 分析查询成本、Prepared Statements、预聚合与物化视图作为「预热」手段、热点查询治理(重复查询去重)、缓存失效策略,以及缓存与实时性的权衡。

1. 查询缓存的价值

OLAP 查询常重复执行:仪表盘每小时刷新同一 SQL、监控面板轮询同一聚合。每次都全量扫描既浪费 CPU/IO,也拉长响应。

无缓存:每条重复查询都重算 → 慢 & 费资源
有缓存:命中直接返回结果 → 快 & 省资源
预热:把"高成本查询"提前算好 → 冷启动变热启动

一句话总结:缓存的本质是"用内存换重复计算"——对固定模式的高频查询,一次计算、多次复用。


2. 查询结果缓存(Query Cache)

ClickHouse 从 v23.x 起提供官方 Query Cache,缓存查询结果集。

2.1 启用与配置

<clickhouse>
  <query_cache>
    <max_size_in_bytes>104857600</max_size_in_bytes> <!-- 100MB -->
    <max_entries>1024</max_entries>
    <max_entry_size_in_bytes>1048576</max_entry_size_in_bytes>
    <max_entry_size_in_rows>30000000</max_entry_size_in_rows>
  </query_cache>
</clickhouse>

2.2 使用

-- 强制使用缓存
SELECT sum(revenue) FROM sales SETTINGS use_query_cache = 1;

-- 带缓存条件:结果小、确定性查询
SELECT count() FROM events SETTINGS use_query_cache = 1;

2.3 什么查询适合缓存

适合不适合
固定 SQL 的仪表盘结果超大
低频变化的数据涉及随机函数
小结果集聚合依赖系统时间
高重复率实时性要求毫秒级

2.4 缓存的限制

- 结果需确定性(无随机、无 currentDate 差异敏感)
- 大结果集不缓存(超过 max_entry_size 直接旁路)
- 缓存默认按查询文本精确匹配

一句话总结:Query Cache 适合"结果小、重复多、数据低频变化"的查询;它替代不了物化视图,但填补了"临时结果复用"的空档。


3. 分析查询成本:profile events

优化前先量化:哪些查询最贵、贵在哪。

-- 查看查询事件统计
SELECT
  query,
  read_rows,
  read_bytes,
  memory_usage,
  query_duration_ms,
  ProfileEvents['QueryCacheHits'] AS cache_hits,
  ProfileEvents['QueryCacheMisses'] AS cache_misses
FROM system.query_log
ORDER BY query_duration_ms DESC
LIMIT 20;

3.1 关键 profile events

事件含义
SelectedRows扫描行数(决定耗时)
SelectedBytes扫描字节数
QueryCacheHits/Misses缓存命中/未命中
CachedReadBufferReadBytes页缓存命中
MergeTreeDataReadRows实际读出的行

3.2 找热点查询

-- 重复度最高的查询(可作为缓存/预热候选)
SELECT
  normalizeQuery(query) AS q,
  count() AS cnt,
  avg(query_duration_ms) AS avg_ms
FROM system.query_log
WHERE event_date = today() AND type = 'QueryFinish'
GROUP BY q
ORDER BY cnt DESC
LIMIT 20;

一句话总结:调优的第一步是用 query_log 找到"重复多、耗时长"的查询——它们是缓存与预热最值得投资的对象。


4. 预热手段:物化视图与预聚合

4.1 物化视图 = 终极预热

对"固定聚合 + 高频查询"的场景,物化视图把聚合结果增量预存,查询直接读聚合表:

CREATE MATERIALIZED VIEW mv_daily_sales
ENGINE = AggregatingMergeTree()
ORDER BY (day, product)
AS SELECT
    toDate(ts) AS day,
    product,
    sumState(revenue) AS revenue_sum
FROM sales
GROUP BY day, product;

4.2 查询

-- 直接查物化视图,秒回(无需扫原始表)
SELECT day, product, sumMerge(revenue_sum)
FROM mv_daily_sales
WHERE day = today() AND product = 'apple';

4.3 预热策略对比

手段粒度更新方式适合
Query Cache查询结果自动(LRU)临时重复查询
物化视图聚合结果增量实时固定聚合模式
预聚合宽表明细ETL 刷新多维分析
分区预计算分区级分区 TTL时间维度

一句话总结:物化视图把"查询时计算"变成"写入时计算",是结构性最强的预热——高频固定聚合应当优先考虑它。


5. 热点查询治理

5.1 治理流程

1. 识别热点:query_log 找高重复 + 高耗时
2. 分类:
   - 固定聚合 → 物化视图
   - 临时重复 → Query Cache
   - 可下沉 → 预计算宽表
3. 执行改造 + 对比延迟
4. 纳入监控:跟踪热点的耗时回归

5.2 报表场景实践

-- 报表原查询(每次全扫)
SELECT
  toDate(ts) AS day,
  countDistinct(user_id) AS dau
FROM events
WHERE ts >= today() - 7
GROUP BY day;

-- 治理:建物化视图 + 查询走聚合表
CREATE MATERIALIZED VIEW mv_dau
ENGINE = AggregatingMergeTree()
ORDER BY day
AS SELECT
  toDate(ts) AS day,
  uniqState(user_id) AS dau_state
FROM events
GROUP BY day;

SELECT day, uniqMerge(dau_state) AS dau FROM mv_dau;

5.3 缓存命中率的监控

-- 缓存命中率
SELECT
  countIf(ProfileEvents['QueryCacheHits'] > 0) AS hits,
  countIf(ProfileEvents['QueryCacheMisses'] > 0) AS misses,
  hits / (hits + misses) AS hit_rate
FROM system.query_log
WHERE event_date = today();

一句话总结:热点治理不是"加个缓存"就完事,而是"识别 → 分类 → 改造 → 监控"的闭环;物化视图承接结构性热点,Query Cache 承接临时热点。


6. 缓存失效与实时性权衡

6.1 新鲜度问题

Query Cache 缓存的是"某个时刻的结果"
  → 新数据写入后,缓存可能过期
  → 需要明确:实时性 vs 缓存收益

取舍策略:
  高频报表:可接受 1-5 分钟延迟 → 用缓存
  实时监控:毫秒级新鲜 → 慎用缓存
  折中:短 TTL + 分区级失效

6.2 失效策略

策略机制
时间过期按 LIFETIME/TTL 定期失效
手动清空SYSTEM DROP QUERY CACHE
分区级失效新分区写入触发相关缓存失效
查询跳过特定查询关闭缓存
-- 手动清空缓存
SYSTEM DROP QUERY CACHE;

-- 单条查询不用缓存
SELECT count() FROM events SETTINGS use_query_cache = 0;

6.3 决策表

数据变化查询重复实时要求建议
低频高宽松Query Cache + 物化视图
高频高严格物化视图(增量实时)
低频低宽松不用缓存
高频低严格直接查,优化索引

一句话总结:缓存与实时性是同一枚硬币的两面——用"数据变化率 × 查询重复度 × 实时要求"三维决策,而不是无脑加缓存。


7. 其他查询加速手段

7.1 Prepared Statements

PREPARE my_q AS
  SELECT * FROM events WHERE user_id = {id: UInt64};
EXECUTE my_q USING id = 42;
-- 避免每次解析 SQL,降低解析开销

7.2 分片并行与集群缓存

分布式查询:GLOBAL IN / GLOBAL JOIN 缓存子查询结果
  减少跨分片重复拉取

7.3 分区修剪

-- 让查询只扫需要的分区
SELECT * FROM events
WHERE ts >= '2026-09-01' AND ts < '2026-09-29';
-- 配合分区键,物理跳过无关分区

8. 生产实践清单

主题核心结论
Query Cache适合小结果、重复多、变化慢的查询
成本分析query_log + profile events 定位热点
结构性预热物化视图增量预聚合,固定聚合首选
热点治理识别 → 分类 → 改造 → 监控闭环
失效权衡数据变化率 × 重复度 × 实时性决策
辅助手段Prepared、GLOBAL 缓存、分区修剪

缓存不是银弹,而是一套分层体系:物化视图做结构性预热、Query Cache 做临时复用、分区修剪做物理剪枝。把每一层用在合适的位置,热点查询才能真正"秒回"。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「数据库」更多文章

  1. 时序分析最佳实践:时间序列建模、降采样与异常检测 SQL
  2. 字典与维度表 JOIN:Dictionaries、dictGet 与星型模型优化
  3. 复制表与跨机房容灾:ReplicatedMergeTree、双活架构与脑裂防护