分类 :3.存储引擎 | 篇章:09 Cache 与 Last 查询

适用版本:TDengine v3.x(v3.3.x / v3.4.x) | 最后更新:2026-05-18
在物联网场景中,"查询每个设备的最新值"是最高频的操作之一。TDengine 通过 CACHEMODEL 机制在 VNode 内维护一套基于 RocksDB 的独立缓存层,让 LAST() 和 LAST_ROW() 查询无需扫描 TSDB 数据文件,直接从缓存返回结果。
核心概念速查表
| 概念 | 说明 |
|---|---|
| CACHEMODEL | 缓存模式(none / last_row / last_value / both) |
| CACHESIZE | 每个 VNode 的缓存大小上限(MB) |
| LAST_ROW() | 返回最后一行(不忽略 NULL) |
| LAST() | 返回每列最后一个非 NULL 值 |
| cache.rdb | RocksDB 持久化缓存文件 |
| 缓存 Key | (uid, column_id, lflag) 三元组 |
详细解析
1. CACHEMODEL 四种模式
CACHEMODEL 配置:
┌─────────────────────────────────────────────────────┐
│ none(默认) │
│ 不维护任何缓存 │
│ LAST()/LAST_ROW() → 需要扫描 TSDB 数据文件 │
│ 适用:不常查最新值的场景 │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ last_row │
│ 只缓存每张子表的最后一行 │
│ LAST_ROW() → 从缓存直接返回(极快) │
│ LAST() → 仍需扫描文件(因为要找非NULL值) │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ last_value │
│ 缓存每列的最后一个非 NULL 值 │
│ LAST() → 从缓存直接返回 │
│ LAST_ROW() → 仍需扫描文件 │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ both │
│ 同时缓存 last_row 和 last_value │
│ LAST()/LAST_ROW() 都从缓存直接返回 │
│ 内存消耗最大,但查询最快 │
└─────────────────────────────────────────────────────┘
2. LAST_ROW 与 LAST 的区别
数据示例:
子表 d1001 的最近 5 行:
┌─────────┬────────────┬──────────┬──────────┐
│ ts │ temperature│ humidity │ status │
├─────────┼────────────┼──────────┼──────────┤
│ T1 │ 25.3 │ 60 │ 'OK' │
│ T2 │ 25.4 │ NULL │ 'OK' │
│ T3 │ NULL │ 62 │ NULL │
│ T4 │ 25.5 │ NULL │ NULL │
│ T5 │ NULL │ NULL │ 'ERR' │ ← 最后一行
└─────────┴────────────┴──────────┴──────────┘
LAST_ROW(temperature, humidity, status):
→ (NULL, NULL, 'ERR') ← 最后一行的值,不管是否为 NULL
LAST(temperature, humidity, status):
→ (25.5, 62, 'ERR') ← 每列最后一个非 NULL 值
(temperature 取 T4 的 25.5, humidity 取 T3 的 62)
3. 缓存存储结构
缓存的物理存储(RocksDB):
每个 VNode 下:
cache/
└── cache.rdb ← RocksDB 数据库文件
Key 结构: (uid, column_id, lflag)
- uid: 子表的唯一 ID
- column_id: 列编号
- lflag: 0=last_row, 1=last_value
Value 结构: (timestamp, value)
- timestamp: 该值对应的时间戳
- value: 实际数据值
示例缓存内容(子表 uid=1001, 3列):
Key │ Value
───────────────────────┼──────────────────
(1001, 0, 0) │ (T5, T5) ← last_row 的 ts 列
(1001, 1, 0) │ (T5, NULL) ← last_row 的 col1
(1001, 2, 0) │ (T5, NULL) ← last_row 的 col2
(1001, 3, 0) │ (T5, 'ERR') ← last_row 的 col3
(1001, 1, 1) │ (T4, 25.5) ← last_value 的 col1
(1001, 2, 1) │ (T3, 62) ← last_value 的 col2
(1001, 3, 1) │ (T5, 'ERR') ← last_value 的 col3
4. 缓存更新机制
写入时的缓存更新(Write-Through):
数据写入 MemTable 的同时,同步更新 Cache:
写入新行 (T6, 26.0, NULL, 'OK')
│
├──→ 写入 MemTable(正常路径)
│
└──→ 更新缓存:
last_row:
所有列更新为 T6 行的值
(1001, 1, 0) → (T6, 26.0)
(1001, 2, 0) → (T6, NULL)
(1001, 3, 0) → (T6, 'OK')
last_value:
只更新非 NULL 的列:
(1001, 1, 1) → (T6, 26.0) ✓ 非NULL,更新
(1001, 2, 1) → 不更新 ✗ 写入值为NULL
(1001, 3, 1) → (T6, 'OK') ✓ 非NULL,更新
删除时的缓存失效:
DELETE FROM d1001 WHERE ts >= T5
│
└──→ 缓存可能失效
→ 需要回扫 TSDB 确定新的 last 值
→ 异步重建缓存
5. 缓存与查询加速
LAST() 查询的执行路径对比:
无缓存(CACHEMODEL=none):
SELECT LAST(temperature) FROM d1001
│
▼
打开 MemTable → 扫描最后几个块 → 找非 NULL 值
打开最新 FileSet → 从后往前读数据块
→ 可能需要读取多个块直到找到非 NULL
延迟:10~100ms
有缓存(CACHEMODEL=both):
SELECT LAST(temperature) FROM d1001
│
▼
查询 RocksDB: Key=(uid_d1001, col_temperature, 1)
→ 直接返回缓存值
延迟:< 1ms
海量子表场景的加速效果:
SELECT LAST(temperature) FROM meters GROUP BY tbname
无缓存:逐表扫描文件 → N × 10ms
有缓存:逐表查缓存 → N × 0.01ms
加速比:100~1000 倍
6. CACHESIZE 参数
CACHESIZE 含义:
CREATE DATABASE power CACHEMODEL both CACHESIZE 16;
- CACHESIZE 单位:MB
- 作用范围:每个 VNode 的缓存上限
- 默认值:1 MB
缓存大小估算:
每个子表每列一个缓存条目
条目大小 ≈ Key(16B) + Value(8B + 列宽) + RocksDB 开销
例:10万子表 × 20列 × 2种(last_row+last_value) = 400万条目
每条目 ≈ 50B → 总计 ≈ 200MB
如果 CACHESIZE 设为 16MB → RocksDB 使用 16MB 内存
→ 超出部分自动落盘到 cache.rdb 文件
→ 查询仍可命中,但可能需要磁盘读取
最佳实践:
- CACHESIZE 不必覆盖所有缓存数据
- RocksDB 自有 Block Cache 机制
- 热点子表的缓存自然保持在内存中
- 冷门子表的缓存存在磁盘上(仍比扫描 TSDB 快)
7. 缓存的持久化与恢复
缓存的持久性:
RocksDB cache.rdb 是持久化的:
- VNode 重启后缓存仍然有效(无需重建)
- 只有 DELETE/DROP 操作可能导致缓存失效需要重建
缓存重建场景:
1. 首次启用 CACHEMODEL → 后台扫描全部数据构建缓存
2. VNode 迁移/恢复 → 从数据文件重建
3. DELETE 操作后 → 局部重建受影响的条目
重建开销:
- 全量重建需要扫描所有 FileSet
- 海量子表 + 大量文件时可能耗时较长
- 重建期间查询可能退化为文件扫描
8. CACHEMODEL 变更的影响
sql
-- 启用缓存(触发后台重建)
ALTER DATABASE power CACHEMODEL 'both';
-- 关闭缓存(释放内存和磁盘)
ALTER DATABASE power CACHEMODEL 'none';
-- 修改缓存大小
ALTER DATABASE power CACHESIZE 32;
变更影响:
none → both:
触发全量缓存重建(后台执行)
重建期间 LAST() 仍可执行(走文件扫描)
重建完成后自动切换到缓存路径
both → none:
立即释放缓存内存
删除 cache.rdb 文件
后续 LAST() 查询走文件扫描路径
代码示例
配置与使用
sql
-- 推荐配置:IoT 监控场景
CREATE DATABASE iot_monitor
CACHEMODEL 'both'
CACHESIZE 16;
-- 查询设备最新值(命中缓存,亚毫秒级)
SELECT LAST_ROW(ts, temperature, humidity)
FROM iot_monitor.meters
GROUP BY tbname;
-- 查询每列最后一个有效值
SELECT LAST(temperature), LAST(humidity)
FROM iot_monitor.d1001;
验证缓存效果
sql
-- 对比查询延迟
-- Step 1: CACHEMODEL='none' 下的查询时间
ALTER DATABASE power CACHEMODEL 'none';
SELECT LAST(temperature) FROM meters GROUP BY tbname;
-- 记录耗时
-- Step 2: CACHEMODEL='both' 下的查询时间
ALTER DATABASE power CACHEMODEL 'both';
-- 等待缓存重建完成
SELECT LAST(temperature) FROM meters GROUP BY tbname;
-- 对比耗时
性能考量
缓存的空间与时间权衡
| 子表数 | CACHEMODEL | 内存增加 | LAST() 延迟 |
|---|---|---|---|
| 1 万 | none | 0 | 10~50ms |
| 1 万 | both | ~10MB | < 1ms |
| 100 万 | none | 0 | 100~500ms |
| 100 万 | both | ~500MB | 1~5ms |
RocksDB vs 内存缓存
| 特性 | 纯内存缓存 | RocksDB 缓存 |
|---|---|---|
| 重启后 | 丢失 | 保留 |
| 容量限制 | 受内存限制 | 可落盘扩展 |
| 访问延迟 | 纳秒级 | 微秒级(内存命中)/ 毫秒级(磁盘) |
| 适合场景 | 少量子表 | 海量子表 |
FAQ
Q1: CACHEMODEL='both' 内存会不会太大?
CACHESIZE 参数限制了 RocksDB 的内存用量。超出内存部分自动写入 cache.rdb 磁盘文件。即使 CACHESIZE 设置较小,常用子表的数据仍然能在内存中命中。
Q2: 乱序写入会影响缓存吗?
乱序数据(时间戳早于当前缓存的时间戳)不会更新缓存------缓存只保留"最新时间戳"的值。只有时间戳更新(大于缓存中的时间戳)的写入才会触发缓存更新。
Q3: DELETE 后 LAST() 返回错误值?
DELETE 会触发缓存失效和重建。在重建完成前的短暂窗口内,可能需要回退到文件扫描。系统保证最终一致性------重建完成后结果正确。
Q4: 只查 LAST_ROW 不查 LAST,应该选 last_row 还是 both?
选 last_row。这样只维护最后一行的缓存,内存消耗最小。both 模式需要为每列独立追踪最后一个非 NULL 值,更新和存储开销更大。
参考
系统构架篇
- 01-《TDengine 整体架构全景》
- 02-《集群拓扑深度解析》
- 03-《MNode 内部机制深度解析》
- 04-《RPC 通信层深度解析》
- 05-《VNode 生命周期》
- 06-《RAFT 共识协议》
- 07-《端到端的消息流》
数据模型
- 01-《数据库创建与参数详解》
- 02-《超级表/子表/普通表》
- 03-《支持数据类型深度解析》
- 04-《TDengine Tag 设计哲学与 Schema 变更机制》
- 05-《TDengine 虚拟表实现原理》
存储引擎
- 01-《TDengine 存储引擎概览》
- 02-《TDengine MemTable 深度解析》
- 03-《TDengine WAL 预写日志机制》
- 04-《TDengine 数据文件格式》
- 05-《TDengine Commit 与 Flush 机制 》
- 06-《TDengine Compaction 合并策略 》
- 07-《TDengine 数据保留与 TTL》
- 08-《TDengine 压缩编码机制》
关于 TDengine
TDengine 专为物联网IoT平台、工业大数据平台设计。其中,TDengine TSDB 是一款高性能、分布式的时序数据库(Time Series Database),同时它还带有内建的缓存、流式计算、数据订阅等系统功能;TDengine IDMP 是一款AI原生工业数据管理平台,它通过树状层次结构建立数据目录,对数据进行标准化、情景化,并通过 AI 提供实时分析、可视化、事件管理与报警等功能。