分类 :9.索引 | 篇章 :03 索引使用指南
适用版本:TDengine v3.x(v3.3.x / v3.4.x) | 最后更新:2026-07-13
索引设计是数据库性能的根基。本文整合 TDengine 多类索引(Tag 索引、SMA、TSMA、RSMA)的使用决策树,给出"看到查询模式立刻知道该建什么索引"的实用指导。
核心概念速查表
| 索引 | 自动/手动 | 加速对象 |
|---|---|---|
| 块 SMA | 自动 | 块级过滤/聚合 |
| 文件 SMA | 自动 | 文件级裁剪 |
| 第一 Tag 索引 | 自动 | 子表筛选 |
| 额外 Tag 索引 | 手动 | 多 Tag 筛选 |
| TSMA | 手动 | 长期范围聚合 |
| RSMA | 数据库级 | 分层存储 |
详细解析
1. 索引决策树
查询模式 → 推荐索引:
① WHERE ts > T → 块/文件 SMA(自动)
② WHERE tag = 'v' → Tag 索引
- 第一 Tag:默认有
- 其他 Tag:手动 CREATE INDEX
③ WHERE current > 100 → 块 SMA(自动)
④ AVG/SUM/MAX/MIN 简单聚合 → 块 SMA + TSMA
⑤ 大时间范围聚合(月/年)→ TSMA / RSMA
⑥ 子表筛选高基数 Tag → 重新设计 Schema(Tag 改为列)
⑦ 复杂表达式聚合 → 流计算
2. 实战 1:电力监控
sql
-- 场景:1 亿设备数据,常用查询模式:
-- A. 单设备最近 1 小时数据
-- B. 某城市所有设备聚合
-- C. 全网日报小时聚合
-- 表设计(Tag 决定子表数)
CREATE STABLE meters (
ts TIMESTAMP, current FLOAT, voltage INT
) TAGS (
city VARCHAR(32), -- 200 个城市(低基数)
district VARCHAR(64), -- 几万区县
factory_id VARCHAR(32) -- 几十万工厂
);
-- 子表数 = 工厂数 × 设备/厂 ≈ 1 亿
-- A 查询: WHERE tbname='d001' AND ts > now-1h
-- → 自动用块 SMA 时间裁剪
-- → 单子表查询,无需 Tag 索引
-- B 查询: WHERE city='Beijing' AND ts > now-1h
-- → 第一 Tag 索引(city)
-- → 自动用块 SMA
-- C 查询: SELECT _wstart, AVG(current) FROM meters
-- WHERE ts > now-1y INTERVAL(1h)
-- → 创建 TSMA 加速
CREATE TSMA meters_1h ON meters
FUNCTION(AVG(current), MAX(current), MIN(current))
INTERVAL(1h);
3. 实战 2:IoT 网关数据
sql
-- 场景:100 万传感器,500 种类型
-- 常用查询:
-- A. 单传感器历史
-- B. 某类型传感器统计
-- C. 异常事件检测
CREATE STABLE sensors (
ts TIMESTAMP, value FLOAT, status TINYINT
) TAGS (
sensor_type VARCHAR(32), -- 500 种(高频过滤)
factory_id VARCHAR(32), -- 100 个工厂
zone_id VARCHAR(32) -- 几千个区域
);
-- 默认 sensor_type 索引(第一 Tag)
-- factory_id 是常用过滤 → 显式建索引
CREATE INDEX idx_factory ON sensors (factory_id);
-- 异常事件流(实时)
CREATE STREAM s_anomaly INTO anomaly_log AS
SELECT _wstart, tbname, MAX(value) FROM sensors
WHERE status > 1
PARTITION BY tbname
INTERVAL(1m);
4. 实战 3:金融行情
sql
-- 场景:5000 只股票,秒级行情
-- 查询:
-- A. 单股票日 K
-- B. 板块/行业聚合
-- C. 全市场指数
CREATE STABLE quotes (
ts TIMESTAMP, price FLOAT, volume BIGINT, ...
) TAGS (
symbol VARCHAR(16), -- 5000 个股票代码(一对一子表)
sector VARCHAR(32), -- 30 个行业
exchange VARCHAR(16) -- 几个交易所
);
-- 默认 symbol 索引
-- sector 是常用聚合维度
CREATE INDEX idx_sector ON quotes (sector);
-- 日 K 用 TSMA
CREATE TSMA quotes_1d ON quotes
FUNCTION(FIRST(price) AS open, MAX(price) AS high,
MIN(price) AS low, LAST(price) AS close,
SUM(volume) AS volume)
INTERVAL(1d);
-- 历史归档用 RSMA(建库时)
CREATE DATABASE quotes_db
RETENTIONS '1s:7d,1m:90d,1h:5y';
5. 反模式与改进
反模式 1: 高基数 Tag
CREATE STABLE orders (...) TAGS (user_id BIGINT);
-- 1 亿用户 = 1 亿子表 = 元数据爆炸
改进:
CREATE STABLE orders (...) TAGS (region VARCHAR(32), shard INT);
-- user_id 作为普通列
-- shard = user_id % 1000 控制子表数
反模式 2: 全字段 Tag
CREATE STABLE t (...) TAGS (a, b, c, d, e, f, g, h);
-- 过多 Tag 增加元数据
改进:
保留 3~5 个常用过滤 Tag,其他作普通列
反模式 3: 长字符串 Tag
TAGS (description VARCHAR(1024))
-- Tag 值过大,索引膨胀
改进:
Tag 仅存类别/ID,详情存普通列
反模式 4: 复杂查询不命中索引
SELECT * FROM meters WHERE UPPER(location) = 'BEIJING';
-- 函数包裹 → Tag 索引失效
改进:
SELECT * FROM meters WHERE location = 'beijing';
-- 或建表时统一大小写
6. 索引维护操作
sql
-- 查看索引
SHOW INDEXES FROM meters;
SHOW TSMAS;
-- 创建索引
CREATE INDEX idx_name ON meters (column);
CREATE TSMA tsma_name ON meters FUNCTION(...) INTERVAL(...);
-- 删除索引
DROP INDEX idx_name;
DROP TSMA tsma_name;
-- 修改不支持,需 DROP 再 CREATE
7. 索引性能验证
验证流程:
① 准备测试数据
② EXPLAIN 看计划
③ EXPLAIN ANALYZE 看实际
④ 验证关键指标:
- blocks_skipped 高 → SMA 生效
- 子表 scan 数量小 → Tag 索引生效
- rows_examined 小 → 索引覆盖好
⑤ 基准测试对比延迟
8. 索引升级策略
随业务变化调整:
① 监控慢查询日志
② 识别高频查询模式
③ 评估是否缺索引
④ 在业务低峰创建索引(建索引本身耗时)
⑤ 验证查询加速效果
⑥ 持续观察
何时考虑重构:
- 子表数失控(亿级以上)
- Tag 设计不合理
- 查询模式发生根本变化
重构方案:
- 新建表迁移数据
- 或重新 ALTER TABLE 修改 Tag(视支持情况)
代码示例
综合应用:智慧城市
sql
-- 数据库:分层存储
CREATE DATABASE city_db
PRECISION 'ms'
KEEP 1825
RETENTIONS '1s:30d,1m:1y,1h:5y';
USE city_db;
-- 超级表
CREATE STABLE devices (
ts TIMESTAMP, value FLOAT, status TINYINT
) TAGS (
district VARCHAR(32), -- 区
device_type VARCHAR(32), -- 类型
vendor VARCHAR(32) -- 厂商
) ROLLUP(AVG, MAX, MIN);
-- → 自动 RSMA
-- 显式索引
CREATE INDEX idx_dtype ON devices (device_type);
CREATE INDEX idx_vendor ON devices (vendor);
-- TSMA 加速 5 分钟报表
CREATE TSMA devices_5m ON devices
FUNCTION(AVG(value), COUNT(*))
INTERVAL(5m);
-- 流计算异常检测
CREATE STREAM s_alarm INTO alarm_log AS
SELECT _wstart, tbname, MAX(value)
FROM devices WHERE status > 1
PARTITION BY tbname INTERVAL(10s);
性能考量
不同索引的成本与收益
| 索引 | 创建成本 | 维护成本 | 查询收益 |
|---|---|---|---|
| 默认 Tag 索引 | 0 | 极低 | 高 |
| 额外 Tag 索引 | 中 | 低 | 高 |
| 块 SMA | 0 | 0 | 高 |
| TSMA | 中(回填) | 低 | 极高 |
| RSMA | 中 | 低 | 极高 |
| 流计算(替代) | 高 | 中 | 极高 |
索引设计原则
| 原则 | 说明 |
|---|---|
| 第一 Tag 最重要 | 默认有索引,放最常用过滤列 |
| 额外索引按需 | 不要预先加所有索引 |
| TSMA 针对慢查询 | 仅为已知慢查询模式建 |
| 避免高基数 Tag | 优先考虑普通列 |
FAQ
Q1: 索引创建慢怎么办?
大表新建索引耗时长。建议低峰期执行;可考虑表/数据库重建。
Q2: 索引和数据存储分离吗?
Tag 索引在 MNode/VNode 元数据中。SMA 在 .data 文件 Header。TSMA 单独文件。
Q3: 每个查询都需要索引吗?
不需要。简单时间范围查询有自动 SMA 即可。仅在 EXPLAIN 表明扫描过多时才加索引。
Q4: 索引数量上限?
无明确硬上限,但过多索引:
- 增加写入开销
- 元数据膨胀
- 查询优化器选择慢
建议每表 < 5 个手动索引。
Q5: 怎么知道一个查询用了哪个索引?
EXPLAIN VERBOSE 显示选择的索引和扫描方式。
参考
系统构架篇
- 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 最佳实践》
