TDengine 超级表/子表/普通表 — 设计理念与内部表示

2.数据模型 > 02 超级表/子表/普通表

适用版本:TDengine v3.x(v3.3.x / v3.4.x) | 最后更新:2026-05-16

概述

TDengine 的数据模型围绕一个核心设计理念------一个数据采集点一张表(One Table Per Data Collection Point)。这与传统关系型数据库"所有设备共享一张宽表"的思路截然不同。

为了在"一设备一表"的基础上实现高效的聚合查询和统一管理,TDengine 创造了**超级表(STable)**机制------同类设备共享 Schema,通过 Tag 区分个体。

本文深入解析三种表类型的设计理念、内部存储结构和适用场景。

核心概念速查表

概念 说明
超级表(Super Table) 定义一类设备的数据模板(Schema + Tag 定义),不直接存储数据
子表(Child Table) 从超级表派生,代表一个具体的数据采集点,存储实际时序数据
普通表(Normal Table) 不从属于超级表的独立表,无 Tag 概念
列(Column) 时序数据的度量值(measurement),随时间变化
标签(Tag) 设备的静态属性(如位置、型号),一张子表的 Tag 值固定不变
时间戳列 每张表的第一列必须是时间戳(主键),数据按时间有序存储
Schema 表的结构定义(列名、列类型、列宽度)

详细解析

1. 设计理念:一设备一表

1.1 为什么不用"大宽表"?

传统方案------将所有设备数据写入同一张表(通过设备 ID 列区分):

复制代码
传统方案(关系型思维):

  CREATE TABLE sensor_data (
    ts TIMESTAMP,
    device_id VARCHAR(64),
    temperature FLOAT,
    humidity FLOAT,
    ...
  );
  
  问题:
  ① 单表亿级行 → 索引巨大,写入/查询都慢
  ② 不同设备的写入相互竞争锁
  ③ 单设备查询需要全表扫描 + device_id 过滤
  ④ 时间戳不唯一(多设备同一时刻都有数据)
1.2 TDengine 的方案
复制代码
TDengine 方案(一设备一表):

  每个设备一张独立的子表:
    d001: ts | temperature | humidity
    d002: ts | temperature | humidity
    d003: ts | temperature | humidity
    ...
  
  优势:
  ① 每张表内时间戳天然有序 → 追加写入即可,无需排序
  ② 单设备查询 = 顺序扫描单表 → O(1) 定位 + 顺序读取
  ③ 各表写入互不干扰 → 高并发写入
  ④ 压缩率高 → 同设备数据相似度高,列式压缩效果好
  ⑤ 时间戳在每张表内唯一 → 天然主键
1.3 超级表解决"多设备聚合"问题

一设备一表的挑战:如何做跨设备的聚合查询?

sql 复制代码
-- 问题:有 100 万张子表,如何查"某区域所有设备的平均温度"?
-- 如果没有超级表,需要 UNION ALL 100 万张表------不现实

-- 超级表的解决方案:
SELECT AVG(temperature) FROM meters WHERE location = 'Beijing';
-- 系统自动:
--   1. 通过 Tag 索引找到 location='Beijing' 的所有子表
--   2. 并行扫描这些子表
--   3. 聚合结果返回

2. 超级表(Super Table)

2.1 超级表的定义

超级表是同类数据采集点的模板,定义了两部分结构:

sql 复制代码
CREATE STABLE meters (
  -- 列(Column):时序数据,随时间变化
  ts TIMESTAMP,
  current FLOAT,
  voltage INT,
  phase FLOAT
) TAGS (
  -- 标签(Tag):设备属性,一张子表内固定不变
  location BINARY(64),
  group_id INT
);
2.2 超级表不存储数据

超级表本身不存储任何时序数据行。它的作用是:

功能 说明
Schema 模板 子表继承超级表的列定义
Tag 定义 定义子表可以携带的静态属性
跨子表查询入口 SELECT ... FROM 超级表名 可聚合查询所有子表
Tag 索引 维护所有子表的 Tag 值索引,加速 WHERE tag 过滤
2.3 超级表的内部表示
复制代码
超级表在系统中的存储:

  MNode(元数据节点)中存储:
    ├── 超级表基本信息:名称、UID、创建时间、所属数据库
    ├── 列 Schema:列名、类型、宽度、编号
    ├── Tag Schema:Tag 名、类型、宽度、编号
    └── 版本号:Schema 变更时递增

  VNode 中存储:
    ├── 超级表的列 Schema 副本(用于本地数据校验)
    └── Tag 索引(本 VNode 管理的所有子表的 Tag 值)
2.4 超级表数量限制
  • 如果数据库设置了 SINGLE_STABLE = 1,则只允许 1 张超级表
  • 默认不限制超级表数量,但建议同类设备共用一张超级表
  • 超级表数量过多会增加 MNode 的元数据管理压力

3. 子表(Child Table)

3.1 创建子表
sql 复制代码
-- 方式 1:显式创建
CREATE TABLE d1001 USING meters TAGS ('California.SanFrancisco', 2);

-- 方式 2:写入时自动创建(推荐)
INSERT INTO d1001 USING meters TAGS ('California.SanFrancisco', 2)
  VALUES (now, 10.3, 219, 0.31);

-- 方式 3:批量创建
CREATE TABLE 
  d1001 USING meters TAGS ('California.SanFrancisco', 2)
  d1002 USING meters TAGS ('California.LosAngeles', 3)
  d1003 USING meters TAGS ('Beijing.Haidian', 1);
3.2 子表与超级表的关系
复制代码
超级表与子表的关系:

  超级表 meters (Schema 模板)
    │
    ├── 子表 d1001 (tags: location='California', group_id=2)
    │     └── 时序数据: ts, current, voltage, phase
    │
    ├── 子表 d1002 (tags: location='LosAngeles', group_id=3)
    │     └── 时序数据: ts, current, voltage, phase
    │
    └── 子表 d1003 (tags: location='Beijing', group_id=1)
          └── 时序数据: ts, current, voltage, phase

  关键规则:
  ① 子表继承超级表的列 Schema(不能单独增删列)
  ② 子表有自己独立的 Tag 值(可以修改)
  ③ 子表存储实际的时序数据
  ④ 超级表 Schema 变更自动影响所有子表
3.3 子表的内部存储结构
复制代码
子表在 VNode 内的存储:

  ┌────────────────────────────────┐
  │         VNode (VGroup N)        │
  │                                 │
  │  元数据引擎(TDB):              │
  │    ├── 子表 UID → 子表信息       │
  │    │     - 所属超级表 UID       │
  │    │     - Tag 值              │
  │    │     - 创建时间            │
  │    └── 超级表 UID → [子表列表]  │
  │                                 │
  │  时序存储引擎(TSDB):           │
  │    ├── MemTable(内存):         │
  │    │     子表 UID → 跳表数据    │
  │    └── 数据文件(磁盘):         │
  │          按时间分组 →            │
  │          [文件头 | 数据块...]    │
  │          数据块内按子表 UID 组织 │
  └────────────────────────────────┘
3.4 子表的 VGroup 归属

每张子表属于且只属于一个 VGroup,由表名的 hash 值决定:

复制代码
子表路由算法:

  hash_value = MurmurHash3(表名)
  vgroup_index = hash_value 落入哪个 VGroup 的 [hashBegin, hashEnd] 范围
  
  注意:
  - hash 计算使用内部全名(含数据库前缀)
  - TABLE_PREFIX / TABLE_SUFFIX 参数影响参与 hash 的字符
  - 一旦建表,VGroup 归属不变(除非显式迁移 VGroup)
3.5 子表数量
  • 理论上限:无硬性上限,受内存和磁盘约束
  • 实践参考:单集群管理上亿张子表是常见场景
  • 每个 VNode 管理的子表数 ≈ 总子表数 / VGROUPS

4. 普通表(Normal Table)

4.1 定义与创建
sql 复制代码
-- 普通表:不从属于任何超级表,无 Tag
CREATE TABLE cpu_summary (
  ts TIMESTAMP,
  cpu_avg FLOAT,
  mem_used BIGINT,
  disk_io FLOAT
);
4.2 普通表 vs 子表
对比维度 子表 普通表
从属关系 必须属于超级表 独立存在
Tag 有,继承自超级表定义
Schema 管理 跟随超级表变更 独立管理
跨表聚合 通过超级表查询 需要显式 JOIN 或 UNION
适用场景 大量同构设备 少量特殊用途表
4.3 普通表的适用场景
  • 全局汇总表(存储系统级统计信息)
  • 配置表(存储应用参数)
  • 临时分析结果表
  • 不同于主业务设备的异构数据

最佳实践 :如果有多个同类设备,始终使用超级表 + 子表,而非大量独立的普通表。超级表提供的统一 Schema 管理和跨表查询能力是普通表无法替代的。

5. 表的内部标识

5.1 UID 体系
复制代码
表标识体系:

  超级表:
    - suid(Super Table UID):全局唯一 64 位整数
    - 存储在 MNode(SDB)和所有相关 VNode
  
  子表:
    - uid(Table UID):全局唯一 64 位整数
    - suid:指向所属超级表
    - 存储在所属 VNode
  
  普通表:
    - uid(Table UID):全局唯一 64 位整数
    - suid = 0(标识为普通表)
    - 存储在所属 VNode
    
  UID 生成:基于时间戳 + 随机数,保证全局唯一
5.2 名称与 UID 的映射
复制代码
名称解析路径:

  用户输入表名 "d1001"
       │
       ▼
  客户端 Catalog 缓存查找:表名 → (uid, suid, vgId, schema)
       │
       ├── 命中 → 直接使用
       └── 未命中 → 向 MNode 请求 → 缓存 → 使用
       
  VNode 内部:通过 uid 进行所有数据操作(不再使用表名字符串)

6. 数据组织方式对比

复制代码
三种表的数据组织:

  ┌─────────────────────────────────────────────────┐
  │              Database "power"                     │
  │                                                   │
  │  超级表 meters:                                   │
  │    Schema: [ts, current, voltage, phase]          │
  │    Tags:   [location, group_id]                   │
  │    │                                              │
  │    ├── VGroup 2                                   │
  │    │     ├── 子表 d1001 (tags: 'CA', 2)          │
  │    │     │     └── [时序数据...]                  │
  │    │     ├── 子表 d1002 (tags: 'CA', 3)          │
  │    │     │     └── [时序数据...]                  │
  │    │     └── 子表 d1005 (tags: 'NY', 1)          │
  │    │           └── [时序数据...]                  │
  │    │                                              │
  │    └── VGroup 3                                   │
  │          ├── 子表 d1003 (tags: 'BJ', 1)          │
  │          │     └── [时序数据...]                  │
  │          └── 子表 d1004 (tags: 'SH', 2)          │
  │                └── [时序数据...]                  │
  │                                                   │
  │  普通表 summary:                                  │
  │    Schema: [ts, total_power, peak_current]        │
  │    (存储在某个 VGroup 中)                          │
  └─────────────────────────────────────────────────┘

7. 查询行为差异

查询方式 行为
SELECT * FROM d1001 直接定位子表所在 VGroup,扫描单表
SELECT * FROM meters 扫描所有 VGroup 的所有子表(全量)
SELECT * FROM meters WHERE location='CA' 通过 Tag 索引过滤,只扫描满足条件的子表
SELECT AVG(current) FROM meters GROUP BY location Tag 过滤 + 分组聚合
SELECT * FROM meters PARTITION BY tbname 按子表分组输出
复制代码
超级表查询执行流程:

  SELECT AVG(current) FROM meters WHERE location = 'CA'
       │
       ▼
  ① Tag 过滤:从各 VNode 的 Tag 索引中筛选 location='CA' 的子表
       │
       ▼
  ② 生成子表列表:[d1001, d1002, d1005]
       │
       ▼
  ③ 按 VGroup 分组下发:
     VGroup 2: 扫描 d1001, d1002, d1005 → 本地部分聚合
     VGroup 3: (该 VGroup 无符合条件的表,跳过)
       │
       ▼
  ④ 汇总各 VGroup 的部分结果 → 最终 AVG

代码示例

完整的建模示例

sql 复制代码
-- 1. 创建数据库
CREATE DATABASE power VGROUPS 4 REPLICA 3 PRECISION 'ms' KEEP 365d;
USE power;

-- 2. 创建超级表(电表模板)
CREATE STABLE meters (
  ts TIMESTAMP,
  current FLOAT,
  voltage INT,
  phase FLOAT
) TAGS (
  location BINARY(64),
  group_id INT
);

-- 3. 创建子表(方式一:显式创建)
CREATE TABLE d1001 USING meters TAGS ('California.SanFrancisco', 2);

-- 4. 写入时自动创建子表(方式二:推荐)
INSERT INTO d1002 USING meters TAGS ('California.LosAngeles', 3)
  VALUES ('2024-01-15 10:00:00', 10.3, 219, 0.31);

-- 5. 查询单个子表
SELECT * FROM d1001 WHERE ts >= NOW() - 1h;

-- 6. 通过超级表聚合查询(Tag 过滤)
SELECT AVG(current), MAX(voltage) 
FROM meters 
WHERE location LIKE 'California%'
INTERVAL(10m);

-- 7. 按 Tag 分组统计
SELECT location, COUNT(*), AVG(current)
FROM meters
GROUP BY location;

-- 8. 修改子表 Tag 值
ALTER TABLE d1001 SET TAG location = 'California.Oakland';

查看表结构信息

sql 复制代码
-- 查看超级表列表
SHOW power.STABLES;

-- 查看超级表结构
DESCRIBE meters;

-- 查看子表数量
SELECT COUNT(*) FROM information_schema.ins_tables 
WHERE db_name = 'power' AND stable_name = 'meters';

-- 查看子表的 Tag 值
SELECT tbname, location, group_id FROM meters;

性能考量

子表数量对性能的影响

子表数量级 写入影响 查询影响 建议
< 1万 无影响 无影响 VGROUPS 2~4
1万~100万 无影响 全超级表扫描较慢 VGROUPS 4~16,善用 Tag 过滤
100万~1亿 元数据内存增大 必须用 Tag 过滤 VGROUPS 16~64,注意内存规划

Tag 过滤的性能

  • Tag 索引存储在 VNode 内存中,Tag 过滤不走磁盘 I/O
  • 过滤后只扫描匹配的子表数据,大幅减少不必要的磁盘读取
  • 建议将高区分度的属性设为 Tag(如设备 ID、区域编码)

写入最佳实践

实践 原因
使用自动建表语法 一次 RPC 完成建表 + 写入,减少网络往返
批量写入多表数据 客户端自动按 VGroup 分组并行发送
避免频繁修改 Tag Tag 修改会更新索引,高频修改影响性能

FAQ

Q1: 超级表和子表在磁盘上是什么关系?

超级表不占用数据磁盘空间(只有 MNode 中的元数据记录)。所有实际数据存储在子表中。同一 VGroup 内的多张子表数据会被合并存储到同一组数据文件中(按时间范围组织),不是每张子表一个文件。

Q2: 一个子表可以属于多个超级表吗?

不可以。子表与超级表是严格的一对多关系:一张超级表可以有无数张子表,但一张子表只能属于一张超级表。

Q3: 删除超级表会发生什么?

删除超级表会级联删除该超级表下的所有子表及其数据。这是一个不可逆操作,需要谨慎。

Q4: 普通表可以转成子表吗?

不可以。普通表和子表在创建时就确定了类型,不能互相转换。如果需要将普通表纳入超级表管理,需要重新建表并迁移数据。

Q5: 为什么不直接查子表而要通过超级表查?

通过超级表查询的优势:

  1. 无需知道具体子表名称(通过 Tag 条件筛选)
  2. 自动并行扫描多个 VGroup
  3. 内置分组聚合支持(GROUP BY tag_column
  4. 当子表数量增长时,查询 SQL 不需要修改

如果只需要查询单个已知设备,直接查子表反而更快(跳过 Tag 过滤步骤)。

Q6: 子表的 Tag 值修改后,历史数据的查询结果会变吗?

会变。Tag 是子表的当前属性,不是时间序列数据。修改 Tag 后,所有查询(包括历史数据)都使用新的 Tag 值进行过滤。如果需要记录 Tag 值的变化历史,应将其作为列(Column)而非标签。

参考

系统构架篇

数据模型

关于 TDengine

TDengine 专为物联网IoT平台、工业大数据平台设计。其中,TDengine TSDB 是一款高性能、分布式的时序数据库(Time Series Database),同时它还带有内建的缓存、流式计算、数据订阅等系统功能;TDengine IDMP 是一款AI原生工业数据管理平台,它通过树状层次结构建立数据目录,对数据进行标准化、情景化,并通过 AI 提供实时分析、可视化、事件管理与报警等功能。

相关推荐
oo哦哦5 小时前
深度解析:星链引擎全域智能营销矩阵系统的技术架构与实践
大数据·矩阵·架构
shuaiqinke6 小时前
【分享】Edge浏览器|内置扩展仓库|支持油猴|上网无限制
android·前端·人工智能·edge
老纪6 小时前
c++怎么利用std--variant处理多种二进制子协议包的自动分支解析【进阶】
jvm·数据库·python
pigs20186 小时前
Docker容器中Kingbase数据库授权到期更换解决方案
数据库·docker·容器
jiayong236 小时前
Git 常见错误与详细解决方案
大数据·git·elasticsearch
guygg886 小时前
C# 监听数据库数据变化(SqlDependency 实现)
数据库·oracle·c#
隐退山林6 小时前
JavaEE进阶:MyBatis 操作数据库(入门)
数据库·java-ee·mybatis
Carson带你学Android6 小时前
见证历史!Swift 6.3 官方支持 Android,跨平台要变天了?
android
视***间6 小时前
视程空间AIR系列——小体积藏强芯,赋能机器人/机器狗全域落地
大数据·人工智能·机器人·机器狗·ai算力·视程空间