跳到主要内容

SQL Cache 查询缓存使用指南

SQL Cache 是 Doris 提供的查询结果缓存机制,将查询结果按 SQL 文本与数据版本等元数据缓存起来,使后续相同查询直接命中缓存返回,从而显著降低重复计算的开销。

阅读前 Checklist

在阅读本手册前,请确认你已了解或准备好以下内容:

  • 你的查询场景是否适合缓存(例如 T+1 离线分析、低频更新的数据)
  • 当前 Doris 版本是否支持 explain plan 查看 SQL Cache(建议 2.1.3+)
  • 是否清楚 SQL Cache 仅支持 OlapTable 内部表与 Hive 外部表
  • 是否了解非确定函数(如 now()random())会影响缓存命中率
  • 是否拥有 FE/BE 的配置修改权限(用于内存控制与全局开关)

在决定是否启用 SQL Cache 前,请先逐项核对你的查询是否满足以下条件:

  • 查询语句重复执行频率较高
  • 数据更新频率较低(缓存命中率高)
  • 查询不包含随机函数(如 random()
  • 每次查询的指标字段一致(不会动态增减)

一、概念介绍

SQL Cache 适用于数据更新频率较低的查询场景,通过缓存查询结果避免重复计算。

缓存命中的关键因素

SQL Cache 基于以下因素的组合唯一确定一条缓存数据:

关键因素说明
SQL 文本完全一致的 SQL 字符串
视图定义涉及视图的 DDL 定义
表和分区的版本数据是否发生过变更
用户变量和结果值SQL 中引用的变量当前取值
非确定函数和结果值例如 now()random() 的运算结果
行策略定义Row Policy 配置
数据脱敏定义Data Masking 配置

任一因素发生变化(SQL 改写、查询字段或条件不同、数据更新导致版本变更等),缓存均不会命中。对于多表 Join 查询,任一表更新都会导致分区 ID 或版本号变更,进而无法命中缓存。

适用场景

  • 强烈推荐:T+1 更新场景。数据在凌晨更新,第一次查询从 BE 获取结果并写入缓存,后续相同查询直接从缓存返回。
  • 可选使用:实时更新数据。仍可启用 SQL Cache,但命中率较低。
  • 支持范围:当前支持 OlapTable 内部表与 Hive 外部表。

二、使用限制

非确定函数的影响

定义:非确定函数指其运算结果与输入参数之间无法形成固定关系的函数。

函数行为说明是否可利用 Cache
now()返回当前秒级时间,每秒变化一次同一秒内可复用
date(now())将秒级时间转为日级粒度同一天内可复用(推荐)
random()每次调用结果都不同几乎无法命中缓存

优化建议:将细粒度时间转为粗粒度时间,例如使用 select * from tbl where dt = date(now()) 替代 select * from tbl where dt = now(),让同一天的查询都能命中缓存;尽量避免在查询中使用 random() 等强非确定函数。

指标字段扩展的影响

SQL Cache 严格按查询字段缓存结果,不支持用「少指标缓存」满足「多指标查询」。

场景行为影响
已缓存 2 个指标的结果,新查询请求 3 个指标缓存无法复用,需重新执行查询命中率下降,新结果会写入新缓存条目

优化建议:尽量保持业务侧报表与查询模板的指标字段稳定;如需新增指标,建议改写查询模板而非临时扩展,以避免缓存无法复用。

三、实现原理

FE 实现原理

FE 接收到查询请求后的处理流程:

  1. 元数据查找:在内存中以 SQL 字符串为 Key 查找元数据(包含表/分区版本)。
  2. 版本比对:若元数据未变化,说明数据未变更,可复用缓存。
  3. 跳过解析:跳过 SQL 解析与优化流程,依据一致性哈希定位到对应 BE。
  4. 结果返回
    • 命中 BE 缓存:直接返回结果给客户端。
    • 未命中:执行完整的 SQL 解析、优化与计算流程。
  5. 结果回写:BE 计算完成后,FE 将结果存入对应 BE,并在自身内存中记录元数据,供下次查询复用。

特殊优化:若 SQL 优化阶段判断结果仅含 0 行或 1 行数据,FE 会将结果直接保存在自身内存中,以加速后续相同查询。

BE 实现原理

  • 通过一致性哈希选择一个 BE 存放结果,结果以 HashMap 结构存储于 BE 内存中。
  • 读写 Cache 时,使用 SQL 字符串等元数据信息的摘要作为 Key,快速检索结果数据。

四、快速上手

步骤 1:开启或关闭 SQL Cache

目的:在 Session 或全局级别启用 SQL Cache(默认关闭)。

-- 在当前 Session 打开 SQL Cache,默认是关闭状态
set enable_sql_cache=true;
-- 在当前 Session 关闭 SQL Cache
set enable_sql_cache=false;

-- 全局打开 SQL Cache,默认是关闭状态
set global enable_sql_cache=true;
-- 全局关闭 SQL Cache
set global enable_sql_cache=false;

说明:Session 级配置仅对当前会话生效;全局配置对所有新建 Session 生效。

步骤 2:检查查询是否命中 SQL Cache

方法 A:使用 explain plan(适用于 Doris 2.1.3+)

目的:通过查询计划判断是否命中。 命令:执行 explain plan <你的 SQL>说明:当查询计划树中出现 LogicalSqlCachePhysicalSqlCache 节点时,即表明查询命中了 SQL Cache。

> explain plan select * from t2;

+------------------------------------------------------------------------------------------------------------+
| Explain String(Nereids Planner) |
+------------------------------------------------------------------------------------------------------------+
| ========== PARSED PLAN (time: 28ms) ========== |
| LogicalSqlCache[1] ( queryId=711dea740e4746e6-8bc11afe08f6542c ) |
| +--PhysicalResultSink[39] ( outputExprs=[id#0, name#1] ) |
| +--PhysicalOlapScan[t2]@0 ( stats=12 ) |
| |
| ========== ANALYZED PLAN ========== |
| LogicalSqlCache[1] ( queryId=711dea740e4746e6-8bc11afe08f6542c ) |
| +--PhysicalResultSink[39] ( outputExprs=[id#0, name#1] ) |
| +--PhysicalOlapScan[t2]@0 ( stats=12 ) |
| |
| ========== REWRITTEN PLAN ========== |
| LogicalSqlCache[1] ( queryId=711dea740e4746e6-8bc11afe08f6542c ) |
| +--PhysicalResultSink[39] ( outputExprs=[id#0, name#1] ) |
| +--PhysicalOlapScan[t2]@0 ( stats=12 ) |
| |
| ========== OPTIMIZED PLAN ========== |
| PhysicalSqlCache[3] ( queryId=711dea740e4746e6-8bc11afe08f6542c, backend=192.168.126.3:9051, rowCount=12 ) |
| +--PhysicalResultSink[39] ( outputExprs=[id#0, name#1] ) |
| +--PhysicalOlapScan[t2]@0 ( stats=12 ) |
+------------------------------------------------------------------------------------------------------------+

方法 B:查看 Profile(适用于 Doris 2.1.3 以前版本)

目的:在没有 explain plan 支持的版本中确认命中情况。 命令:开启 Profile 后查看 Execution Summary。 说明:若 Is Cached: 字段显示为 Yes,则表明该查询命中了 SQL Cache。

Execution  Summary:
- Parse SQL Time: 18ms
- Nereids Analysis Time: N/A
- Nereids Rewrite Time: N/A
- Nereids Optimize Time: N/A
- Nereids Translate Time: N/A
- Workload Group: normal
- Analysis Time: N/A
- Wait and Fetch Result Time: N/A
- Fetch Result Time: 0ms
- Write Result Time: 0ms
- Doris Version: 915138e801
- Is Nereids: Yes
- Is Cached: Yes
- Total Instances Num: 0
- Instances Num Per BE:
- Parallel Fragment Exec Instance Num: 1
- Trace ID:
- Transaction Commit Time: N/A
- Nereids Distribute Time: N/A

五、指标监控

FE 监控指标

接口http://${FE_IP}:${FE_HTTP_PORT}/metrics 说明:指标统计只增不减,FE 重启后从 0 重新计数。

# 已经把 1 个 SQL 写入到缓存中
doris_fe_cache_added{type="sql"} 1

# 命中了 2 次 SQL Cache
doris_fe_cache_hit{type="sql"} 2

BE 监控指标

接口http://${BE_IP}:${BE_HTTP_PORT}/metrics 说明:不同 Cache 可能存放在不同 BE 中,需收集所有 BE 的 Metrics 才能得到完整信息。

# 当前 BE 内存中存在 1205 个 Cache
doris_be_query_cache_sql_total_count 1205

# 当前所有 Cache 占用 BE 内存约 44KB
doris_be_query_cache_memory_total_byte 44101

六、内存控制

FE 内存控制

FE 中的 Cache 元数据使用弱引用:当 FE 内存不足时,自动释放最近最久未使用的元数据。同时支持以下参数限制:

参数默认值含义
sql_cache_manage_num100元数据条目上限,超过后自动释放最近最久未使用项
expire_sql_cache_in_fe_second300元数据过期时间(秒),超过未访问则自动释放
cache_result_max_row_count3000结果行数上限,超过则不创建 SQL Cache
cache_result_max_data_size31457280 (30MB)结果大小上限(字节),超过则不创建 SQL Cache

配置命令(实时生效,每个 FE 都需配置;持久化需写入 fe.conf):

-- 最多存放 100 个 Cache 元数据
ADMIN SET FRONTEND CONFIG ('sql_cache_manage_num'='100');

-- 300 秒未访问该 Cache 元数据后自动释放
ADMIN SET FRONTEND CONFIG ('expire_sql_cache_in_fe_second'='300');

-- 默认超过 3000 行结果时不创建 SQL Cache
ADMIN SET FRONTEND CONFIG ('cache_result_max_row_count'='3000');

-- 默认超过 30MB 时不创建 SQL Cache
ADMIN SET FRONTEND CONFIG ('cache_result_max_data_size'='31457280');

BE 内存控制

参数默认值 (示例)含义
query_cache_max_size_mb256Cache 占用内存的稳定上限
query_cache_elasticity_size_mb128弹性扩展空间,超过 max+elasticity 时触发淘汰至 max 之下

配置文件be.conf(修改后需重启 BE 生效)。

-- 当 Cache 内存空间超过 query_cache_max_size_mb + query_cache_elasticity_size_mb 时,
-- 释放最近最久未使用的 Cache,直至占用内存低于 query_cache_max_size_mb。
query_cache_max_size_mb = 256
query_cache_elasticity_size_mb = 128

七、Troubleshooting:排查缓存失效原因

下表汇总了常见的缓存未命中或失效原因,以及对应的检查方向:

序号失效原因典型操作排查建议
1表/视图结构变化drop tablereplace tablealter tablealter view检查表与视图的近期 DDL 历史
2表数据变化insertdeleteupdatetruncate检查导入与变更日志,确认数据版本是否变化
3用户权限被移除revoke核对查询账户的权限变更
4使用了非确定函数select random()select now()改用粗粒度函数或常量参数
5变量值变化select * from tbl where dt = @dt_var检查会话变量的取值是否一致
6Row Policy / Data Masking 变化调整了行策略或脱敏策略核对策略最近的变更记录
7结果行数超限超过 cache_result_max_row_count(默认 3000 行)调整阈值或缩小查询结果集
8结果大小超限超过 cache_result_max_data_size(默认 30MB)调整阈值或减少返回字段

八、FAQ

Q1:SQL Cache 默认是否开启? A:默认关闭。需通过 set enable_sql_cache=true 在 Session 级开启,或 set global enable_sql_cache=true 全局开启。

Q2:SQL Cache 支持哪些表类型? A:当前支持 OlapTable 内部表与 Hive 外部表。

Q3:实时更新的数据可以使用 SQL Cache 吗? A:可以,但每次数据更新都会导致分区版本变化,使缓存失效,命中率较低。更适合 T+1 离线分析场景。

Q4:多表 Join 查询的缓存如何失效? A:只要 Join 中的任一表发生数据变更,分区 ID 或版本号即变化,整条查询缓存均无法命中。

Q:之前缓存了 2 个指标,现在查询 3 个指标能复用缓存吗? A:不能。SQL Cache 严格按查询字段缓存结果,无法用部分指标的缓存满足更多指标的查询请求,需重新执行查询。

Q5:now() 函数会让缓存完全失效吗? A:不会完全失效。now() 返回秒级时间,同一秒内的相同查询可复用缓存;建议使用 date(now()) 转为日级粒度以扩大缓存命中范围。

Q6:缓存数据存放在哪里? A:绝大多数结果存放在 BE 内存(HashMap 结构);当结果仅 0 行或 1 行时,FE 会直接保存在自身内存中。

Q7:FE 重启后缓存是否还在? A:FE 中的元数据会丢失,监控指标也会重置为 0;BE 内存中的结果同样在重启后失效。

九、对比与延伸

维度SQL CachePartition Cache(如适用)
缓存粒度整条 SQL 的结果集按分区缓存中间结果
适用更新模式T+1、低频更新部分分区频繁更新、其他分区稳定
命中条件SQL 文本 + 全部依赖元数据未变涉及的分区版本未变
失效粒度任一依赖变更即整体失效仅未命中分区需重新计算

提示:本表中 Partition Cache 仅作对比参考,具体可用性以当前 Doris 版本特性为准。