TDEngine不完全指北

2,158 阅读55分钟

TDEngine不完全指北

阅读建议:

简介部分看个开头即可。

基本概念中详细看《数据特征》,《一个数据采集点一张表》,《存储模型与数据分区、分片》,《缓存与持久化》,《数据查询》。

基本使用并且高级功能部分要重点理解。

整合Springboot和Mybatis最好上手操作,文中配置皆为真实配置。

最后的请全程关注超级表的概念,要理解超级表是什么以及围绕其产生的基本概念。

简介

TDengine是涛思数据面对高速增长的物联网大数据市场和技术挑战推出的创新性的大数据处理产品,在吸取众多传统关系型数据库、NoSQL数据库、流式计算引擎、消息队列等软件的优点之后自主开发的产品,在时序空间大数据处理上,有着自己独到的优势。

TDengine的模块之一是时序数据库。但除此之外,为减少研发的复杂度、系统维护的难度,TDengine还提供缓存、消息队列、订阅、流式计算等功能,为物联网、工业互联网大数据的处理提供全栈的技术方案,是一个高效易用的物联网大数据平台。与Hadoop等典型的大数据平台相比,它具有如下鲜明的特点:

  • 10倍以上的性能提升:定义了创新的数据存储结构,单核每秒能处理至少2万次请求,插入数百万个数据点,读出一千万以上数据点,比现有通用数据库快十倍以上。
  • 硬件或云服务成本降至1/5:由于超强性能,计算资源不到通用大数据方案的1/5;通过列式存储和先进的压缩算法,存储空间不到通用数据库的1/10。
  • 全栈时序数据处理引擎:将数据库、消息队列、缓存、流式计算等功能融为一体,应用无需再集成Kafka/Redis/HBase/Spark/HDFS等软件,大幅降低应用开发和维护的复杂度成本。
  • 强大的分析功能:无论是十年前还是一秒钟前的数据,指定时间范围即可查询。数据可在时间轴上或多个设备上进行聚合。即席查询可通过Shell, Python, R, MATLAB随时进行。
  • 与第三方工具无缝连接:不用一行代码,即可与Telegraf, Grafana, EMQ, HiveMQ, Prometheus, MATLAB, R等集成。后续将支持OPC, Hadoop, Spark等, BI工具也将无缝连接。
  • 零运维成本、零学习成本:安装集群简单快捷,无需分库分表,实时备份。类似标准SQL,支持RESTful, 支持Python/Java/C/C++/C#/Go/Node.js, 与MySQL相似,零学习成本。

基本概念

数据模型

物联网典型场景

在典型的物联网、车联网、运维监测场景中,往往有多种不同类型的数据采集设备,采集一个到多个不同的物理量。而同一种采集设备类型,往往又有多个具体的采集设备分布在不同的地点。大数据处理系统就是要将各种采集的数据汇总,然后进行计算和分析。对于同一类设备,其采集的数据都是很规则的。以智能电表为例,假设每个智能电表采集电流、电压、相位三个量,其采集的数据类似如下的表格:

设备ID时间戳采集量标签
Device IDTime StampcurrentvoltagephaselocationgroupId
d1001153854868500010.32190.31Beijing.Chaoyang2
d1002153854868400010.22200.23Beijing.Chaoyang3
d1003153854868650011.52210.35Beijing.Haidian3
d1004153854868550013.42230.29Beijing.Haidian2
d1001153854869500012.62180.33Beijing.Chaoyang2
d1004153854869660011.82210.28Beijing.Haidian2
d1002153854869665010.32180.25Beijing.Chaoyang3
d1001153854869680012.32210.31Beijing.Chaoyang2

每一条记录都有设备ID,时间戳,采集的物理量(如上图中的电流、电压、相位),还有与每个设备相关的静态标签(如上述表一中的位置Location和分组groupId)。每个设备是受外界的触发,或按照设定的周期采集数据。采集的数据点是时序的,是一个数据流。

数据特征

除时序特征外,仔细研究发现,物联网、车联网、运维监测类数据还具有很多其他明显的特征:

  1. 数据高度结构化;
  2. 数据极少有更新或删除操作;
  3. 无需传统数据库的事务处理;
  4. 相对互联网应用,写多读少;
  5. 流量平稳,根据设备数量和采集频次,可以预测出来;
  6. 用户关注的是一段时间的趋势,而不是某一特定时间点的值;
  7. 数据有保留期限;
  8. 数据的查询分析一定是基于时间段和空间区域;
  9. 除存储、查询操作外,还需要各种统计和实时计算操作;
  10. 数据量巨大,一天可能采集的数据就可以超过100亿条。

关系型数据库模型

因为采集的数据一般是结构化数据,同时为降低学习门槛,TDengine采用传统的关系型数据库模型管理数据。因此用户需要先创建库,然后创建表,之后才能插入或查询数据。TDengine采用的是结构化存储,而不是NoSQL的key-value存储。

一个数据采集点一张表(重点)

为充分利用其数据的时序性和其他数据特点,TDengine要求对每个数据采集点单独建表(比如有一千万个智能电表,就需创建一千万张表,上述表格中的d1001, d1002, d1003, d1004都需单独建表),用来存储这个采集点所采集的时序数据。这种设计有几大优点:

  1. 能保证一个采集点的数据在存储介质上是以块为单位连续存储的。如果读取一个时间段的数据,它能大幅减少随机读取操作,成数量级的提升读取和查询速度。
  2. 由于不同采集设备产生数据的过程完全独立,每个设备的数据源是唯一的,一张表也就只有一个写入者,这样就可采用无锁方式来写,写入速度就能大幅提升。
  3. 对于一个数据采集点而言,其产生的数据是时序的,因此写的操作可用追加的方式实现,进一步大幅提高数据写入速度。

如果采用传统的方式,将多个设备的数据写入一张表,由于网络延时不可控,不同设备的数据到达服务器的时序是无法保证的,写入操作是要有锁保护的,而且一个设备的数据是难以保证连续存储在一起的。采用一个数据采集点一张表的方式,能最大程度的保证单个数据采集点的插入和查询的性能是最优的。

TDengine 建议用数据采集点的名字(如上表中的D1001)来做表名。每个数据采集点可能同时采集多个物理量(如上表中的curent, voltage, phase),每个物理量对应一张表中的一列,数据类型可以是整型、浮点型、字符串等。除此之外,表的第一列必须是时间戳,即数据类型为 timestamp。对采集的数据,TDengine将自动按照时间戳建立索引,但对采集的物理量不建任何索引。数据用列式存储方式保存。

超级表:同一类型数据采集点的集合(重点)

由于一个数据采集点一张表,导致表的数量巨增,难以管理,而且应用经常需要做采集点之间的聚合操作,聚合的操作也变得复杂起来。为解决这个问题,TDengine引入超级表(Super Table,简称为STable)的概念。

超级表是指某一特定类型的数据采集点的集合。同一类型的数据采集点,其表的结构是完全一样的,但每个表(数据采集点)的静态属性(标签)是不一样的。描述一个超级表(某一特定类型的数据采集点的结合),除需要定义采集量的表结构之外,还需要定义其标签的schema,标签的数据类型可以是整数、浮点数、字符串,标签可以有多个,可以事后增加、删除或修改。 如果整个系统有N个不同类型的数据采集点,就需要建立N个超级表。

在TDengine的设计里,表用来代表一个具体的数据采集点,超级表用来代表一组相同类型的数据采集点集合。当为某个具体数据采集点创建表时,用户使用超级表的定义做模板,同时指定该具体采集点(表)的标签值。与传统的关系型数据库相比,表(一个数据采集点)是带有静态标签的,而且这些标签可以事后增加、删除、修改。一张超级表包含有多张表,这些表具有相同的时序数据schema,但带有不同的标签值

当对多个具有相同数据类型的数据采集点进行聚合操作时,TDengine会先把满足标签过滤条件的表从超级表中找出来,然后再扫描这些表的时序数据,进行聚合操作,这样需要扫描的数据集会大幅减少,从而显著提高聚合计算的性能。

集群与基本逻辑单元(了解)

TDengine 的设计是基于单个硬件、软件系统不可靠,基于任何单台计算机都无法提供足够计算能力和存储能力处理海量数据的假设进行设计的。因此 TDengine 从研发的第一天起,就按照分布式高可靠架构进行设计,是支持水平扩展的,这样任何单台或多台服务器发生硬件故障或软件错误都不影响系统的可用性和可靠性。同时,通过节点虚拟化并辅以自动化负载均衡技术,TDengine 能最高效率地利用异构集群中的计算和存储资源降低硬件投资。

主要逻辑单元

TDengine 分布式架构的逻辑结构图如下:

一个完整的 TDengine 系统是运行在一到多个物理节点上的,逻辑上,它包含数据节点(dnode)、TDengine应用驱动(taosc)以及应用(app)。系统中存在一到多个数据节点,这些数据节点组成一个集群(cluster)。应用通过taosc的API与TDengine集群进行互动。下面对每个逻辑单元进行简要介绍。

物理节点(pnode): pnode是一独立运行、拥有自己的计算、存储和网络能力的计算机,可以是安装有OS的物理机、虚拟机或Docker容器。

数据节点(dnode): dnode 是 TDengine 服务器侧执行代码 taosd 在物理节点上的一个运行实例,一个工作的系统必须有至少一个数据节点。

虚拟节点(vnode) : 为更好的支持数据分片、负载均衡,防止数据过热或倾斜,数据节点被虚拟化成多个虚拟节点(vnode,图中V2, V3, V4等)。

管理节点(mnode): 一个虚拟的逻辑单元,负责所有数据节点运行状态的监控和维护,以及节点之间的负载均衡(图中M)。

虚拟节点组(VGroup): 不同数据节点上的 vnode 可以组成一个虚拟节点组(vnode group)来保证系统的高可靠。

TAOSC: taosc是TDengine给应用提供的驱动程序(driver),负责处理应用与集群的接口交互,提供C/C++语言原生接口,内嵌于JDBC、C#、Python、Go、Node.js语言连接库里

节点之间的通讯(了解)

**通讯方式:**TDengine系统的各个数据节点之间,以及应用驱动与各数据节点之间的通讯是通过TCP/UDP进行的。

FQDN配置:一个数据节点有一个或多个FQDN,可以在系统配置文件taos.cfg通过参数“fqdn"进行指定,如果没有指定,系统将自动获取计算机的hostname作为其FQDN。

**端口配置:**一个数据节点对外的端口由TDengine的系统配置参数serverPort决定,对集群内部通讯的端口是serverPort+5。

集群对外连接: TDengine集群可以容纳单个、多个甚至几千个数据节点。应用只需要向集群中任何一个数据节点发起连接即可,连接需要提供的网络参数是一数据节点的End Point(FQDN加配置的端口号)。

集群内部通讯: 各个数据节点之间通过TCP/UDP进行连接。

MNODE的选择: TDengine逻辑上有管理节点,但没有单独的执行代码,服务器侧只有一套执行代码taosd。

新数据节点的加入:系统有了一个数据节点后,就已经成为一个工作的系统。

重定向:无论是dnode还是taosc,最先都是要发起与mnode的连接,但mnode是系统自动创建并维护的,因此对于用户来说,并不知道哪个dnode在运行mnode。

一个典型的消息流程

为解释vnode, mnode, taosc和应用之间的关系以及各自扮演的角色,下面对写入数据这个典型操作的流程进行剖析。

  1. 应用通过JDBC、ODBC或其他API接口发起插入数据的请求。
  2. taosc会检查缓存,看是否保存有该表的meta data。如果有,直接到第4步。如果没有,taosc将向mnode发出get meta-data请求。
  3. mnode将该表的meta-data返回给taosc。Meta-data包含有该表的schema, 而且还有该表所属的vgroup信息(vnode ID以及所在的dnode的End Point,如果副本数为N,就有N组End Point)。如果taosc迟迟得不到mnode回应,而且存在多个mnode, taosc将向下一个mnode发出请求。
  4. taosc向master vnode发起插入请求。
  5. vnode插入数据后,给taosc一个应答,表示插入成功。如果taosc迟迟得不到vnode的回应,taosc会认为该节点已经离线。这种情况下,如果被插入的数据库有多个副本,taosc将向vgroup里下一个vnode发出插入请求。
  6. taosc通知APP,写入成功。

存储模型与数据分区、分片(重点理解)

存储模型

TDengine存储的数据包括采集的时序数据以及库、表相关的元数据、标签数据等,这些数据具体分为三部分:

  • 时序数据:存放于vnode里,由data、head和last三个文件组成,数据量大,查询量取决于应用场景。容许乱序写入,但暂时不支持删除操作,并且仅在update参数设置为1时允许更新操作。通过采用一个采集点一张表的模型,一个时间段的数据是连续存储,对单张表的写入是简单的追加操作,一次读,可以读到多条记录,这样保证对单个采集点的插入和查询操作,性能达到最优。
  • 标签数据:存放于vnode里的meta文件,支持增删改查四个标准操作。数据量不大,有N张表,就有N条记录,因此可以全内存存储。如果标签过滤操作很多,查询将十分频繁,因此TDengine支持多核多线程并发查询。只要计算资源足够,即使有数千万张表,过滤结果能毫秒级返回。
  • 元数据:存放于mnode里,包含系统节点、用户、DB、Table Schema等信息,支持增删改查四个标准操作。这部分数据的量不大,可以全内存保存,而且由于客户端有缓存,查询量也不大。因此目前的设计虽是集中式存储管理,但不会构成性能瓶颈。

与典型的NoSQL存储模型相比,TDengine将标签数据与时序数据完全分离存储,它具有两大优势:

  • 能够极大地降低标签数据存储的冗余度:一般的NoSQL数据库或时序数据库,采用的K-V存储,其中的Key包含时间戳、设备ID、各种标签。每条记录都带有这些重复的内容,浪费存储空间。而且如果应用要在历史数据上增加、修改或删除标签,需要遍历数据,重写一遍,操作成本极其昂贵。
  • 能够实现极为高效的多表之间的聚合查询:做多表之间聚合查询时,先把符合标签过滤条件的表查找出来,然后再查找这些表相应的数据块,这样大幅减少要扫描的数据集,从而大幅提高查询效率。而且标签数据采用全内存的结构进行管理和维护,千万级别规模的标签数据查询可以在毫秒级别返回。

数据分片

对于海量的数据管理,为实现水平扩展,一般都需要采取分片(Sharding)分区(Partitioning)策略。TDengine是通过vnode来实现数据分片的,通过一个时间段一个数据文件来实现时序数据分区的。vnode(虚拟数据节点)负责为采集的时序数据提供写入、查询和计算功能。对于单独一个数据采集点,无论其数据量多大,一个vnode(或vnode group, 如果副本数大于1)有足够的计算资源和存储资源来处理,因此TDengine将一张表(一个数据采集点)的所有数据都存放在一个vnode里。

当创建一张表时,系统将看是否有已经分配的vnode, 且该vnode是否有空余的表空间,如果有,立即在该有空位的vnode创建表。如果没有,系统将从集群中,根据当前的负载情况,在一个dnode上创建一新的vnode, 然后创建表。如果DB有多个副本,系统不是只创建一个vnode,而是一个vgroup(虚拟数据节点组)。系统对vnode的数目没有任何限制,仅仅受限于物理节点本身的计算和存储资源。

每张表的meda data(包含schema, 标签等)也存放于vnode里,而不是集中存放于mnode,实际上这是对Meta数据的分片,这样便于高效并行的进行标签过滤操作。

数据分区

TDengine除vnode分片之外,还对时序数据按照时间段进行分区。每个数据文件只包含一个时间段的时序数据,时间段的长度由DB的配置参数days决定。这种按时间段分区的方法还便于高效实现数据的保留策略,只要数据文件超过规定的天数(系统配置参数keep),将被自动删除。而且不同的时间段可以存放于不同的路径和存储介质,以便于大数据的冷热管理,实现多级存储。

总的来说,TDengine是通过vnode以及时间两个维度,对大数据进行切分,便于并行高效的管理,实现水平扩展。

负载均衡

每个dnode都定时向 mnode(虚拟管理节点)报告其状态(包括硬盘空间、内存大小、CPU、网络、虚拟节点个数等),因此mnode了解整个集群的状态。基于整体状态,当mnode发现某个dnode负载过重,它会将dnode上的一个或多个vnode挪到其他dnode。在挪动过程中,对外服务继续进行,数据插入、查询和计算操作都不受影响。

负载均衡过程无需任何人工干预,应用也无需重启,将自动连接新的节点,完全透明。 提示:负载均衡由参数balance控制,决定开启/关闭自动负载均衡。

数据写入与复制流程

如果一个数据库有N个副本,那一个虚拟节点组就有N个虚拟节点,但是只有一个是Master,其他都是slave。当应用将新的记录写入系统时,只有Master vnode能接受写的请求。如果slave vnode收到写的请求,系统将通知taosc需要重新定向。

Master vnode写入流程

Master Vnode遵循下面的写入流程:

  1. Master vnode收到应用的数据插入请求,验证OK,进入下一步;
  2. 如果系统配置参数walLevel大于0,vnode将把该请求的原始数据包写入数据库日志文件WAL。如果walLevel设置为2,而且fsync设置为0,TDengine还将WAL数据立即落盘,以保证即使宕机,也能从数据库日志文件中恢复数据,避免数据的丢失;
  3. 如果有多个副本,vnode将把数据包转发给同一虚拟节点组内slave vnodes, 该转发包带有数据的版本号(version);
  4. 写入内存,并将记录加入到skip list;
  5. Master vnode返回确认信息给应用,表示写入成功。
  6. 如果第2,3,4步中任何一步失败,将直接返回错误给应用。

Slave vnode写入流程

对于slave vnode, 写入流程是:

  1. Slave vnode收到Master vnode转发了的数据插入请求。

  2. 如果系统配置参数walLevel大于0,vnode将把该请求的原始数据包写入数据库日志文件WAL。如果walLevel设置为2,而且fsync设置为0,TDengine还将WAL数据立即落盘,以保证即使宕机,也能从数据库日志文件中恢复数据,避免数据的丢失;

  3. 写入内存,更新内存中的skip list。

    与Master vnode相比,slave vnode不存在转发环节,也不存在回复确认环节,少了两步。但写内存与WAL是完全一样的。

主从选择

Vnode会保持一个数据版本号(Version),对内存数据进行持久化存储时,对该版本号也进行持久化存储。每个数据更新操作,无论是采集的时序数据还是元数据,这个版本号将增一。

一个vnode启动时,角色(master、slave) 是不定的,数据是处于未同步状态,它需要与虚拟节点组内其他节点建立TCP连接,并互相交换status,其中包括version和自己的角色。通过status的交换,系统进入选主流程,规则如下:

  1. 如果只有一个副本,该副本永远就是master
  2. 所有副本都在线时,版本最高的被选为master
  3. 在线的虚拟节点数过半,而且有虚拟节点是slave的话,该虚拟节点自动成为master
  4. 对于2和3,如果多个虚拟节点满足成为master的要求,那么虚拟节点组的节点列表里,最前面的选为master

更多的关于数据复制的流程,请见TDengine 2.0数据复制模块设计

同步复制

对于数据一致性要求更高的场景,异步数据复制无法满足要求,因为有极小的概率丢失数据,因此TDengine提供同步复制的机制供用户选择。在创建数据库时,除指定副本数replica之外,用户还需要指定新的参数quorum。如果quorum大于一,它表示每次Master转发给副本时,需要等待quorum-1个回复确认,才能通知应用,数据在slave已经写入成功。如果在一定的时间内,得不到quorum-1个回复确认,master vnode将返回错误给应用。

采用同步复制,系统的性能会有所下降,而且latency会增加。因为元数据要强一致,mnode之间的数据同步缺省就是采用的同步复制。

缓存与持久化(重点理解)

缓存

**TDengine采用时间驱动缓存管理策略(First-In-First-Out,FIFO),又称为写驱动的缓存管理机制。这种策略有别于读驱动的数据缓存模式(Least-Recent-Used,LRU),直接将最近写入的数据保存在系统的缓存中。当缓存达到临界值的时候,将最早的数据批量写入磁盘。 **一般意义上来说,对于物联网数据的使用,用户最为关心的是刚产生的数据,即当前状态。TDengine充分利用这一特性,将最近到达的(当前状态)数据保存在缓存中。

TDengine通过查询函数向用户提供毫秒级的数据获取能力。直接将最近到达的数据保存在缓存中,可以更加快速地响应用户针对最近一条或一批数据的查询分析,整体上提供更快的数据库查询响应能力。从这个意义上来说,可通过设置合适的配置参数将TDengine作为数据缓存来使用,而不需要再部署Redis或其他额外的缓存系统,可有效地简化系统架构,降低运维的成本。需要注意的是,TDengine重启以后系统的缓存将被清空,之前缓存的数据均会被批量写入磁盘,缓存的数据将不会像专门的Key-value缓存系统再将之前缓存的数据重新加载到缓存中。

每个vnode有自己独立的内存,而且由多个固定大小的内存块组成,不同vnode之间完全隔离。数据写入时,类似于日志的写法,数据被顺序追加写入内存,但每个vnode维护有自己的skip list,便于迅速查找。当三分之一以上的内存块写满时,启动落盘操作,而且后续写的操作在新的内存块进行。这样,一个vnode里有三分之一内存块是保留有最近的数据的,以达到缓存、快速查找的目的。一个vnode的内存块的个数由配置参数blocks决定,内存块的大小由配置参数cache决定。

持久化存储

TDengine采用数据驱动的方式让缓存中的数据写入硬盘进行持久化存储。当vnode中缓存的数据达到一定规模时,为了不阻塞后续数据的写入,TDengine也会拉起落盘线程将缓存的数据写入持久化存储。TDengine在数据落盘时会打开新的数据库日志文件,在落盘成功后则会删除老的数据库日志文件,避免日志文件无限制的增长。

为充分利用时序数据特点,TDengine将一个vnode保存在持久化存储的数据切分成多个文件,每个文件只保存固定天数的数据,这个天数由系统配置参数days决定。切分成多个文件后,给定查询的起止日期,无需任何索引,就可以立即定位需要打开哪些数据文件,大大加快读取速度。

对于采集的数据,一般有保留时长,这个时长由系统配置参数keep决定。超过这个设置天数的数据文件,将被系统自动删除,释放存储空间。

给定days与keep两个参数,一个典型工作状态的vnode中总的数据文件数为:向上取整(keep/days)+1个。总的数据文件个数不宜过大,也不宜过小。10到100以内合适。基于这个原则,可以设置合理的days。 目前的版本,参数keep可以修改,但对于参数days,一但设置后,不可修改。

在每个数据文件里,一张表的数据是一块一块存储的。一张表可以有一到多个数据文件块。在一个文件块里,数据是列式存储的,占用的是一片连续的存储空间,这样大大提高读取速度。文件块的大小由系统参数maxRows(每块最大记录条数)决定,缺省值为4096。这个值不宜过大,也不宜过小。过大,定位具体时间段的数据的搜索时间会变长,影响读取速度;过小,数据块的索引太大,压缩效率偏低,也影响读取速度。

每个数据文件(.data结尾)都有一个对应的索引文件(.head结尾),该索引文件对每张表都有一数据块的摘要信息,记录了每个数据块在数据文件中的偏移量,数据的起止时间等信息,以帮助系统迅速定位需要查找的数据。每个数据文件还有一对应的last文件(.last结尾),该文件是为防止落盘时数据块碎片化而设计的。如果一张表落盘的记录条数没有达到系统配置参数minRows(每块最小记录条数),将被先存储到last文件,等下次落盘时,新落盘的记录将与last文件的记录进行合并,再写入数据文件。

数据写入磁盘时,根据系统配置参数comp决定是否压缩数据。TDengine提供了三种压缩选项:无压缩、一阶段压缩和两阶段压缩,分别对应comp值为0、1和2的情况。一阶段压缩根据数据的类型进行了相应的压缩,压缩算法包括delta-delta编码、simple 8B方法、zig-zag编码、LZ4等算法。二阶段压缩在一阶段压缩的基础上又用通用压缩算法进行了压缩,压缩率更高。

多级存储

在默认配置下,TDengine会将所有数据保存在/var/lib/taos目录下,而且每个vnode的数据文件保存在该目录下的不同目录。为扩大存储空间,尽量减少文件读取的瓶颈,提高数据吞吐率 TDengine可通过配置系统参数dataDir让多个挂载的硬盘被系统同时使用。除此之外,TDengine也提供了数据分级存储的功能,即根据数据文件的新老程度存储在不同的存储介质上。比如最新的数据存储在SSD上,超过一周的数据存储在本地硬盘上,超过4周的数据存储在网络存储设备上,这样来降低存储成本,而又保证高效的访问数据。数据在不同存储介质上的移动是由系统自动完成的,对应用是完全透明的。数据的分级存储也是通过系统参数dataDir来配置。

dataDir的配置格式如下:

dataDir data_path [tier_level]

其中data_path为挂载点的文件夹路径,tier_level为介质存储等级。介质存储等级越高,盛放数据文件越老。同一存储等级可挂载多个硬盘,同一存储等级上的数据文件分布在该存储等级的所有硬盘上。TDengine最多支持3级存储,所以tier_level的取值为0、1和2。在配置dataDir时,必须存在且只有一个挂载路径不指定tier_level,称之为特殊挂载盘(路径)。该挂载路径默认为0级存储介质,且包含特殊文件链接,不可被移除,否则会对写入的数据产生毁灭性影响。

假设一物理节点有六个可挂载的硬盘/mnt/disk1、/mnt/disk2、…、/mnt/disk6,其中disk1和disk2需要被指定为0级存储介质,disk3和disk4为1级存储介质, disk5和disk6为2级存储介质。disk1为特殊挂载盘,则可在/etc/taos/taos.cfg中做如下配置:

dataDir /mnt/disk1/taosdataDir /mnt/disk2/taos 0dataDir /mnt/disk3/taos 1dataDir /mnt/disk4/taos 1dataDir /mnt/disk5/taos 2dataDir /mnt/disk6/taos 2

挂载的盘也可以是非本地的网络盘,只要系统能访问即可。

数据查询(重点理解)

TDengine提供了多种多样针对表和超级表的查询处理功能,除了常规的聚合查询之外,还提供针对时序数据的窗口查询、统计聚合等功能。TDengine的查询处理需要客户端、vnode, mnode节点协同完成。

单表查询

SQL语句的解析和校验工作在客户端完成。解析SQL语句并生成抽象语法树(Abstract Syntax Tree, AST),然后对其进行校验和检查。以及向管理节点(mnode)请求查询中指定表的元数据信息(table metadata)。

根据元数据信息中的End Point信息,将查询请求序列化后发送到该表所在的数据节点(dnode)。dnode接收到查询请求后,识别出该查询请求指向的虚拟节点(vnode),将消息转发到vnode的查询执行队列。vnode的查询执行线程建立基础的查询执行环境,并立即返回该查询请求,同时开始执行该查询。

客户端在获取查询结果的时候,dnode的查询执行队列中的工作线程会等待vnode执行线程执行完成,才能将查询结果返回到请求的客户端。

按时间轴聚合、降采样、插值

时序数据有别于普通数据的显著特征是每条记录均具有时间戳,因此针对具有时间戳数据在时间轴上进行聚合是不同于普通数据库的重要功能。从这点上来看,与流计算引擎的窗口查询有相似的地方。

在TDengine中引入关键词interval来进行时间轴上固定长度时间窗口的切分,并按照时间窗口对数据进行聚合,对窗口范围内的数据按需进行聚合。例如:

select count(*) from d1001 interval(1h);

针对d1001设备采集的数据,按照1小时的时间窗口返回每小时存储的记录数量。

在需要连续获得查询结果的应用场景下,如果给定的时间区间存在数据缺失,会导致该区间数据结果也丢失。TDengine提供策略针对时间轴聚合计算的结果进行插值,通过使用关键词Fill就能够对时间轴聚合结果进行插值。例如:

select count(*) from d1001 interval(1h) fill(prev);

针对d1001设备采集数据统计每小时记录数,如果某一个小时不存在数据,则返回之前一个小时的统计数据。TDengine提供前向插值(prev)、线性插值(linear)、NULL值填充(NULL)、特定值填充(value)。

多表聚合查询(超级表重点理解)

TDengine对每个数据采集点单独建表,但在实际应用中经常需要对不同的采集点数据进行聚合。为高效的进行聚合操作,TDengine引入超级表(STable)的概念。超级表用来代表一特定类型的数据采集点,它是包含多张表的表集合,集合里每张表的模式(schema)完全一致,但每张表都带有自己的静态标签,标签可以多个,可以随时增加、删除和修改。 应用可通过指定标签的过滤条件,对一个STable下的全部或部分表进行聚合或统计操作,这样大大简化应用的开发。其具体流程如下图所示:

  1. 应用将一个查询条件发往系统;
  2. taosc将超级表的名字发往 Meta Node(管理节点);
  3. 管理节点将超级表所拥有的 vnode 列表发回 taosc;
  4. taosc将计算的请求连同标签过滤条件发往这些vnode对应的多个数据节点;
  5. 每个vnode先在内存里查找出自己节点里符合标签过滤条件的表的集合,然后扫描存储的时序数据,完成相应的聚合计算,将结果返回给taosc;
  6. taosc将多个数据节点返回的结果做最后的聚合,将其返回给应用。

由于TDengine在vnode内将标签数据与时序数据分离存储,通过在内存里过滤标签数据,先找到需要参与聚合操作的表的集合,将需要扫描的数据集大幅减少,大幅提升聚合计算速度。同时,由于数据分布在多个vnode/dnode,聚合计算操作在多个vnode里并发进行,又进一步提升了聚合的速度。 对普通表的聚合函数以及绝大部分操作都适用于超级表,语法完全一样,细节请看 TAOS SQL。

预计算

为有效提升查询处理的性能,针对物联网数据的不可更改的特点,在数据块头部记录该数据块中存储数据的统计信息:包括最大值、最小值、和。我们称之为预计算单元。如果查询处理涉及整个数据块的全部数据,直接使用预计算结果,完全不需要读取数据块的内容。由于预计算数据量远小于磁盘上存储的数据块数据的大小,对于磁盘IO为瓶颈的查询处理,使用预计算结果可以极大地减小读取IO压力,加速查询处理的流程。预计算机制与Postgre SQL的索引BRIN(block range index)有异曲同工之妙。

基本使用

数据建模

创建库

创建一个库时,除SQL标准的选项外,应用还可以指定保留时长、副本数、内存块个数、时间精度、文件块里最大最小记录条数、是否压缩、一个数据文件覆盖的天数等多种参数。比如:

CREATE DATABASE power KEEP 365 DAYS 10 BLOCKS 4 UPDATE 1;

述语句将创建一个名为power的库,这个库的数据将保留365天(超过365天将被自动删除),每10天一个数据文件,内存块数为4,允许更新数据

创建库之后,需要使用SQL命令USE将当前库切换过来,例如:

USE power;

注意:

  • 任何一张表或超级表是属于一个库的,在创建表之前,必须先创建库。
  • 处于两个不同库的表是不能进行JOIN操作的。

创建超级表

CREATE STABLE meters (ts timestamp, current float, voltage int, phase float) TAGS (location binary(64), groupId int);

与创建普通表一样,创建表时,需要提供表名(示例中为meters),表结构Schema,即数据列的定义。第一列必须为时间戳(示例中为ts),其他列为采集的物理量(示例中为current, voltage, phase),数据类型可以为整型、浮点型、字符串等。除此之外,还需要提供标签的schema (示例中为location, groupId),标签的数据类型可以为整型、浮点型、字符串等。

一张超级表最多容许1024列,如果一个采集点采集的物理量个数超过1024,需要建多张超级表来处理。一个系统可以有多个DB,一个DB里可以有一到多个超级表。

创建表

INSERT INTO d1001 USING METERS TAGS ("Beijng.Chaoyang", 2) VALUES (now, 10.2, 219, 0.32);

其中d1001是表名,meters是超级表的表名,后面紧跟标签Location的具体标签值”Beijing.Chaoyang",标签groupId的具体标签值2。虽然在创建表时,需要指定标签值,但可以事后修改。

自动建表:在某些特殊场景中,用户在写数据时并不确定某个数据采集点的表是否存在,此时可在写入数据时使用自动建表语法来创建不存在的表,若该表已存在则不会建立新表。比如:

INSERT INTO d1001 USING METERS TAGS ("Beijng.Chaoyang", 2) VALUES (now, 10.2, 219, 0.32);

上述SQL语句将记录 (now, 10.2, 219, 0.32) 插入表d1001。如果表d1001还未创建,则使用超级表meters做模板自动创建,同时打上标签值“Beijing.Chaoyang", 2。

TAOS SQL

支持的数据类型

使用 TDengine,最重要的是时间戳。创建并插入记录、查询历史记录的时候,均需要指定时间戳。时间戳有如下规则:

  • 时间格式为 YYYY-MM-DD HH:mm:ss.MS,默认时间分辨率为毫秒。比如:2017-08-12 18:25:58.128
  • 内部函数 now 是客户端的当前时间
  • 插入记录时,如果时间戳为 now,插入数据时使用提交这条记录的客户端的当前时间
  • Epoch Time:时间戳也可以是一个长整数,表示从格林威治时间 1970-01-01 00:00:00.000 (UTC/GMT) 开始的毫秒数(相应地,如果所在 Database 的时间精度设置为“微秒”,则长整型格式的时间戳含义也就对应于从格林威治时间 1970-01-01 00:00:00.000 (UTC/GMT) 开始的微秒数;纳秒精度的逻辑也是类似的。)
  • 时间可以加减,比如 now-2h,表明查询时刻向前推 2 个小时(最近 2 小时) 。数字后面的时间单位可以是 b(纳秒)、u(微秒)、a(毫秒)、s(秒)、m(分)、h(小时)、d(天)、w(周)。 比如 select * from t1 where ts > now-2w and ts <= now-1w,表示查询两周前整整一周的数据。在指定降频操作(down sampling)的时间窗口(interval)时,时间单位还可以使用 n(自然月) 和 y(自然年)。

在TDengine中,普通表的数据模型中可使用以下 10 种数据类型。

#类型Bytes说明
1TIMESTAMP8时间戳。缺省精度毫秒,可支持微秒和纳秒。从格林威治时间 1970-01-01 00:00:00.000 (UTC/GMT) 开始,计时不能早于该时间。(从 2.0.18.0 版本开始,已经去除了这一时间范围限制)(从 2.1.5.0 版本开始支持纳秒精度)
2INT4整型,范围 [-2^31+1, 2^31-1], -2^31 用作 NULL
3BIGINT8长整型,范围 [-2^63+1, 2^63-1], -2^63 用于 NULL
4FLOAT4浮点型,有效位数 6-7,范围 [-3.4E38, 3.4E38]
5DOUBLE8双精度浮点型,有效位数 15-16,范围 [-1.7E308, 1.7E308]
6BINARY自定义记录单字节字符串,建议只用于处理 ASCII 可见字符,中文等多字节字符需使用 nchar。理论上,最长可以有 16374 字节,但由于每行数据最多 16K 字节,实际上限一般小于理论值。binary 仅支持字符串输入,字符串两端需使用单引号引用。使用时须指定大小,如 binary(20) 定义了最长为 20 个单字节字符的字符串,每个字符占 1 byte 的存储空间,总共固定占用 20 bytes 的空间,此时如果用户字符串超出 20 字节将会报错。对于字符串内的单引号,可以用转义字符反斜线加单引号来表示,即 \’
7SMALLINT2短整型, 范围 [-32767, 32767], -32768 用于 NULL
8TINYINT1单字节整型,范围 [-127, 127], -128 用于 NULL
9BOOL1布尔型,{true, false}
10NCHAR自定义记录包含多字节字符在内的字符串,如中文字符。每个 nchar 字符占用 4 bytes 的存储空间。字符串两端使用单引号引用,字符串内的单引号需用转义字符 \’。nchar 使用时须指定字符串大小,类型为 nchar(10) 的列表示此列的字符串最多存储 10 个 nchar 字符,会固定占用 40 bytes 的空间。如果用户字符串长度超出声明长度,将会报错。

Tips:

  1. TDengine 对 SQL 语句中的英文字符不区分大小写,自动转化为小写执行。因此用户大小写敏感的字符串及密码,需要使用单引号将字符串引起来。
  2. 注意,虽然 Binary 类型在底层存储上支持字节型的二进制字符,但不同编程语言对二进制数据的处理方式并不保证一致,因此建议在 Binary 类型中只存储 ASCII 可见字符,而避免存储不可见字符。多字节的数据,例如中文字符,则需要使用 nchar 类型进行保存。如果强行使用 Binary 类型保存中文字符,虽然有时也能正常读写,但并不带有字符集信息,很容易出现数据乱码甚至数据损坏等情况。

数据库管理

创建数据库

CREATE DATABASE [IF NOT EXISTS] db_name [KEEP keep] [DAYS days] [UPDATE 1];

说明:

  1. KEEP是该数据库的数据保留多长天数,缺省是3650天(10年),数据库会自动删除超过时限的数据;
  2. UPDATE 标志数据库支持更新相同时间戳数据;
  3. 数据库名最大长度为33;
  4. 一条SQL 语句的最大长度为65480个字符;
  5. 数据库还有更多与存储相关的配置参数,请参见 服务端配置 章节。
  • 显示系统当前参数

    SHOW VARIABLES;
    
  • 使用数据库

    USE db_name;
    

    使用/切换数据库(在 RESTful 连接方式下无效)。

  • 删除数据库

    DROP DATABASE [IF EXISTS] db_name;
    

    删除数据库。指定 Database 所包含的全部数据表将被删除,谨慎使用!

  • 修改数据库参数

    ALTER DATABASE db_name COMP 2;
    

    COMP 参数是指修改数据库文件压缩标志位,缺省值为 2,取值范围为 [0, 2]。0 表示不压缩,1 表示一阶段压缩,2 表示两阶段压缩。

    ALTER DATABASE db_name REPLICA 2;
    

    REPLICA 参数是指修改数据库副本数,取值范围 [1, 3]。在集群中使用,副本数必须小于或等于 DNODE 的数目。

    ALTER DATABASE db_name KEEP 365;
    

    KEEP 参数是指修改数据文件保存的天数,缺省值为 3650,取值范围 [days, 365000],必须大于或等于 days 参数值。

    ALTER DATABASE db_name QUORUM 2;
    

    QUORUM 参数是指数据写入成功所需要的确认数,取值范围 [1, 2]。对于异步复制,quorum 设为 1,具有 master 角色的虚拟节点自己确认即可。对于同步复制,需要至少大于等于 2。原则上,Quorum >= 1 并且 Quorum <= replica(副本数),这个参数在启动一个同步模块实例时需要提供。

    ALTER DATABASE db_name BLOCKS 100;
    

    BLOCKS 参数是每个 VNODE (TSDB) 中有多少 cache 大小的内存块,因此一个 VNODE 的用的内存大小粗略为(cache * blocks)。取值范围 [3, 1000]。

    ALTER DATABASE db_name CACHELAST 0;
    

    CACHELAST 参数控制是否在内存中缓存子表的最近数据。缺省值为 0,取值范围 [0, 1, 2, 3]。其中 0 表示不缓存,1 表示缓存子表最近一行数据,2 表示缓存子表每一列的最近的非 NULL 值,3 表示同时打开缓存最近行和列功能。(从 2.0.11.0 版本开始支持参数值 [0, 1],从 2.1.2.0 版本开始支持参数值 [0, 1, 2, 3]。) 说明:缓存最近行,将显著改善 LAST_ROW 函数的性能表现;缓存每列的最近非 NULL 值,将显著改善无特殊影响(WHERE、ORDER BY、GROUP BY、INTERVAL)下的 LAST 函数的性能表现。

    Tips: 以上所有参数修改后都可以用show databases来确认是否修改成功。另外,从 2.1.3.0 版本开始,修改这些参数后无需重启服务器即可生效。

  • 显示系统所有数据库

    SHOW DATABASES;
    
  • 显示一个数据库的创建语句

    SHOW CREATE DATABASE db_name;
    

表管理

创建表

CREATE TABLE [IF NOT EXISTS] tb_name (timestamp_field_name TIMESTAMP, field1_name data_type1 [, field2_name data_type2 ...]);

说明:

  1. 表的第一个字段必须是 TIMESTAMP,并且系统自动将其设为主键;
  2. 表名最大长度为 192;
  3. 表的每行长度不能超过 16k 个字符;(注意:每个 BINARY/NCHAR 类型的列还会额外占用 2 个字节的存储位置)
  4. 子表名只能由字母、数字和下划线组成,且不能以数字开头
  5. 使用数据类型 binary 或 nchar,需指定其最长的字节数,如 binary(20),表示 20 字节;
  • 以超级表为模板创建数据表

    CREATE TABLE [IF NOT EXISTS] tb_name USING stb_name TAGS (tag_value1, ...);
    

    简化创建过程不用手写表结构了

  • 以超级表为模板创建数据表,并指定具体的 TAGS 列

    CREATE TABLE [IF NOT EXISTS] tb_name USING stb_name (tag_name1, ...) TAGS (tag_value1, ...);
    
  • 批量创建数据表

    CREATE TABLE [IF NOT EXISTS] tb_name1 USING stb_name TAGS (tag_value1, ...) [IF NOT EXISTS] tb_name2 USING stb_name TAGS (tag_value2, ...) ...;
    
  • 删除数据表

    DROP TABLE [IF EXISTS] tb_name;
    
  • 显示当前数据库下的所有数据表信息

    SHOW TABLES [LIKE tb_name_wildcar];
    

    通配符匹配:1)'%'(百分号)匹配0到任意个字符;2)'_'下划线匹配单个任意字符。

  • 显示一个数据表的创建语句

    SHOW CREATE TABLE tb_name;
    

    常用于数据库迁移。对一个已经存在的数据表,返回其创建语句;在另一个集群中执行该语句,就能得到一个结构完全相同的数据表。

  • 在线修改显示字符宽度

    SET MAX_BINARY_DISPLAY_WIDTH <nn>;
    

    如显示的内容后面以...结尾时,表示该内容已被截断,可通过本命令修改显示字符宽度以显示完整的内容。

  • 获取表的结构信息

    DESCRIBE tb_name;
    
  • 表增加列

    ALTER TABLE tb_name ADD COLUMN field_name data_type;
    

    说明:

    1. 列的最大个数为1024,最小个数为2;
    2. 列名最大长度为64。
  • 表删除列

    ALTER TABLE tb_name DROP COLUMN field_name; 
    

    如果表是通过超级表创建,更改表结构的操作只能对超级表进行。同时针对超级表的结构更改对所有通过该结构创建的表生效。对于不是通过超级表创建的表,可以直接修改表结构。(之前我们可以使用超级表模板生产超级表,这些表就会受影响)

  • 表修改列宽。(2.1.3.0 版本新增)

    ALTER TABLE tb_name MODIFY COLUMN field_name data_type(length); 
    

超级表STable管理

  • 创建超级表

    CREATE STABLE [IF NOT EXISTS] stb_name (timestamp_field_name TIMESTAMP, field1_name data_type1 [, field2_name data_type2 ...]) TAGS (tag1_name tag_type1, tag2_name tag_type2 [, tag3_name tag_type3]);
    

    创建 STable,与创建表的 SQL 语法相似,但需要指定 TAGS 字段的名称和类型

    说明:

    \1) TAGS 列的数据类型不能是 timestamp 类型;(从 2.1.3.0 版本开始,TAGS 列中支持使用 timestamp 类型,但需注意在 TAGS 中的 timestamp 列写入数据时需要提供给定值,而暂不支持四则运算,例如 NOW + 10s 这类表达式)

    \2) TAGS 列名不能与其他列名相同;

    \3) TAGS 列名不能为预留关键字(参见:参数限制与保留关键字 章节);

    \4) TAGS 最多允许 128 个,至少 1 个,总长度不超过 16 KB。

  • 删除超级表

    DROP STABLE [IF EXISTS] stb_name;
    

    删除 STable 会自动删除通过 STable 创建的子表。

  • 显示当前数据库下的所有超级表信息

    SHOW STABLES [LIKE tb_name_wildcard];
    

    查看数据库内全部 STable,及其相关信息,包括 STable 的名称、创建时间、列数量、标签(TAG)数量、通过该 STable 建表的数量。

  • 显示一个超级表的创建语句

    SHOW CREATE STABLE stb_name;
    

    常用于数据库迁移。对一个已经存在的超级表,返回其创建语句;在另一个集群中执行该语句,就能得到一个结构完全相同的超级表。

  • 获取超级表的结构信息

    DESCRIBE stb_name;
    
  • 超级表增加列

    ALTER STABLE stb_name ADD COLUMN field_name data_type;
    
  • 超级表删除列

    ALTER STABLE stb_name DROP COLUMN field_name; 
    
  • 超级表修改列宽(2.1.3.0 版本新增)

    ALTER STABLE stb_name MODIFY COLUMN field_name data_type(length); 
    

超级表 STable 中 TAG 管理

  • 添加标签

    ALTER STABLE stb_name ADD TAG new_tag_name tag_type;
    
  • 删除标签

    ALTER STABLE stb_name DROP TAG tag_name;
    
  • 修改标签名

    ALTER STABLE stb_name CHANGE TAG old_tag_name new_tag_name;
    
  • 修改标签列宽度(2.1.3.0 版本新增)

    ALTER STABLE stb_name MODIFY TAG tag_name data_type(length); 
    
  • 修改子表标签值

    ALTER TABLE tb_name SET TAG tag_name=new_tag_value;
    

输入写入

INSERT INTO
    tb_name
        [USING stb_name [(tag1_name, ...)] TAGS (tag1_value, ...)]
        [(field1_name, ...)]
        VALUES (field1_value, ...) [(field1_value2, ...) ...] | FILE csv_file_path
    [tb2_name        [USING stb_name [(tag1_name, ...)] TAGS (tag1_value, ...)]
        [(field1_name, ...)]
        VALUES (field1_value, ...) [(field1_value2, ...) ...] | FILE csv_file_path
    ...];

例子:

  • 插入一条或多条记录 指定已经创建好的数据子表的表名,并通过 VALUES 关键字提供一行或多行数据,即可向数据库写入这些数据。例如,执行如下语句可以写入一行记录:
INSERT INTO d1001 VALUES (NOW, 10.2, 219, 0.32);

或者,可以通过如下语句写入两行记录:

INSERT INTO d1001 VALUES ('2021-07-13 14:06:32.272', 10.2, 219, 0.32) (1626164208000, 10.15, 217, 0.33);

注意: 1)在第二个例子中,两行记录的首列时间戳使用了不同格式的写法。其中字符串格式的时间戳写法不受所在 DATABASE 的时间精度设置影响;而长整形格式的时间戳写法会受到所在 DATABASE 的时间精度设置影响——例子中的时间戳在毫秒精度下可以写作 1626164208000,而如果是在微秒精度设置下就需要写为 1626164208000000,纳秒精度设置下需要写为 1626164208000000000。 2)**在使用“插入多条记录”方式写入数据时,不能把第一列的时间戳取值都设为 NOW,否则会导致语句中的多条记录使用相同的时间戳,于是就可能出现相互覆盖以致这些数据行无法全部被正确保存。其原因在于,NOW 函数在执行中会被解析为所在 SQL 语句的实际执行时间,出现在同一语句中的多个 NOW 标记也就会被替换为完全相同的时间戳取值。 **3)允许插入的最老记录的时间戳,是相对于当前服务器时间,减去配置的 keep 值(数据保留的天数);允许插入的最新记录的时间戳,是相对于当前服务器时间,加上配置的 days 值(数据文件存储数据的时间跨度,单位为天)。keep 和 days 都是可以在创建数据库时指定的,缺省值分别是 3650 天和 10 天。

  • 插入记录,数据对应到指定的列

    INSERT INTO d1001 (ts, current, phase) VALUES ('2021-07-13 14:06:33.196', 10.27, 0.31);
    
  • 向多个表插入记录

    INSERT INTO d1001 VALUES ('2021-07-13 14:06:34.630', 10.2, 219, 0.32) ('2021-07-13 14:06:35.779', 10.15, 217, 0.33)
                d1002 (ts, current, phase) VALUES ('2021-07-13 14:06:34.255', 10.27, 0.31);
    
  • 插入记录时自动建表

    INSERT INTO d21001 USING meters TAGS ('Beijing.Chaoyang', 2) VALUES ('2021-07-13 14:06:32.272', 10.2, 219, 0.32);
    

    也可以在自动建表时,只是指定部分 TAGS 列的取值,未被指定的 TAGS 列将置为 NULL。例如:

    INSERT INTO d21001 USING meters (groupId) TAGS (2) VALUES ('2021-07-13 14:06:33.196', 10.15, 217, 0.33);
    

    自动建表语法也支持在一条语句中向多个表插入记录。例如:

    INSERT INTO 
    d21001 USING meters TAGS ('Beijing.Chaoyang', 2) VALUES ('2021-07-13 14:06:34.630', 10.2, 219, 0.32) ('2021-07-13 14:06:35.779', 10.15, 217, 0.33)            
    d21002 USING meters (groupId) TAGS (2) VALUES ('2021-07-13 14:06:34.255', 10.15, 217, 0.33)            d21003 USING meters (groupId) TAGS (2) (ts, current, phase) VALUES ('2021-07-13 14:06:34.255', 10.27, 0.31);
    
  • 插入来自文件的数据记录

    INSERT INTO d1001 FILE '/tmp/csvfile.csv';
    
  • 插入来自文件的数据记录,并自动建表

    INSERT INTO d21001 USING meters TAGS ('Beijing.Chaoyang', 2) FILE '/tmp/csvfile.csv';
    
    //向多个表以自动建表的方式插入记录
    INSERT INTO d21001 USING meters TAGS ('Beijing.Chaoyang', 2) FILE '/tmp/csvfile_21001.csv'
                d21002 USING meters (groupId) TAGS (2) FILE '/tmp/csvfile_21002.csv';
    

数据查询

  • 查询语法

    SELECT select_expr [, select_expr ...]
        FROM {tb_name_list}
        [WHERE where_condition]
        [SESSION(ts_col, tol_val)]
        [STATE_WINDOW(col)]
        [INTERVAL(interval_val [, interval_offset]) [SLIDING sliding_val]]
        [FILL(fill_mod_and_val)]
        [GROUP BY col_list]
        [ORDER BY col_list { DESC | ASC }]
        [SLIMIT limit_val [SOFFSET offset_val]]
        [LIMIT limit_val [OFFSET offset_val]]
        [>> export_file];
    

    在JOIN查询中,带前缀的和不带前缀返回的结果有差别, *返回全部表的所有列数据(不包含标签),带前缀的通配符,则只返回该表的列数据。

    taos> SELECT * FROM d1001, d1003 WHERE d1001.ts=d1003.ts;
               ts            | current |   voltage   |    phase     |           ts            | current |   voltage   |    phase     |
    ==================================================================================================================================
     2018-10-03 14:38:05.000 | 10.30000|         219 |      0.31000 | 2018-10-03 14:38:05.000 | 10.80000|         223 |      0.29000 |
    Query OK, 1 row(s) in set (0.017385s)
    
    taos> SELECT d1001.* FROM d1001,d1003 WHERE d1001.ts = d1003.ts;
               ts            |       current        |   voltage   |        phase         |
    ======================================================================================
     2018-10-03 14:38:05.000 |             10.30000 |         219 |              0.31000 |
    Query OK, 1 row(s) in set (0.020443s)
    
  • 标签列

    taos> SELECT location, groupid, current FROM d1001 LIMIT 2;
                location            |   groupid   |       current        |
    ======================================================================
     Beijing.Chaoyang               |           2 |             10.30000 |
     Beijing.Chaoyang               |           2 |             12.60000 |
    Query OK, 2 row(s) in set (0.003112s)
    
  • 获取标签列的去重取值

    SELECT DISTINCT tag_name FROM stb_name;
    
  • 结果集列名

    taos> SELECT ts, ts AS primary_key_ts FROM d1001;
               ts            |     primary_key_ts      |
    ====================================================
     2018-10-03 14:38:05.000 | 2018-10-03 14:38:05.000 |
     2018-10-03 14:38:15.000 | 2018-10-03 14:38:15.000 |
     2018-10-03 14:38:16.800 | 2018-10-03 14:38:16.800 |
    Query OK, 3 row(s) in set (0.001191s)
    
  • 小技巧

    获取一个超级表所有的子表名及相关的标签信息:

    SELECT TBNAME, location FROM meters;
    

    统计超级表下辖子表数量:

    SELECT COUNT(TBNAME) FROM meters;
    

    以上两个查询均只支持在WHERE条件子句中添加针对标签(TAGS)的过滤条件。例如:

    taos> SELECT TBNAME, location FROM meters;             tbname             |            location            |================================================================== d1004                          | Beijing.Haidian                | d1003                          | Beijing.Haidian                | d1002                          | Beijing.Chaoyang               | d1001                          | Beijing.Chaoyang               |Query OK, 4 row(s) in set (0.000881s)taos> SELECT COUNT(tbname) FROM meters WHERE groupId > 2;     count(tbname)     |========================                     2 |Query OK, 1 row(s) in set (0.001091s)
    
    • 可以使用 * 返回所有列,或指定列名。可以对数字列进行四则运算,可以给输出的列取列名。

      • 暂不支持含列名的四则运算表达式用于条件过滤算子(例如,不支持 where a*2>6;,但可以写 where a>6/2;)。
      • 暂不支持含列名的四则运算表达式作为 SQL 函数的应用对象(例如,不支持 select min(2*a) from t;,但可以写 select 2*min(a) from t;)。
    • WHERE 语句可以使用各种逻辑判断来过滤数字值,或使用通配符来过滤字符串。

    • 输出结果缺省按首列时间戳升序排序,但可以指定按降序排序( _c0 指首列时间戳)。使用 ORDER BY 对其他字段进行排序为非法操作。

    • 参数 LIMIT 控制输出条数,OFFSET 指定从第几条开始输出。LIMIT/OFFSET 对结果集的执行顺序在 ORDER BY 之后。且LIMIT 5 OFFSET 2可以简写为LIMIT 2, 5。

      • 在有 GROUP BY 子句的情况下,LIMIT 参数控制的是每个分组中至多允许输出的条数。
    • 参数 SLIMIT 控制由 GROUP BY 指令划分的分组中,至多允许输出几个分组的数据。且 SLIMIT 5 SOFFSET 2 可以简写为 SLIMIT 2, 5

    • 通过 “>>” 输出结果可以导出到指定文件。

  • 支持的条件过滤操作

    OperationNoteApplicable Data Types
    larger thantimestamp and all numeric types
    <smaller thantimestamp and all numeric types
    >=larger than or equal totimestamp and all numeric types
    <=smaller than or equal totimestamp and all numeric types
    =equal toall types
    <>not equal toall types
    between andwithin a certain rangetimestamp and all numeric types
    inmatches any value in a setall types except first column timestamp
    %match with any char sequencesbinary nchar
    _match with a single charbinary nchar
  1. <> 算子也可以写为 != ,请注意,这个算子不能用于数据表第一列的 timestamp 字段。
  2. 同时进行多个字段的范围过滤,需要使用关键词 AND 来连接不同的查询条件,暂不支持 OR 连接的不同列之间的查询过滤条件。
  3. 针对单一字段的过滤,如果是时间过滤条件,则一条语句中只支持设定一个;但针对其他的(普通)列或标签列,则可以使用 OR 关键字进行组合条件的查询过滤。例如: ((value > 20 AND value < 30) OR (value < 12))
  4. 从 2.0.17.0 版本开始,条件过滤开始支持 BETWEEN AND 语法,例如 WHERE col2 BETWEEN 1.5 AND 3.25 表示查询条件为“1.5 ≤ col2 ≤ 3.25”。
  5. 从 2.1.4.0 版本开始,条件过滤开始支持 IN 算子,例如 WHERE city IN ('Beijing', 'Shanghai')。说明:BOOL 类型写作 {true, false}{0, 1} 均可,但不能写作 0、1 之外的整数;FLOAT 和 DOUBLE 类型会受到浮点数精度影响,集合内的值在精度范围内认为和数据行的值完全相等才能匹配成功;TIMESTAMP 类型支持非主键的列。
  • UNION ALL 操作符

    SELECT ...UNION ALL SELECT ...[UNION ALL SELECT ...]
    

    TDengine 支持 UNION ALL 操作符。如果多个 SELECT 子句返回结果集的结构完全相同(列名、列类型、列数、顺序),那么可以通过 UNION ALL 把这些结果集合并到一起且合并过程中是不去重的。

SQL 示例

  • 对于下面的例子,表tb1用以下语句创建:

    CREATE TABLE tb1 (ts TIMESTAMP, col1 INT, col2 FLOAT, col3 BINARY(50));
    
  • 查询tb1刚过去的一个小时的所有记录:

    SELECT * FROM tb1 WHERE ts >= NOW - 1h;
    
  • 查询表tb1从2018-06-01 08:00:00.000 到2018-06-02 08:00:00.000时间范围,并且col3的字符串是'nny'结尾的记录,结果按照时间戳降序:

    SELECT * FROM tb1 WHERE ts > '2018-06-01 08:00:00.000' AND ts <= '2018-06-02 08:00:00.000' AND col3 LIKE '%nny' ORDER BY ts DESC;
    
  • 查询col1与col2的和,并取名complex, 时间大于2018-06-01 08:00:00.000, col2大于1.2,结果输出仅仅10条记录,从第5条开始:

    SELECT (col1 + col2) AS 'complex' FROM tb1 WHERE ts > '2018-06-01 08:00:00.000' AND col2 > 1.2 LIMIT 10 OFFSET 5;
    
  • 查询过去10分钟的记录,col2的值大于3.14,并且将结果输出到文件 /home/testoutpu.csv

    SELECT COUNT(*) FROM tb1 WHERE ts >= NOW - 10m AND col2 > 3.14 >> /home/testoutpu.csv;
    

SQL 函数

聚合函数
  • COUNT

    功能:统计表/超级表中记录行数或某列的非空值个数。

    返回结果数据类型:长整型INT64。

    注意:

    1、可以使用星号( )来替代具体的字段,使用星号( )返回全部记录数量2、

    2、针对同一表的(不包含NULL值)字段查询结果均相同。

    3、如果统计对象是具体的列,则返回该列中非NULL值的记录数量。

    SELECT COUNT([*|field_name]) FROM tb_name [WHERE clause];
    
  • AVG

    功能:统计表/超级表中某列的平均值。

    返回结果数据类型:双精度浮点数Double。

    注意:不能应用在timestamp、binary、nchar、bool字段。

    SELECT AVG(field_name) FROM tb_name [WHERE clause];
    
  • TWA

    功能:时间加权平均函数。统计表中某列在一段时间内的时间加权平均。

    返回结果数据类型:双精度浮点数Double。

    注意:不能应用在timestamp、binary、nchar、bool字段。

    SELECT TWA(field_name) FROM tb_name WHERE clause;
    
  • IRATE

    功能:计算瞬时增长率。使用时间区间中最后两个样本数据来计算瞬时增长速率;如果这两个值呈递减关系,那么只取最后一个数用于计算,而不是使用二者差值。

    返回结果数据类型:双精度浮点数Double。

    注意:不能应用在timestamp、binary、nchar、bool类型字段。

    SELECT IRATE(field_name) FROM tb_name WHERE clause;
    
  • SUM

    功能:统计表/超级表中某列的和。

    返回结果数据类型:双精度浮点数Double和长整型INT64。

    注意:不能应用在timestamp、binary、nchar、bool类型字段。

    SELECT SUM(field_name) FROM tb_name [WHERE clause];
    
  • STDDEV

    功能:统计表中某列的均方差。

    返回结果数据类型:双精度浮点数Double。

    注意:不能应用在timestamp、binary、nchar、bool类型字段。

    SELECT STDDEV(field_name) FROM tb_name [WHERE clause];
    
    taos> SELECT STDDEV(current) FROM d1001;
        stddev(current)      |
    ============================
                1.020892909 |
    Query OK, 1 row(s) in set (0.000915s)
    
  • LEASTSQUARES

    功能:统计表中某列的值是主键(时间戳)的拟合直线方程。start_val是自变量初始值,step_val是自变量的步长值。

    返回结果数据类型:字符串表达式(斜率, 截距)。

    注意:不能应用在timestamp、binary、nchar、bool类型字段。

    SELECT LEASTSQUARES(field_name, start_val, step_val) FROM tb_name [WHERE clause];
    
    taos> SELECT LEASTSQUARES(current, 1, 1) FROM d1001;
                leastsquares(current, 1, 1)             |
    =====================================================
    {slop:1.000000, intercept:9.733334}                 |
    Query OK, 1 row(s) in set (0.000921s)
    
选择函数
  • MIN

    SELECT MIN(field_name) FROM {tb_name | stb_name} [WHERE clause];
    
  • MAX

    SELECT MAX(field_name) FROM { tb_name | stb_name } [WHERE clause];
    
  • FIRST

    功能:统计表/超级表中某列的值最先写入的非NULL值。

    SELECT FIRST(field_name) FROM { tb_name | stb_name } [WHERE clause];
    
  • LAST

    功能:统计表/超级表中某列的值最后写入的非NULL值。

    SELECT LAST(field_name) FROM { tb_name | stb_name } [WHERE clause];
    
  • TOP

    功能: 统计表/超级表中某列的值最大 k 个非 NULL 值。如果多条数据取值一样,全部取用又会超出 k 条限制时,系统会从相同值中随机选取符合要求的数量返回。

    注意:

    1. k值取值范围1≤k≤100;
    2. 系统同时返回该记录关联的时间戳列;
    3. 限制:TOP函数不支持FILL子句。
    SELECT TOP(field_name, K) FROM { tb_name | stb_name } [WHERE clause];
    
    taos> SELECT TOP(current, 3) FROM meters;
            ts            |   top(current, 3)    |
    =================================================
    2018-10-03 14:38:15.000 |             12.60000 |
    2018-10-03 14:38:16.600 |             13.40000 |
    2018-10-03 14:38:16.800 |             12.30000 |
    Query OK, 3 row(s) in set (0.001548s)
    taos> SELECT TOP(current, 2) FROM d1001;
            ts            |   top(current, 2)    |
    =================================================
    2018-10-03 14:38:15.000 |             12.60000 |
    2018-10-03 14:38:16.800 |             12.30000 |
    Query OK, 2 row(s) in set (0.000810s)
    
  • BOTTOM

    功能:统计表/超级表中某列的值最小 k 个非 NULL 值。如果多条数据取值一样,全部取用又会超出 k 条限制时,系统会从相同值中随机选取符合要求的数量返回。

    注意:

    1. k值取值范围1≤k≤100;
    2. 系统同时返回该记录关联的时间戳列;
    3. 限制:TOP函数不支持FILL子句。
    SELECT BOTTOM(field_name, K) FROM { tb_name | stb_name } [WHERE clause];
    
    taos> SELECT BOTTOM(voltage, 2) FROM meters;
            ts            | bottom(voltage, 2) |
    ===============================================
    2018-10-03 14:38:15.000 |                218 |
    2018-10-03 14:38:16.650 |                218 |
    Query OK, 2 row(s) in set (0.001332s)
    taos> SELECT BOTTOM(current, 2) FROM d1001;
            ts            |  bottom(current, 2)  |
    =================================================
    2018-10-03 14:38:05.000 |             10.30000 |
    2018-10-03 14:38:16.800 |             12.30000 |
    Query OK, 2 row(s) in set (0.000793s)
    
  • PERCENTILE

    功能说明:统计表中某列的值百分比分位数。

    注意:P值取值范围0≤P≤100,为0的时候等同于MIN,为100的时候等同于MAX。

    SELECT PERCENTILE(field_name, P) FROM { tb_name } [WHERE clause];
    
  • APERCENTILE

    功能:统计表/超级表中某列的值百分比分位数,与PERCENTILE函数相似,但是返回近似结果。

    SELECT APERCENTILE(field_name, P) FROM { tb_name | stb_name } [WHERE clause];
    
  • LAST_ROW

    返回最后一条数据

    SELECT LAST_ROW(field_name) FROM { tb_name | stb_name };
    
  • INTERP

    功能:返回表/超级表的指定时间截面、指定字段的记录。

    SELECT INTERP(field_name) FROM { tb_name | stb_name } WHERE ts='timestamp' [FILL ({ VALUE | PREV | NULL | LINEAR})];
    
    taos> select interp(*) from meters where ts='2017-7-14 10:42:00.005' fill(prev);
           interp(ts)        | interp(f1)  | interp(f2)  | interp(f3)  |
    ====================================================================
     2017-07-14 10:42:00.005 |           5 |           9 |           6 |
    Query OK, 1 row(s) in set (0.002912s)
    taos> select interp(*) from meters where tbname in ('t1') and ts='2017-7-14 10:42:00.005' fill(prev);
           interp(ts)        | interp(f1)  | interp(f2)  | interp(f3)  |
    ====================================================================
     2017-07-14 10:42:00.005 |           5 |           6 |           7 |
    Query OK, 1 row(s) in set (0.002005s)
    
计算函数
  • DIFF

    功能:统计表中某列的值与前一行对应值的差。

    SELECT DIFF(field_name) FROM tb_name [WHERE clause];
    
  • DERIVATIVE(从 2.1.3.0 版本开始新增此函数)

    功能:统计表中某列数值的单位变化率。其中单位时间区间的长度可以通过 time_interval 参数指定,最小可以是 1 秒(1s);ignore_negative 参数的值可以是 0 或 1,为 1 时表示忽略负值。

    SELECT DERIVATIVE(field_name, time_interval, ignore_negative) FROM tb_name [WHERE clause];
    
  • SPREAD

    功能:统计表/超级表中某列的最大值和最小值之差。

    SELECT SPREAD(field_name) FROM { tb_name | stb_name } [WHERE clause];
    
  • 四则运算

    功能:统计表/超级表中某列或多列间的值加、减、乘、除、取余计算结果。

    SELECT field_name [+|-|*|/|%][Value|field_name] FROM { tb_name | stb_name }  [WHERE clause];
    

按窗口切分聚合

TDengine 支持按时间段等窗口切分方式进行聚合结果查询

SELECT function_list FROM tb_name
  [WHERE where_condition]
  [SESSION(ts_col, tol_val)]
  [STATE_WINDOW(col)]
  [INTERVAL(interval [, offset]) [SLIDING sliding]]
  [FILL({NONE | VALUE | PREV | NULL | LINEAR | NEXT})]
SELECT function_list FROM stb_name
  [WHERE where_condition]
  [SESSION(ts_col, tol_val)]
  [STATE_WINDOW(col)]
  [INTERVAL(interval [, offset]) [SLIDING sliding]]
  [FILL({NONE | VALUE | PREV | NULL | LINEAR | NEXT})]
  [GROUP BY tags]
  • 在聚合查询中,function_list 位置允许使用聚合和选择函数,并要求每个函数仅输出单个结果(例如:COUNT、AVG、SUM、STDDEV、LEASTSQUARES、PERCENTILE、MIN、MAX、FIRST、LAST),而不能使用具有多行输出结果的函数(例如:TOP、BOTTOM、DIFF 以及四则运算)。

  • 查询过滤、聚合等操作按照每个切分窗口为独立的单位执行。聚合查询目前支持三种窗口的划分方式:

    1. 时间窗口:聚合时间段的窗口宽度由关键词 INTERVAL 指定,最短时间间隔 10 毫秒(10a);并且支持偏移 offset(偏移必须小于间隔),也即时间窗口划分与“UTC 时刻 0”相比的偏移量。SLIDING 语句用于指定聚合时间段的前向增量,也即每次窗口向前滑动的时长。当 SLIDING 与 INTERVAL 取值相等的时候,滑动窗口即为翻转窗口。

      • 从 2.1.5.0 版本开始,INTERVAL 语句允许的最短时间间隔调整为 1 微秒(1u),当然如果所查询的 DATABASE 的时间精度设置为毫秒级,那么允许的最短时间间隔为 1 毫秒(1a)。
      • **注意:**用到 INTERVAL 语句时,除非极特殊的情况,都要求把客户端和服务端的 taos.cfg 配置文件中的 timezone 参数配置为相同的取值,以避免时间处理函数频繁进行跨时区转换而导致的严重性能影响。
    2. 状态窗口:使用整数或布尔值来标识产生记录时设备的状态量,产生的记录如果具有相同的状态量取值则归属于同一个状态窗口,数值改变后该窗口关闭。状态量所对应的列作为 STATE_WINDOW 语句的参数来指定。

    3. 会话窗口:时间戳所在的列由 SESSION 语句的 ts_col 参数指定,会话窗口根据相邻两条记录的时间戳差值来确定是否属于同一个会话——如果时间戳差异在 tol_val 以内,则认为记录仍属于同一个窗口;如果时间变化超过 tol_val,则自动开启下一个窗口。

  • WHERE 语句可以指定查询的起止时间和其他过滤条件。

  • FILL 语句指定某一窗口区间数据缺失的情况下的填充模式。填充模式包括以下几种:

    1. 不进行填充:NONE(默认填充模式)。
    2. VALUE 填充:固定值填充,此时需要指定填充的数值。例如:FILL(VALUE, 1.23)。
    3. PREV 填充:使用前一个非 NULL 值填充数据。例如:FILL(PREV)。
    4. NULL 填充:使用 NULL 填充数据。例如:FILL(NULL)。
    5. LINEAR 填充:根据前后距离最近的非 NULL 值做线性插值填充。例如:FILL(LINEAR)。
    6. NEXT 填充:使用下一个非 NULL 值填充数据。例如:FILL(NEXT)。

示例:智能电表的建表语句如下:

CREATE TABLE meters (ts TIMESTAMP, current FLOAT, voltage INT, phase FLOAT) TAGS (location BINARY(64), groupId INT);

针对智能电表采集的数据,以 10 分钟为一个阶段,计算过去 24 小时的电流数据的平均值、最大值、电流的中位数、以及随着时间变化的电流走势拟合直线。如果没有计算值,用前一个非 NULL 值填充。使用的查询语句如下:

SELECT AVG(current), MAX(current), LEASTSQUARES(current, start_val, step_val), PERCENTILE(current, 50) FROM meters  WHERE ts>=NOW-1d  INTERVAL(10m)  FILL(PREV);

TAOS SQL其他约定

GROUP BY的限制

TAOS SQL支持对标签、TBNAME进行GROUP BY操作,也支持普通列进行GROUP BY,前提是:仅限一列且该列的唯一值小于10万个。

JOIN操作的限制

TAOS SQL支持表之间按主键时间戳来join两张表的列,暂不支持两个表之间聚合后的四则运算。

IS NOT NULL与不为空的表达式适用范围

IS NOT NULL支持所有类型的列。不为空的表达式为 <>"",仅对非数值类型的列适用。

高级功能(重点)

连续查询

连续查询是TDengine定期自动执行的查询,采用滑动窗口的方式进行计算,是一种简化的时间驱动的流式计算。TDengine可提供定期自动执行的连续查询,用户可让TDengine推送查询的结果,也可以将结果再写回到TDengine中。

TDengine提供的连续查询与普通流计算中的时间窗口计算具有以下区别:

  • 不同于流计算的实时反馈计算结果,连续查询只在时间窗口关闭以后才开始计算。例如时间周期是1天,那么当天的结果只会在23:59:59以后才会生成。
  • 如果有历史记录写入到已经计算完成的时间区间,连续查询并不会重新进行计算,也不会重新将结果推送给用户。对于写回TDengine的模式,也不会更新已经存在的计算结果。
  • 使用连续查询推送结果的模式,服务端并不缓存客户端计算状态,也不提供Exactly-Once的语意保证。如果用户的应用端崩溃,再次拉起的连续查询将只会从再次拉起的时间开始重新计算最近的一个完整的时间窗口。如果使用写回模式,TDengine可确保数据写回的有效性和连续性。

使用连续查询

可以通过下面这条SQL语句以一分钟为时间窗口、30秒为前向增量统计这些电表的平均电压。

select avg(voltage) from meters interval(1m) sliding(30s);

每次执行这条语句,都会重新计算所有数据。 如果需要每隔30秒执行一次来增量计算最近一分钟的数据,可以把上面的语句改进成下面的样子,每次使用不同的 startTime 并定期执行:

select avg(voltage) from meters where ts > {startTime} interval(1m) sliding(30s);

重点理解)这样做没有问题,但TDengine提供了更简单的方法,只要在最初的查询语句前面加上 create table {tableName} as 就可以了, 例如:

create table avg_vol as select avg(voltage) from meters interval(1m) sliding(30s);

会自动创建一个名为 avg_vol 的新表,然后每隔30秒,TDengine会增量执行 as 后面的 SQL 语句,并将查询结果写入这个表中,用户程序后续只要从 avg_vol 中查询数据即可。 例如:

taos> select * from avg_vol;   

需要注意,查询时间窗口的最小值是10毫秒,没有时间窗口范围的上限。

create table avg_vol as select avg(voltage) from meters where ts > now and ts <= now + 1h interval(1m) sliding(30s);

TDengine还支持用户指定连续查询的起止时间。

管理连续查询

用户可在控制台中通过 show streams 命令来查看系统中全部运行的连续查询,并可以通过 kill stream 命令杀掉对应的连续查询。

数据订阅(Publisher/Subscriber)

基于数据天然的时间序列特性,TDengine的数据写入(insert)与消息系统的数据发布(pub)逻辑上一致,均可视为系统中插入一条带时间戳的新记录。同时,TDengine在内部严格按照数据时间序列单调递增的方式保存数据。本质上来说,TDengine中里每一张表均可视为一个标准的消息队列。

TDengine内嵌支持轻量级的消息订阅与推送服务。使用系统提供的API,用户可使用普通查询语句订阅数据库中的一张或多张表。订阅的逻辑和操作状态的维护均是由客户端完成,客户端定时轮询服务器是否有新的记录到达,有新的记录到达就会将结果反馈到客户。

*TDengine的订阅与推送服务的状态是客户端维持,TDengine服务器并不维持。 *因此如果应用重启,从哪个时间点开始获取最新数据,由应用决定。

TDengine的API中,与订阅相关的主要有以下三个:

taos_subscribe
taos_consume
taos_unsubscribe
Java 使用数据订阅功能

目前 Java 接口没有提供异步订阅模式,但用户程序可以通过创建 TimerTask 等方式达到同样的效果。

创建
TSDBSubscribe sub = ((TSDBConnection)conn).subscribe("topic", "select * from meters", false);

subscribe 方法的三个参数含义如下:

  • topic:订阅的主题(即名称),此参数是订阅的唯一标识
  • sql:订阅的查询语句,此语句只能是 select 语句,只应查询原始数据,只能按时间正序查询数据
  • restart:如果订阅已经存在,是重新开始,还是继续之前的订阅

如上面的例子将使用 SQL 语句 select * from meters 创建一个名为 topic 的订阅,如果这个订阅已经存在,将继续之前的查询进度,而不是从头开始消费所有的数据。

消费数据
int total = 0;
while(true) {
    TSDBResultSet rs = sub.consume();
    int count = 0;
    while(rs.next()) {
        count++;
    }
    total += count;
    System.out.printf("%d rows consumed, total %d\n", count, total);
    Thread.sleep(1000);
}

consume 方法返回一个结果集,其中包含从上次 consume 到目前为止的所有新数据。请务必按需选择合理的调用 consume 的频率(如例子中的 Thread.sleep(1000)),否则会给服务端造成不必要的压力。

关闭订阅
sub.close(true);

close 方法关闭一个订阅。如果其参数为 true 表示保留订阅进度信息,后续可以创建同名订阅继续消费数据;如为 false 则不保留订阅进度。

关闭资源
resultSet.close();stmt.close();conn.close();

缓存

参考基本概念中的缓存与持久化。

报警监测(Alert)

在 TDengine 的应用场景中,报警监测是一个常见需求,从概念上说,它要求程序从最近一段时间的数据中筛选出符合一定条件的数据,并基于这些数据根据定义好的公式计算出一个结果,当这个结果符合某个条件且持续一定时间后,以某种形式通知用户。

为了满足用户对报警监测的需求,TDengine 以独立模块的形式提供了这一功能,有关它的安装使用方法,请参考博客 使用 TDengine 进行报警监测

整合SpringBoot和Mybatis

基本与mysql相同除开个别sql语句存在不同。

Java连接器的使用

taos-jdbcdriver 的实现包括 2 种形式: JDBC-JNI 和 JDBC-RESTful(taos-jdbcdriver-2.0.18 开始支持 JDBC-RESTful)。 JDBC-JNI 通过调用客户端 libtaos.so(或 taos.dll )的本地方法实现, JDBC-RESTful 则在内部封装了 RESTful 接口实现。

TDengine 的 JDBC 驱动实现尽可能与关系型数据库驱动保持一致,但时序空间数据库与关系对象型数据库服务的对象和技术特征存在差异,导致 taos-jdbcdriver 与传统的 JDBC driver 也存在一定差异。在使用时需要注意以下几点:

TDengine 目前不支持针对单条数据记录的删除操作。

  1. 目前不支持事务操作。
  2. 目前不支持嵌套查询(nested query)。
  3. 对每个 Connection 的实例,至多只能有一个打开的 ResultSet 实例;如果在 ResultSet 还没关闭的情况下执行了新的查询,taos-jdbcdriver 会自动关闭上一个 ResultSet。

JDBC-JNI和JDBC-RESTful的对比

对比项JDBC-JNIJDBC-RESTful
支持的操作系统linux、windows全平台
是否需要安装 client需要不需要
server 升级后是否需要升级 client需要不需要
写入性能JDBC-RESTful 是 JDBC-JNI 的 50%~90%
查询性能JDBC-RESTful 与 JDBC-JNI 没有差别

目前我们采用JDBC-RESTFUL的形式进行开发

注意:与 JNI 方式不同,RESTful 接口是无状态的,因此 USE db_name 指令没有效果,RESTful 下所有对表名、超级表名的引用都需要指定数据库名前缀。

配置数据源

注意事项

  • TDengine v1.6.4.1 版本开始提供了一个专门用于心跳检测的函数 select server_status(),所以在使用连接池时推荐使用 select server_status() 进行 Validation Query。
spring.datasource.driver-class-name=com.taosdata.jdbc.rs.RestfulDriver
spring.datasource.url=jdbc:TAOS-RS://192.168.1.203:6041/test?timezone=UTC-8&charset=UTF-8&locale=en_US.UTF-8
spring.datasource.username=root
spring.datasource.password=taosdata

spring.datasource.druid.initial-size=5
spring.datasource.druid.min-idle=5
spring.datasource.druid.max-active=5
spring.datasource.druid.max-wait=30000
spring.datasource.druid.validation-query=select server_status();

主要功能

创建数据库和表

<!-- weatherMapper.xml -->
 <update id="createDB" >
        create database if not exists test;
    </update>

    <update id="createTable" >
        create table if not exists test.weather(ts timestamp, temperature int, humidity float);
    </update>

插入单条记录

<!-- weatherMapper.xml -->
    <insert id="insert" parameterType="Weather" >
        insert into test.weather (ts, temperature, humidity) values (now, #{temperature,jdbcType=INTEGER}, #{humidity,jdbcType=FLOAT})
    </insert>

分页查询

<!-- weatherMapper.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="WeatherMapper">

    <resultMap id="BaseResultMap" type="Weather">
        <id column="ts" jdbcType="TIMESTAMP" property="ts" />
        <result column="temperature" jdbcType="INTEGER" property="temperature" />
        <result column="humidity" jdbcType="FLOAT" property="humidity" />
    </resultMap>

    <sql id="Base_Column_List">
        ts, temperature, humidity
    </sql>

    <select id="select" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from test.weather
        order by ts desc
        <if test="limit != null">
            limit #{limit,jdbcType=BIGINT}
        </if>
        <if test="offset != null">
            offset #{offset,jdbcType=BIGINT}
        </if>
    </select>
</mapper>

mybatis xml详情

<mapper namespace="com.taosdata.example.springbootdemo.dao.WeatherMapper">

    <resultMap id="BaseResultMap" type="com.taosdata.example.springbootdemo.domain.Weather">
        <id column="ts" jdbcType="TIMESTAMP" property="ts"/>
        <result column="temperature" jdbcType="FLOAT" property="temperature"/>
        <result column="humidity" jdbcType="FLOAT" property="humidity"/>
    </resultMap>

    <update id="dropDB">
        drop database if exists test1
    </update>

    <update id="createDB">
        create database if not exists test1
    </update>

    <update id="createSuperTable">
        create stable if not exists test1.weather(ts timestamp, temperature float, humidity float) tags(location nchar(64), groupId int)
    </update>

    <update id="createTable" parameterType="com.taosdata.example.springbootdemo.domain.Weather">
        create table if not exists test1.t#{weather.groupId} using test1.weather TAGS (#{weather.location}, #{weather.groupId})
    </update>

    <select id="select" resultMap="BaseResultMap">
        select * from test1.weather order by ts desc
        <if test="limit != null">
            limit #{limit,jdbcType=BIGINT}
        </if>
        <if test="offset != null">
            offset #{offset,jdbcType=BIGINT}
        </if>
    </select>

    <insert id="insert" parameterType="com.taosdata.example.springbootdemo.domain.Weather">
        insert into test1.t#{groupId} (ts, temperature, humidity) values (now, #{temperature}, #{humidity})
    </insert>

    <select id="getSubTables" resultType="String">
        select tbname from test1.weather
    </select>

    <select id="count" resultType="int">
        select count(*) from test1.weather
    </select>

    <resultMap id="avgResultSet" type="com.taosdata.example.springbootdemo.domain.Weather">
        <id column="ts" jdbcType="TIMESTAMP" property="ts" />
        <result column="avg(temperature)" jdbcType="FLOAT" property="temperature" />
        <result column="avg(humidity)" jdbcType="FLOAT" property="humidity" />
    </resultMap>

    <select id="avg" resultMap="avgResultSet">
        select avg(temperature), avg(humidity)from test1.weather interval(1m)
    </select>

如何使用idea内置数据库管理工具连接TDengine

选择Driver and Data Source

点击Drivers,并点击下方”+“号

点击下方出现的User Driver,然后点击右边的"+"号

选择Custom JARS

然后选择驱动(驱动可以从demo的依赖里面复制一份),选择com.taosdata.jdbc.TSDBDriver另外一个com.taosdata.jdbc.rs.Restfulriver暂时发现有bug报错。选用非restful方式驱动需要下载客户端,记得选择对应版本,目前选择TDengine-client-2.1.6.0-beta-Windows-x64.exe即可。然后将下载文件中Driver文件夹下的taos.dll放到c:\windows\system32\下即可。

选择好之后回到DataSource页面,填写user,password,url(roottaosdatajdbc:TAOS://192.168.1.203:6030/test?timezone=Asia/Shanghai-8&charset=UTF-8&locale=en_US.UTF-8)

随后连接即可

使用TDengineGUI进行可视化管理

下载地址,下载后打开点击新建连接

输入参数(名称随意,192.168.1.203,6041,root,taosdata),点击确定即可

进入后 超级表选项卡为超级表管理页面,表选项卡为数据表管理页面,控制台选项卡可以输入sql语句,数据库连接属性仅为展示连接属性

切换数据库点击右下方切换,然后选择对应数据库即可