TDengine是涛思数据专为物联网、车联网、工业互联网、IT运维等设计和优化的大数据平台,核心的时序数据库在处理时序数据上有着十分优异的性能。
一般来说,时序数据就是带有时间序列属性的数据。在处理时序数据时,TDengine有着自己独特的方式。但是如果你没有正确理解TDengine在写入和查询上的行为,极可能会因为配置了错误的时区(timezone),而导致写入和查询的时间出现错位。
下面是一个真实用户的例子:
从上图中可以看到,用户执行的一条SQL写入了“2021-07-23 07:04:00.000"这个时刻的数据,可是在不同的客户端中,查询出的结果却相差了13个小时。
今天希望通过这篇文章,将日期时间、时间戳以及TDengine在写入和查询时处理时区的行为等描述清楚,并给出如何设置timezone参数的意见,供大家参考。
在开始之前,你需要先了解以下这三点
本文所用相关概念
本地日期时间:表示当地的日期时间。12:00是中午吃饭的时间,8:00是早上上班的时间,这是人类习惯的一种表示时间的方式,是不带时区信息的日期和时间,可以当成一个String。例如:2021-07-21 12:00:00.000,表示2021年7月21日正午,时间精度以毫秒记,这个日期时间的表示方法,不带任何时区信息。
时区:地理概念,按照UTC/格林威治时区,把地球划分成向东和向西各12个时区,其中东12区和西12区是一个区。时区可以通过'Asia/shanghai'这样的'地区/城市'的方式表示,也可以用UTC偏移的方式表示。例如:UTC+8,代表东八区,当协调世界时(UTC)时间为凌晨2点的时候,当地的时间为2+8点,即早上10点。
RFC 3339:一种表示日期时间的标准格式。RFC 3339是带时区信息的格式,即包含日期时间信息,也有时区信息。例如,以下两个时间在地球上是同一时刻:
2019-10-12T07:20:50+00:00,这个表示2019年10月12日,上午7点20分50秒(UTC+0时区), 2019-10-12T15:20:50+08:00,这个表示2019年10月12日,下午3点20分50秒(UTC+8时区)。 时间戳:是机器存储和计算时间的方式。以Unix元年(UTC时区1970年1月1日0点0分0秒)开始经过的秒数计算,不同精度的计时方式,可以有不同的时间戳。例如:0,表示UTC时区1970年1月1日凌晨的时间。
TDengine如何处理日期时间?
写入
这里,介绍一个小技巧:使用-r参数启动taos shell时,timestamp类型的数据,将会以时间戳(long值)的形式显示。
# taos -r
taos> drop table test.weather;
Query OK, of row(s) in database (0.004202s)
taos> create table test.weather(ts timestamp, f1 float) ;
Query OK, of row(s) in database (0.012690s)
taos> insert into test.weather values('1970-01-01T08:00:00.000+08:00',22.00) ;
Query OK, 1 of 1 row(s) in database (0.002363s)
taos> select * from test.weather;
ts | f1 |
========================================
| 22.00000 |
Query OK, 1 row(s) in set (0.001476s)
可以看到,1970-01-01T08:00:00.000+08:00,代表UTC+8时区1970年1月1日上午8:00,这正好对应UTC时区的凌晨,所以在timestamp是0。
第2种情况——在insert语句中使用yyyy-MM-dd hh:mm:ss格式的时间字符串,不含时区信息。这时,taos客户端会采用当前timezone信息,将字符串转化成timestamp。
例如:
taos> show variables;
name | value |
============================================================
timezone | (CST, +0800) |
taos> insert into test.weather(ts, f1) values('1970-01-01 00:00:00.000', 22.00);
Query OK, 1 of 1 row(s) in database (0.001290s)
taos> select * from test.weather;
ts | f1 |
========================================
-28800000 | 22.00000 |
Query OK, 1 row(s) in set (0.002220s)
可以看到,insert语句使用了配置文件中的时区信息,和insert语句中的日期时间信息,即“1970-01-01 00:00:00+08:00”,这个值在时间戳中正好代表-28800000。
由此可见,在TDengine中,时间原点是国际通用的Unix元年(UTC时区1970年1月1日凌晨)。
查询
# 在taos.cfg内配置timezone
# cat /etc/taos/taos.cfg | grep timezone
timezone UTC+
# 在shell中查询timezone
taos> show variables;
name | value |
============================================================
timezone | (CST, +0800) |
# taos -s "select * from test.weather" -r
Welcome to the TDengine shell from Linux, Client Version:2.0.20.11
Copyright (c) 2020 by TAOS Data, Inc. All rights reserved.
taos> select * from test.weather
ts | f1 |
========================================
-28800000 | 22.00000 |
Query OK, 1 row(s) in set (0.002564s)
# taos -s "select * from test.weather"
Welcome to the TDengine shell from Linux, Client Version:2.0.20.11
Copyright (c) 2020 by TAOS Data, Inc. All rights reserved.
taos> select * from test.weather;
ts | f1 |
=================================================
1969-12-31 16:00:00.000 | 22.00000 |
Query OK, 1 row(s) in set (0.002306s)
可以看到,select语句在查询时,依然存在着从ts转换为一个string串的情况,Tdengine会将ts转换成当前taos client中的时区。
Timezone配置为UTC-8
# date --help
用法:date [选项]... [+格式]
或:date [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]
Display the current time in the given FORMAT, or set the system date.
%z +hhmm 数字时区(例如,-0400)
%:z +hh:mm 数字时区(例如,-04:00)
%Z 按字母表排序的时区缩写 (例如,EDT)
Examples:
Show the time on the west coast of the US (use tzselect(1) to find TZ)
$ TZ='America/Los_Angeles' date
# 使用TZ='UTC-8'查看当前时间
# TZ='UTC-8' date +'%Y-%m-%d %H:%M:%S %Z %z'
2021-08-01 22:31:29 UTC +0800
# 使用TZ='UTC'查看当前时间
# TZ='UTC' date +'%Y-%m-%d %H:%M:%S %Z %z'
2021-08-01 14:31:51 UTC +0000
# 使用TZ='UTC+8'查看当前时间
# TZ='UTC+8' date +'%Y-%m-%d %H:%M:%S %Z %z'
2021-08-01 06:32:06 UTC -0800
可见,在POSIX标准中,UTC-8代表东八区,UTC+8代表西八区。这里与地理上表示时区的习惯是不一致的。在taos.cfg中,TDengine使用的是POSIX Timezone标准。
在JDBC中设置Timezone
在使用JDBC Connector连接TDengine时,可以通过3个途径设置timezone参数,分别为:url、properties和taos.cfg配置文件。
// url
Connection conn = DriverManager.getConnection("jdbc:TAOS://taosdemo.com:6030/test?timezone=UTC-8", "root", "taosdata");
// properties
Properties connProps = new Properties();
connProps.setProperty(TSDBDriver.PROPERTY_KEY_USER, "root");
connProps.setProperty(TSDBDriver.PROPERTY_KEY_PASSWORD, "taosdata");
connProps.setProperty(TSDBDriver.PROPERTY_KEY_TIME_ZONE, "UTC-8");
Connection conn = DriverManager.getConnection("jdbc:TAOS://taosdemo.com:6030/test", connProps);
// 当url和properties中都没有制定timezone的情况下,会使用本地配置文件taos.cfg中timezone的配置参数
总结
INSERT INTO n802344030600001_w21003 USING mnt_factor_item_data TAGS ("N802344030600001", 'w21003') VALUES ('2021-7-23 07:04:00:000',3, 999,'N802344030600002','w21003',19,'COD','mg/L',4,2,'大空港片区',1,'龙翔北路监测控制站',1,'龙翔北路水监测设备',3,'龙翔北路监测终端');
参考文献: