目录
[多列模型 VS 单列模型](#多列模型 VS 单列模型)
[1. 数据模型概述](#1. 数据模型概述)
[2. 表结构设计](#2. 表结构设计)
[2.1 静态数据表](#2.1 静态数据表)
[2.2 动态数据表](#2.2 动态数据表)
[4. 查询数据](#4. 查询数据)
[4.1 查询单个车辆的数据](#4.1 查询单个车辆的数据)
[4.2 查询多个车辆的数据](#4.2 查询多个车辆的数据)
[4.3 聚合查询](#4.3 聚合查询)
[5. 性能优化](#5. 性能优化)
[6. 安全性和监控](#6. 安全性和监控)
建库和建表
创建数据库
创建一个数据库以存储电表数据的 SQL 如下:
CREATE DATABASE power PRECISION 'ms' KEEP 3650 DURATION 10 BUFFER 16;
该 SQL 将创建一个名为 power
的数据库,各参数说明如下:
PRECISION 'ms'
:这个数据库的时序数据使用毫秒(ms)精度的时间戳KEEP 3650
:这个库的数据将保留 3650 天,超过 3650 天的数据将被自动删除DURATION 10
:每 10 天的数据放在一个数据文件中BUFFER 16
:写入使用大小为 16MB 的内存池。
注意:KEEP 3650这个日期影响着后面插入的时间,如果后面插入的时间戳早于这个时间将会报错
Timestamp data out of range
在创建power数据库后,可以执行 USE 语句来使用切换数据库。
use power;
该 SQL 将当前数据库切换为 power
,表示之后的插入、查询等操作,都在当前的 power
数据库中进行。
创建超级表
创建一张名为 meters
的超级表的 SQL 如下:
CREATE STABLE meters (
ts timestamp,
current float,
voltage int,
phase float
) TAGS (
location varchar(64),
group_id int
);
在 TDengine 中,创建超级表的 SQL 语句与关系型数据库类似。例如,上面的 SQL 中,CREATE STABLE
为关键字,表示创建超级表;接着,meters
是超级表的名称;在表名后面的括号中,定义超级表的列(列名、数据类型等),规则如下:
- 第 1 列必须为时间戳列。例如:
ts timestamp
表示,时间戳列名是t
s,数据类型为timestamp
; - 从第 2 列开始是采集量列。采集量的数据类型可以为整型、浮点型、字符串等。例如:
current float
表示,采集量电流current
,数据类型为float
;
最后,TAGS是关键字,表示标签,在 TAGS 后面的括号中,定义超级表的标签(标签名、数据类型等)。
- 标签的数据类型可以为整型、浮点型、字符串等。例如:
location varchar(64)
表示,标签地区location
,数据类型为varchar(64)
; - 标签的名称不能与采集量列的名称相同。
创建表
通过超级表创建子表 d1001
的 SQL 如下:
CREATE TABLE d1001
USING meters (
location,
group_id
) TAGS (
"California.SanFrancisco",
2
);
上面的 SQL 中,CREATE TABLE
为关键字,表示创建表;d1001
是子表的名称;USING
是关键字,表示要使用超级表作为模版;meters
是超级表的名称;在超级表名后的括号中,location
, group_id
表示,是超级表的标签列名列表;TAGS
是关键字,在后面的括号中指定子表的标签列的值。"California.SanFrancisco"
和 2
表示子表 d1001
的位置为 California.SanFrancisco
,分组 ID 为 2
。
当对超级表进行写入或查询操作时,用户可以使用伪列 tbname 来指定或输出对应操作的子表名。
自动建表
在 TDengine 中,为了简化用户操作并确保数据的顺利写入,即使子表尚不存在,用户也可以使用带有 using 关键字的自动建表 SQL 进行数据写入。这种机制允许系统在遇到不存在的子表时,自动创建该子表,然后再执行数据写入操作。如果子表已经存在,系统则会直接写入数据,不需要任何额外的步骤。
在写入数据的同时自动建表的 SQL 如下:
INSERT INTO d1002
USING meters
TAGS (
"California.SanFrancisco",
2
) VALUES (
NOW,
10.2,
219,
0.32
);
上面的 SQL 中,INSERT INTO d1002
表示,向子表 d1002
中写入数据;USING meters
表示,使用超级表 meters
作为模版;TAGS ("California.SanFrancisco", 2)
表示,子表 d1002
的标签值分别为 California.SanFrancisco
和 2
;VALUES (NOW, 10.2, 219, 0.32)
表示,向子表 d1002
插入一行记录,值分别为NOW(当前时间戳)、10.2(电流)、219(电压)、0.32(相位)。在 TDengine 执行这条 SQL 时,如果子表 d1002
已经存在,则直接写入数据;当子表 d1002
不存在,会先自动创建子表,再写入数据。
创建普通表
在 TDengine 中,除了具有标签的子表以外,还存在一种不带任何标签的普通表。这类表与普通关系型数据库中的表相似,用户可以使用 SQL 创建它们。
普通表与子表的区别在于:
- 标签扩展性:子表在普通表的基础上增加了静态标签,这使得子表能够携带更多的元数据信息。此外,子表的标签是可变的,用户可以根据需要增加、删除或修改标签。
- 表归属:子表总是隶属于某张超级表,它们是超级表的一部分。而普通表则独立存在,不属于任何超级表。
- 转换限制:在 TDengine 中,普通表无法直接转换为子表,同样,子表也无法转换为普通表。这两种表类型在创建时就确定了它们的结构和属性,后期无法更改。
总结来说,普通表提供了类似于传统关系型数据库的表功能,而子表则通过引入标签机制,为时序数据提供了更丰富的描述能力和更灵活的管理方式。用户可以根据实际需求选择创建普通表还是子表。
创建不带任何标签的普通表的 SQL 如下:
CREATE TABLE d1003(
ts timestamp,
current float,
voltage int,
phase float,
location varchar(64),
group_id int
);
上面的 SQL 表示,创建普通表 d1003
,表结构包括 ts
、current
、voltage
、phase
、location
、group_id
,共 6 个列。这样的数据模型,与关系型数据库完全一致。
采用普通表作为数据模型意味着静态标签数据(如 location 和 group_id)会重复存储在表的每一行中。这种做法不仅增加了存储空间的消耗,而且在进行查询时,由于无法直接利用标签数据进行过滤,查询性能会显著低于使用超级表的数据模型。
多列模型 VS 单列模型
TDengine 支持灵活的数据模型设计,包括多列模型和单列模型。多列模型允许将多个由同一数据采集点同时采集且时间戳一致的物理量作为不同列存储在同一张超级表中。然而,在某些极端情况下,可能会采用单列模型,即每个采集的物理量都单独建立一张表。例如,对于电流、电压和相位这 3 种物理量,可能会分别建立 3 张超级表。
尽管 TDengine 推荐使用多列模型,因为这种模型在写入效率和存储效率方面通常更优,但在某些特定场景下,单列模型可能更为适用。例如,当一个数据采集点的采集量种类经常发生变化时,如果采用多列模型,就需要频繁修改超级表的结构定义,这会增加应用程序的复杂性。在这种情况下,采用单列模型可以简化应用程序的设计和管理,因为它允许独立地管理和扩展每个物理量的超级表。
总之,TDengine 提供了灵活的数据模型选项,用户可以根据实际需求和场景选择最适合的模型,以优化性能和管理复杂性。
数据类型映射
TDengine 目前支持时间戳、数字、字符、布尔类型,与 Java 对应类型转换如下:
TDengine DataType | JDBCType |
---|---|
TIMESTAMP | java.sql.Timestamp |
INT | java.lang.Integer |
BIGINT | java.lang.Long |
FLOAT | java.lang.Float |
DOUBLE | java.lang.Double |
SMALLINT | java.lang.Short |
TINYINT | java.lang.Byte |
BOOL | java.lang.Boolean |
BINARY | byte array |
NCHAR | java.lang.String |
JSON | java.lang.String |
VARBINARY | byte[] |
GEOMETRY | byte[] |
注意 :JSON 类型仅在 tag 中支持。
由于历史原因,TDengine中的BINARY底层不是真正的二进制数据,已不建议使用。请用VARBINARY类型代替。
GEOMETRY类型是little endian字节序的二进制数据,符合WKB规范。详细信息请参考 数据类型
WKB规范请参考Well-Known Binary (WKB)
对于java连接器,可以使用jts库来方便的创建GEOMETRY类型对象,序列化后写入TDengine,这里有一个样例Geometry示例
示例程序汇总
示例程序源码位于 TDengine/docs/examples/JDBC
下:
- JDBCDemo:JDBC 示例源程序。
- connectionPools:HikariCP, Druid, dbcp, c3p0 等连接池中使用 taos-jdbcdriver。
- SpringJdbcTemplate:Spring JdbcTemplate 中使用 taos-jdbcdriver。
- mybatisplus-demo:Springboot + Mybatis 中使用 taos-jdbcdriver。
- springbootdemo: Springboot 中使用 taos-jdbcdriver。
- consumer-demo:Consumer 消费 TDengine 数据示例,可通过参数控制消费速度。
请参考:JDBC example
在车联网领域的应用
设计车联网数据在TDengine时序数据库中的表结构时,需要考虑以下几个关键因素:数据的类型、频率、存储效率以及查询性能。TDengine 是一个专为物联网(IoT)和时序数据设计的高性能时序数据库,特别适合处理大规模的时序数据。
1. 数据模型概述
车联网数据通常包括车辆的基本信息、传感器数据、位置信息等。我们可以将这些数据分为两类:
- 静态数据:车辆的基本信息,如车辆ID、车型、生产日期等。
- 动态数据:车辆运行时产生的数据,如速度、油耗、位置、温度等。
2. 表结构设计
在TDengine中,可以使用超级表(Super Table)和子表(Sub Table)来组织数据。超级表定义了数据的通用结构,而子表则存储具体设备的数据。
2.1 静态数据表
静态数据可以存储在一个单独的表中,或者作为超级表的一部分。如果静态数据变化不频繁,可以将其作为超级表的标签(Tag)。
CREATE TABLE vehicle_info (
vehicle_id BINARY(32), -- 车辆ID
model BINARY(32), -- 车型
production_date TIMESTAMP, -- 生产日期
PRIMARY KEY(vehicle_id)
);
2.2 动态数据表
动态数据可以使用超级表和子表来存储。超级表定义了所有子表的公共结构,子表则存储具体车辆的数据。
sql
-- 创建超级表
CREATE STABLE vehicle_data (
ts TIMESTAMP, -- 时间戳
speed DOUBLE, -- 速度
fuel_level DOUBLE, -- 油耗
temperature DOUBLE, -- 温度
latitude DOUBLE, -- 纬度
longitude DOUBLE -- 经度
) TAGS (
vehicle_id BINARY(32), -- 车辆ID
model BINARY(32) -- 车型
);
-- 创建子表
CREATE TABLE vehicle_001 USING vehicle_data TAGS ('vehicle_001', 'ModelA');
CREATE TABLE vehicle_002 USING vehicle_data TAGS ('vehicle_002', 'ModelB');
--也可以自动建表,见上面目录自动建表
3. 插入数据
插入数据时,指定子表名称和具体的时间戳及数据值。
INSERT INTO vehicle_001 (ts, speed, fuel_level, temperature, latitude, longitude) VALUES (NOW, 60, 50, 25, 37.7749, -122.4194);
INSERT INTO vehicle_002 (ts, speed, fuel_level, temperature, latitude, longitude) VALUES (NOW, 55, 45, 22, 37.7749, -122.4194);
4. 查询数据
查询数据时,可以利用超级表的标签进行过滤和聚合。
4.1 查询单个车辆的数据
SELECT * FROM vehicle_001 WHERE ts >= NOW - 1h;
4.2 查询多个车辆的数据
SELECT * FROM vehicle_data WHERE ts >= NOW - 1h AND vehicle_id IN ('vehicle_001', 'vehicle_002');
4.3 聚合查询
SELECT vehicle_id, AVG(speed), MAX(fuel_level) FROM vehicle_data WHERE ts >= NOW - 1h GROUP BY vehicle_id;
5. 性能优化
- 分区:可以根据时间或车辆ID对数据进行分区,提高查询性能。
- 索引:虽然TDengine不支持传统的关系型数据库索引,但通过合理的设计表结构和使用标签,可以实现高效的查询。
- 数据压缩:TDengine支持多种数据压缩算法,可以有效减少存储空间。
6. 安全性和监控
- 权限管理:配置合适的用户权限,确保数据的安全性。
- 监控:定期监控数据库的性能指标,及时发现和解决问题。