Spark SQL CACHE TABLE 详解
一、语法
sql
-- 1. 缓存已存在的表/视图
CACHE TABLE table_name;
-- 2. 缓存查询结果为临时表
CACHE TABLE table_name AS SELECT ...;
-- 3. 懒加载(首次查询时才缓存)
CACHE LAZY TABLE table_name AS SELECT ...;
二、缓存原理
┌─────────────────────────────────────────────────────────┐
│ SparkSession │
│ ┌─────────────────────────────────────────────────┐ │
│ │ CacheManager │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Table A │ │ Table B │ │ Table C │ │ │
│ │ │ (缓存) │ │ (缓存) │ │ (缓存) │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Executor 内存 │ │
│ │ BlockManager (实际存储缓存数据) │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
缓存流程:
- 执行查询生成 DataFrame
- 将 DataFrame 注册到 Session 的 CacheManager
- 触发 Action(非 LAZY 模式立即触发)将数据缓存到 Executor 内存
三、缓存级别
| 级别 | 说明 | 适用场景 |
|---|---|---|
| MEMORY_ONLY | 仅内存,内存不足时分区丢失 | 小数据集,内存充足 |
| MEMORY_AND_DISK | 内存优先,不足时溢写到磁盘 | Spark SQL 默认,通用场景 |
| DISK_ONLY | 仅磁盘 | 大数据集,内存紧张 |
| MEMORY_ONLY_SER | 内存+序列化,节省空间 | 内存紧张但 CPU 有余 |
| OFF_HEAP | 堆外内存 | 需避免 GC 影响 |
SQL 限制 :CACHE TABLE 默认 MEMORY_AND_DISK,不可自定义。
DataFrame API 可自定义:
scala
df.persist(StorageLevel.MEMORY_ONLY)
四、LAZY vs 非 LAZY
sql
-- 非懒加载:立即执行查询并缓存
CACHE TABLE tmp AS SELECT * FROM big_table WHERE dt='2026-07-01';
-- ↑ 执行计划立即触发,数据加载到内存
-- 懒加载:仅记录缓存意向,首次查询时才缓存
CACHE LAZY TABLE tmp AS SELECT * FROM big_table WHERE dt='2026-07-01';
-- ↑ 此时什么都不做
SELECT * FROM tmp; -- 这里才真正缓存
选择建议:
- 确定会用 →
CACHE TABLE(立即加载) - 可能不用 →
CACHE LAZY TABLE(按需加载)
五、作用域与生命周期
┌────────────────────────────────────────────────┐
│ Spark Application │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Session A │ │ Session B │ │
│ │ ┌──────────┐ │ │ ┌──────────┐ │ │
│ │ │ cache_t1 │ │ │ │ cache_t1 │ │ │
│ │ │ (独立) │ │ │ │ (独立) │ │ │
│ │ └──────────┘ │ │ └──────────┘ │ │
│ └──────────────┘ └──────────────┘ │
│ ↓ ↓ │
│ Session 关闭 Session 关闭 │
│ 缓存自动释放 缓存自动释放 │
└────────────────────────────────────────────────┘
| 特性 | 说明 |
|---|---|
| 作用域 | Session 级别,不同 Session 隔离 |
| 生命周期 | Session 存活期间有效 |
| 释放时机 | Session 关闭 / UNCACHE TABLE / CLEAR CACHE |
| 跨 Session | 不共享,同名表互不影响 |
六、常用操作
sql
-- 1. 缓存表
CACHE TABLE tmp AS
SELECT waybill_code, status, update_time
FROM app.test;
-- 2. 查看缓存状态
DESCRIBE EXTENDED tmp;
-- 输出中查找: Is Cached: true
-- 3. 解除单个缓存
UNCACHE TABLE tmp;
-- 4. 清除所有缓存
CLEAR CACHE;
七、实际应用场景
场景1:维表重复关联
sql
-- 小维表被多次关联,缓存避免重复扫描
CACHE TABLE dim_region;
CACHE TABLE dim_status;
SELECT a.*, r.region_name, s.status_name
FROM fact_order a
JOIN dim_region r ON a.region_id = r.id
JOIN dim_status s ON a.status = s.code;
SELECT a.*, r.region_name
FROM fact_order_detail a
JOIN dim_region r ON a.region_id = r.id;
场景2:中间结果复用
sql
-- 中间结果被多次引用
CACHE TABLE tmp_filtered AS
SELECT * FROM big_table WHERE dt = '2026-07-01' AND status IN (1, 31);
-- 后续多次查询直接走内存
SELECT COUNT(*) FROM tmp_filtered WHERE status = 1;
SELECT COUNT(*) FROM tmp_filtered WHERE status = 31;
SELECT waybill_code FROM tmp_filtered LIMIT 100;
场景3:迭代计算
sql
-- 机器学习/图计算迭代场景
CACHE TABLE graph_nodes AS SELECT * FROM nodes;
-- 多轮迭代读取同一份数据
-- 第1轮
SELECT /*+ BROADCAST(graph_nodes) */ ... FROM graph_nodes;
-- 第2轮
SELECT /*+ BROADCAST(graph_nodes) */ ... FROM graph_nodes;
八、注意事项
| 问题 | 说明 | 解决方案 |
|---|---|---|
| 内存溢出 | 缓存过多导致 OOM | 控制缓存数据量,及时 UNCACHE |
| 数据过期 | 源表更新后缓存未刷新 | 手动 UNCACHE 后重新 CACHE |
| 占用 Executor 内存 | 影响其他任务执行 | 评估内存容量,选择合适数据集 |
| 小文件问题 | 缓存表有小文件 | 缓存前 REPARTITION |
| 重复缓存 | 同一表多次 CACHE 无效 | Spark 会检查是否已缓存 |
九、性能调优建议
sql
-- 1. 缓存前优化文件数
CACHE TABLE tmp AS
SELECT /*+ REPARTITION(100) */ * FROM big_table;
-- 2. 只缓存需要的列
CACHE TABLE tmp AS
SELECT col1, col2, col3 FROM big_table; -- 不要 SELECT *
-- 3. 及时释放
UNCACHE TABLE tmp; -- 用完及时释放
十、与 Hive Metastore 的区别
| 对比项 | CACHE TABLE | CREATE TABLE |
|---|---|---|
| 存储位置 | Executor 内存 | HDFS/S3 |
| 作用域 | Session 级别 | 全局(Metastore) |
| 持久性 | Session 结束即消失 | 永久保存 |
| 跨 Session | 不可见 | 所有 Session 可见 |
| 同名冲突 | 不同 Session 不冲突 | 全局冲突 |
十一、总结
- CACHE TABLE = 内存缓存 + Session 隔离
- 默认级别 = MEMORY_AND_DISK
- 适用场景 = 维表关联、中间结果复用、迭代计算
- 核心原则 = 够用即可,用完释放