数据建模是时序数据库应用开发的基础环节,合理的建模设计直接影响系统的性能和可维护性。本文将以智能电表为例,详细介绍TDengine时序数据库的数据建模实践。
一、建模设计原则
1.1 核心设计理念
TDengine采用”一个数据采集点一张表”的设计理念,这一理念带来以下优势:
- 写入性能最优:单表单写入者,无锁写入
- 查询效率最高:数据连续存储,减少随机IO
- 压缩率最高:列式存储,变化缓慢的数据压缩率高
1.2 建模步骤
- 分析业务场景,识别数据采集点
- 确定采集量和标签
- 设计超级表结构
- 创建数据库
- 创建超级表和子表
二、创建数据库
2.1 数据库创建语法
CREATE DATABASE power PRECISION 'ms' KEEP 3650 DURATION 10 BUFFER 16;
2.2 参数详解
| 参数 | 说明 | 示例值 |
|---|---|---|
| PRECISION | 时间戳精度 | ‘ms’(毫秒) |
| KEEP | 数据保留天数 | 3650(约10年) |
| DURATION | 数据文件时间跨度 | 10(天) |
| BUFFER | 写入内存池大小 | 16(MB) |
2.3 参数选择建议
时间精度选择:
- 毫秒(ms):适用于大多数物联网场景
- 微秒(us):适用于金融、高频交易场景
- 纳秒(ns):适用于科学实验、精密测量
数据保留策略:
- 根据业务需求和存储容量确定
- 历史数据可定期归档
- 冷热数据分层存储
2.4 切换数据库
USE power;
执行后,后续的插入、查询等操作都在当前的power数据库中进行。
三、创建超级表
3.1 超级表创建语法
CREATE STABLE meters (
ts timestamp,
current float,
voltage int,
phase float
) TAGS (
location varchar(64),
group_id int
);
3.2 列定义规则
时间戳列:
- 第1列必须为时间戳列
- 格式:
列名 timestamp
采集量列:
- 从第2列开始定义
- 数据类型可以是整型、浮点型、字符串等
- 示例:
current float、voltage int
标签列:
- 通过TAGS关键字定义
- 数据类型可以是任意类型
- 标签名不能与采集量列名相同
3.3 数据类型选择
| 数据类型 | 适用场景 | 示例 |
|---|---|---|
| TINYINT | 小范围整数 | 状态码 |
| SMALLINT | 中等范围整数 | 温度(整数) |
| INT | 常规整数 | 电压值 |
| BIGINT | 大整数 | 设备ID |
| FLOAT | 单精度浮点 | 电流值 |
| DOUBLE | 双精度浮点 | 精确测量值 |
| VARCHAR | 字符串 | 位置信息 |
| TIMESTAMP | 时间戳 | 采集时间 |
四、创建子表
4.1 手动创建子表
CREATE TABLE d1001
USING meters (
location,
group_id
) TAGS (
"California.SanFrancisco",
2
);
参数说明:
d1001:子表名称USING meters:使用超级表meters作为模板TAGS后指定标签值
4.2 自动建表
在写入数据时自动创建子表:
INSERT INTO d1002
USING meters
TAGS (
"California.SanFrancisco",
2
) VALUES (
NOW,
10.2,
219,
0.32
);
当子表d1002不存在时,系统会先自动创建子表,再写入数据。
4.3 部分标签指定
可以只指定部分标签值,未指定的标签值为NULL:
INSERT INTO d1005
USING meters (location)
TAGS ("beijing.chaoyang")
VALUES ("2018-10-04 14:38:07", 10.15, 217, 0.33);
五、创建普通表
5.1 普通表概念
普通表是不带任何标签的表,与普通关系型数据库中的表相似。
5.2 普通表与子表的区别
| 特性 | 普通表 | 子表 |
|---|---|---|
| 标签扩展性 | 无标签 | 具有可变标签 |
| 表归属 | 独立存在 | 隶属于超级表 |
| 转换限制 | 不能相互转换 | 不能相互转换 |
5.3 使用场景
普通表适用于:
- 不需要标签分类的数据
- 单一数据源的场景
- 与传统关系型数据库兼容的需求
六、建模最佳实践
6.1 数据库规划
按数据特征分库:
- 不同采集频率的数据分库
- 不同保留策略的数据分库
- 不同业务域的数据分库
示例:
-- 高频采集数据库
CREATE DATABASE high_freq PRECISION 'ms' KEEP 365 DURATION 1;
-- 低频采集数据库
CREATE DATABASE low_freq PRECISION 'ms' KEEP 3650 DURATION 30;
6.2 超级表设计
同类型设备归为一张超级表:
- 相同采集量结构
- 相同的标签维度
- 便于统一查询和管理
标签设计原则:
- 选择稳定不变的属性作为标签
- 常用于筛选条件的属性优先
- 标签数量不宜过多(建议不超过10个)
6.3 子表命名规范
推荐命名方式:
- 使用设备ID作为子表名
- 保持命名规则的一致性
- 避免使用特殊字符
示例:
d1001, d1002, d1003... -- 设备ID
meter_sf_001 -- 地区+序号
sensor_temp_001 -- 传感器类型+序号
6.4 采集量设计
列数控制:
- 单表列数不宜过多(建议不超过50列)
- 相关采集量放在一起
- 不常用的采集量可单独建表
数据类型选择:
- 选择合适的数据类型,避免浪费存储
- 浮点数注意精度要求
- 字符串类型指定合理长度
七、完整建模示例
7.1 场景描述
某智能电表系统,需要存储以下数据:
- 设备数量:10000台
- 采集频率:每10秒一次
- 采集量:电流、电压、相位
- 标签:位置、分组ID
7.2 建模实现
-- 1. 创建数据库
CREATE DATABASE power
PRECISION 'ms'
KEEP 365
DURATION 10
BUFFER 96;
-- 2. 切换数据库
USE power;
-- 3. 创建超级表
CREATE STABLE meters (
ts timestamp,
current float,
voltage int,
phase float
) TAGS (
location varchar(64),
group_id int
);
-- 4. 写入数据(自动创建子表)
INSERT INTO d1001 USING meters TAGS ("California.SanFrancisco", 2)
VALUES (NOW, 10.3, 219, 0.31);
7.3 查询验证
-- 查询所有设备
SELECT COUNT(*) FROM meters;
-- 按地区分组统计
SELECT location, COUNT(*), AVG(voltage)
FROM meters
GROUP BY location;
-- 时间窗口聚合
SELECT tbname, _wstart, avg(voltage)
FROM meters
WHERE ts >= NOW - 1h
PARTITION BY tbname
INTERVAL(1m);
八、建模常见问题
8.1 表数量过多
问题:设备数量巨大,担心表数量过多
解决方案:TDengine专门优化了海量表管理,千万级表数量不影响性能
8.2 标签修改需求
问题:设备标签可能需要修改
解决方案:TDengine支持标签的修改、添加、删除操作
8.3 历史数据迁移
问题:如何迁移历史数据
解决方案:
- 使用批量INSERT语句导入
- 通过第三方工具(如DataX)迁移
- 利用TDengine的导入功能
九、性能优化建议
9.1 写入优化
- 使用批量写入代替单条写入
- 利用自动建表机制
- 合理设置BUFFER参数
9.2 查询优化
- 查询时指定时间范围
- 使用PARTITION BY并行查询
- 合理使用SLIMIT控制结果
9.3 存储优化
- 选择合适的KEEP参数
- 定期清理过期数据
- 监控压缩率
十、监控与维护
10.1 查看压缩率
SELECT * FROM INFORMATION_SCHEMA.INS_DISK_USAGE
WHERE db_name = 'power';
10.2 查看表分布
SHOW TABLE DISTRIBUTED meters;
10.3 数据库状态
SHOW DATABASES;
SHOW STABLES;
SHOW TABLES;
总结
数据建模是时序数据库应用开发的基础,合理的建模设计能够充分发挥TDengine的性能优势。通过”一个数据采集点一张表”的设计理念,配合超级表模板机制,开发者可以高效管理海量设备数据。本文介绍的建模实践,包括数据库创建、超级表设计、子表管理等,为构建工业数据管理平台(IDMP)和实时数据库应用提供了完整的指导。TDengine凭借其专业的时序数据建模能力,成为物联网和工业场景的理想选择。

























