分类 :9.索引 | 篇章 :02 SMA 索引
适用版本:TDengine v3.x(v3.3.x / v3.4.x) | 最后更新:2026-06-04
SMA(Small Materialized Aggregation)是 TDengine 在数据块/文件级别自动维护的统计索引。它不像传统 B-Tree 索引,而是为聚合查询和过滤剪枝服务。本文聚焦"SMA 作为索引"的视角。
核心概念速查表
| 概念 | 说明 |
|---|---|
| Block SMA | 数据块统计(min/max/sum/count) |
| File SMA | 数据文件统计 |
| Skipping Index | 跳过索引(基于范围) |
| Bloom Filter | 布隆过滤(视版本) |
| Index Effectiveness | 索引有效性 |
详细解析
1. SMA 索引的定位
传统索引(B-Tree)vs SMA 索引:
B-Tree 索引(Tag 索引):
- 行级精确定位
- 适合等值/范围查询
- 占空间大
SMA 索引:
- 块级统计跳过
- 适合聚合 + 大范围过滤
- 占空间极小(< 1%)
- 时序数据场景优势明显
定位:
- Tag 索引:跨子表筛选
- SMA 索引:单子表内的块剪枝
2. Block SMA 工作原理
每个数据块 4096 行,存储元数据:
Block Header {
time_range: [ts_min, ts_max],
row_count: 4096,
cols: [
{ name: "current", min: 20.5, max: 25.8, sum: 95000, count: 4096 },
{ name: "voltage", min: 218, max: 223, sum: 902160, count: 4096 },
{ name: "phase", min: 0.45, max: 0.55, sum: 2048, count: 4096 },
]
}
查询时的判断:
SELECT * FROM d001 WHERE current > 100 AND ts > T1;
对每个块:
if block.time_range.max < T1: 跳过
if block.cols.current.max < 100: 跳过
else: 进入块读取
3. SMA 用于聚合优化
SELECT AVG(current) FROM d001 WHERE ts > T1;
执行:
① 找到符合时间范围的所有块
② 对每个块:
- 直接读取 SMA 中的 sum 和 count
- 不解码原始数据
③ 累加:total_sum / total_count
效果:
- 100 万行数据:扫描 ~250 个块
- 仅读块 Header(~50KB)
- 不读 .data 主体(数 GB)
- 毫秒返回
支持函数:
✓ COUNT, SUM, AVG(需 sum/count)
✓ MIN, MAX
✓ FIRST, LAST(部分场景)
✗ PERCENTILE, MEDIAN(需扫行)
4. SMA 用于过滤剪枝
WHERE 子句的剪枝:
WHERE current > 100
对每个块:
if block.cols.current.max <= 100: 整块跳过
if block.cols.current.min > 100: 整块满足,可能加速
else: 块部分匹配,需扫描行
示例:
100 万行分 250 个块
WHERE current > 100:
- 200 块 max < 100 → 跳过
- 30 块 min > 100 → 全选
- 20 块部分匹配 → 扫描行
实际扫描行数:20 × 4096 = 82K(节省 92%)
5. 时间维度的 SMA
时间列是隐式 SMA 关键:
每块的 time_range 用于时间裁剪:
WHERE ts > T → 跳过所有 ts_max < T 的块
WHERE ts BETWEEN T1 AND T2 → 跳过范围外
时间裁剪是最有效的剪枝(时序数据按时间分布)
三级裁剪:
① 文件级:跳过整个 .data 文件
② 块级:跳过文件内部分块
③ 行级:块内进一步过滤(按数据列)
6. SMA 索引效果监控
sql
-- EXPLAIN ANALYZE 看实际效果
EXPLAIN ANALYZE
SELECT AVG(current) FROM meters
WHERE ts > now-1h AND voltage > 200;
-- 关键指标:
-- blocks_total: 总块数
-- blocks_scanned: 实际扫描的块数
-- blocks_skipped: 跳过的块数(SMA 生效)
-- rows_examined: 实际读取行数
-- rows_returned: 返回行数
-- 比率 = blocks_skipped / blocks_total
-- 高比率说明 SMA 有效
7. 影响 SMA 索引效果的因素
影响因素:
① 块大小(MAXROWS):
- 小块:更细粒度剪枝,但块多
- 大块:单块容纳更多数据,剪枝粒度粗
- 默认 4096 通常合适
② 数据分布:
- 同块内数据相似 → 剪枝效果好
- 同块内数据差异大 → 剪枝难
③ 列类型:
- 数值类型剪枝有效
- 字符串类型 SMA 较弱
④ 数据排序:
- 按 ts 自然排序 → 时间剪枝最优
- 数据列乱序 → 数据列剪枝弱
8. SMA 索引设计建议
最佳实践:
① 让常查询列与时间有相关性:
- 时间相近的数据值也相近 → 块内 min/max 紧凑
- 利于过滤剪枝
② 控制块大小:
- 默认 MAXROWS 4096 适合大多数场景
- 极少行/极多行可能需调整
③ 不要过度依赖 SMA:
- 复杂表达式无法用 SMA
- 字符串 LIKE 等无 SMA
④ 配合 Tag 索引:
- Tag 索引筛选子表
- SMA 索引剪枝块
- 两者协同最强
代码示例
SMA 效果验证
sql
-- 准备数据
INSERT INTO d001 USING meters TAGS('Beijing', 1)
VALUES (NOW-1d, 25, 220, 0.5);
-- ... 写入百万行
-- 测试 1: 时间范围(SMA 最有效)
EXPLAIN ANALYZE
SELECT COUNT(*) FROM d001 WHERE ts > now-1h;
-- 期望 blocks_skipped 大
-- 测试 2: 数据列过滤
EXPLAIN ANALYZE
SELECT COUNT(*) FROM d001 WHERE current > 100;
-- 期望部分 blocks_skipped(取决于数据分布)
-- 测试 3: 聚合(SMA 直接命中)
EXPLAIN ANALYZE
SELECT AVG(current) FROM d001 WHERE ts > now-1d;
-- 期望 rows_examined 几乎为 0(直接用 SMA sum/count)
提升 SMA 命中
sql
-- ✗ 复杂过滤无法用 SMA
SELECT * FROM meters WHERE current > AVG(current);
-- ✓ 简单常量比较
SELECT * FROM meters WHERE current > 100;
-- ✗ 函数包裹
SELECT * FROM meters WHERE ABS(phase - 0.5) < 0.1;
-- ✓ 直接范围
SELECT * FROM meters WHERE phase BETWEEN 0.4 AND 0.6;
性能考量
SMA 索引性能特点
| 场景 | SMA 效果 |
|---|---|
| 时间裁剪 | 极高(90%+ 跳过) |
| 数值列过滤 | 中~高(取决于分布) |
| 聚合 SUM/AVG/COUNT | 极高(直接读 SMA) |
| MIN/MAX | 极高(直接读 SMA) |
| 字符串过滤 | 低 |
| 复杂表达式 | 无效 |
存储开销
| 项 | 占比 |
|---|---|
| 块 SMA | < 1% |
| 文件 SMA | < 0.1% |
| TSMA/RSMA | 1%~10% |
| Tag 索引 | < 0.1% |
FAQ
Q1: SMA 索引可以手动创建吗?
块/文件 SMA 自动维护。TSMA/RSMA 需要 CREATE TSMA 或建库 RETENTIONS。
Q2: SMA 索引会失效吗?
写入新数据 → 新块自动有 SMA。Compaction 重算 SMA。极端故障 + 数据修复后 SMA 自动重建。
Q3: 字符串列 SMA 弱怎么办?
- 转为枚举(Tag)
- 用前缀提取列
- 或接受较低性能
Q4: 如何判断 SMA 是否生效?
EXPLAIN ANALYZE 看 blocks_skipped。比率 > 80% 说明有效。
Q5: 不同版本 SMA 行为差异?
总体方向一致。新版本可能增加 Bloom Filter、字符串 min/max 等。具体看版本 Release Note。
参考
系统构架篇
- 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 压缩编码机制》
- 09-《TDengine Cache 与 Last 查询加速》
- 10-《TDengine 逻辑计划生成》
查询引擎
- 01-《TDengine 查询引擎概览》
- 02-《TDengine SQL 解析与词法分析》
- 03-《TDengine 语义分析与 AST 重写》
- 04-《TDengine 逻辑计划生成》
- 05-《TDengine 物理计划生成》
- 06-《TDengine 扫描算子》
- 07-《TDengine 聚合算子》
- 08-《TDengine 聚合算子》
- 09-《TDengine 连接算子》
- 10-《TDengine 排序、填充与投影》
- 11-《TDengine 分布式查询执行》
- 12-《TDengine EXPLAIN 与查询优化》
数据写入
- 01-《TDengine SQL INSERT》
- 02-《TDengine 无模式写入》
- 03-《TDengine STMT 写入》
- 04-《TDengine 写入内部流程》
- 05-《TDengine 数据更新删除》
数据订阅
- 01-《TDengine 数据订阅》
- 02-《TDengine 订阅 vs Kafka》
- 03-《TDengine TMQ 消费流程》
- 04-《TDengine 内部机制》
- 05-《TDengine TMQ 最佳实践》
预聚合
索引
关于 TDengine
TDengine 专为物联网IoT平台、工业大数据平台设计。其中,TDengine TSDB 是一款高性能、分布式的时序数据库(Time Series Database),同时它还带有内建的缓存、流式计算、数据订阅等系统功能;TDengine IDMP 是一款AI原生工业数据管理平台,它通过树状层次结构建立数据目录,对数据进行标准化、情景化,并通过 AI 提供实时分析、可视化、事件管理与报警等功能。
