时序数据库全称为时间序列数据库。时间序列数据库指主要用于处理带时间标签(按照时间的顺序变化,即时间序列化)的数据,带时间标签的数据也称为时间序列数据。
时间序列数据主要由电力行业、化工行业、气象行业、地理信息等各类型实时监测、检查与分析设备所采集、产生的数据,这些工业数据的典型特点是:产生频率快(每一个监测点一秒钟内可产生多条数据)、严重依赖于采集时间(每一条数据均要求对应唯一的时间)、测点多信息量大(常规的实时监测系统均有成千上万的监测点,监测点每秒钟都产生数据,每天产生几十GB的数据量)。
TimescaleDB是一个时间序列数据库,建立在 PG之上,Halo数据库是100%兼容PG数据库的,所以TimescaleDB也可以运行在Halo数据库之上。然而,不仅如此,TimescaleDB还是时间序列的关系数据库。使用 TimescaleDB 的开发人员将受益于专门构建的时间序列数据库以及经典的关系数据库,所有这些都有完整的SQL支持。下面就介绍TimescaleDB在CentOS 7环境下的Halo数据库上的安装与使用。
一、升级CMake
因TimescaleDB的编译安装需要较新版本的CMake,所以我们需要对原CentOS 7环境下的CMake进行升级更新。升级方法如下。
1.移除老版本CMake版本并安装依赖包
yum remove cmake -yyum install -y gcc gcc-c++ make automake openssl openssl-devel
2.下载cmake-3.19.2.tar.gz安装包并解压
CMake官网地址 https://cmake.org/download/,我们可以去这里下载新包。
wget https://cmake.org/files/v3.19/cmake-3.19.2-Linux-x86_64.tar.gztar -zxf cmake-3.19.2-Linux-x86_64.tar.gz
3.安装CMake
cmake-3.19.2-Linux-x86_64cp -r share/* /usr/share/cp bin/* /usr/bin/
4.查看编译后的CMake版本并创建连接
cmake --versionln -s /usr/local/bin/cmake /usr/bin/
二、安装TimescaleDB
1.下载
首先下载TimescaleDB压缩包:

解压:
unzip timescaledb-main.zip2.编译安装
mv timescaledb-main /data/chown -R halo.halo /data/timescaledb-mainsu - halocd /data/timescaledb-main
修改配置文件:
vim CMakeLists.txt:set number
修改以下两处内容:
增加Halo数据库程序路径:/u01/app/halo/product/dbms/14/bin/

Halo数据库版本:

编译:
./bootstrap -DUSE_OPENSSL=0
cd ./build && make
make install
3.修改数据库配置文件
查看配置文件路径
psql -c "SHOW config_file;"添加timescaledb配置
vim /data/halo/postgresql.confshared_preload_libraries = 'timescaledb'
重启数据库
pg_ctl restart4.创建扩展
创建测试数据库
CREATE database timesdb;切换数据库
\c timesdb创建timescaledb扩展
CREATE EXTENSION IF NOT EXISTS timescaledb;查看插件
\dx这样TimescaleDB就可以使用了。

三、使用TimescaleDB
超表(Hypertable)是具有特殊功能的PG表,可以很容易地处理时间序列的数据。与它们的交互就与普通PG表交互一样,但在后端,超表会自动按时间将数据划分为块。
在TimescaleDB中,超表与普通PG表可以一起存在。超表用来存储时序数据,这样可以提高插入和查询的性能,而且可以访问一些有用的时间序列特性。普通PG表用来存储其它关系型数据。
本节中将对超表的使用进行详细的介绍。
1.创建超表Hypertables
创建一个PG普通表:
CREATE TABLE timescaletb (time TIMESTAMPTZ NOT NULL,location TEXT NOT NULL,device TEXT NOT NULL,temperature DOUBLE PRECISION NULL,humidity DOUBLE PRECISION NULL);

然后使用create_hypertable函数将普通PG表转化成Hypertable超表:
SELECT create_hypertable('timescaletb', 'time'); 
2.超表Hypertable的增、删、改、查
使用INSERT命令向Hypertable表中插入数据
INSERT INTO timescaletb(time, location, device, temperature, humidity)SELECT now(), to_char(i, 'FM0000'), to_char(i, 'FM00000'), random()*i, random()*i FROM generate_series(1,10000) i;

使用SELECT命令查询Hypertable表中数据
SELECT * FROM timescaletb ORDER BY time DESC LIMIT 10; 
使用UPDATE命令更新Hypertable表数据
先查询出要修改的数据:
SELECT * FROM timescaletb WHERE time = '2024-04-16 15:21:27.971959+08' AND location = '0001';
更新并检查数据:
UPDATE timescaletb SET temperature = 70.2, humidity = 50.0 WHERE time = '2024-04-16 15:21:27.971959+08' AND location = '0001';SELECT * FROM timescaletb WHERE time = '2024-04-16 15:21:27.971959+08' AND location = '0001';

也可以修改指定范围内的多行记录
例如:修改时间 2024-04-16 15:21:27 到 2024-04-16 15:21:28 这1秒内的数据
SELECT * FROM timescaletb ORDER BY time DESC LIMIT 10; 
UPDATE timescaletb SET temperature = temperature + 0.1WHERE time >= '2024-04-16 15:21:27.971959+08' AND time < '2024-04-16 15:21:28.971959+08';SELECT * FROM timescaletb ORDER BY time DESC LIMIT 10;

使用Upsert命令插入新数据或更新已存在的数据
Upsert只能在有唯一索引或唯一约束的表中生效,可以使用ALTER TABLE … ADD CONSTRAINT … UNIQUE语句为已经存在的Hypertable创建唯一约束:
ALTER TABLE timescaletb ADD CONSTRAINT timescaletb_time_location UNIQUE (time, location);\d timescaletb

使用 INSERT INTO … VALUES … ON CONFLICT … DO UPDATE 语句实现插入新数据或更新已存在的数据
SELECT * FROM timescaletb WHERE time = '2024-04-16 15:21:27.971959+08' AND location = '0001';
INSERT INTO timescaletb VALUES ('2024-04-16 15:21:27.971959+08', '0001', '00001', 70.2, 50.1) ON CONFLICT (time, location) DO UPDATE SET temperature = excluded.temperature, humidity = excluded.humidity;SELECT * FROM timescaletb WHERE time = '2024-04-16 15:21:27.971959+08' AND location = '0001';

使用INSERT INTO … VALUES … ON CONFLICT DO NOTHING语句执行插入操作,如果记录已存在则直接跳过该操作
INSERT INTO timescaletb VALUES (NOW(), 'new', '00001', 70.1, 50.0) ON CONFLICT DO NOTHING;SELECT * FROM timescaletb WHERE location = 'new';

如果已存在则跳过插入:
INSERT INTO timescaletb VALUES ('2024-04-16 15:21:27.971959+08', '0001', '00001', 71, 50.1) ON CONFLICT DO NOTHING;
DELETE 删除数据
删除数据的语句与标准 SQL 一致
SELECT * FROM timescaletb WHERE location = 'new';DELETE FROM timescaletb WHERE location = 'new';SELECT * FROM timescaletb WHERE location = 'new';

3.修改Hypertable超表
可以使用ALTER TABLE相关语句实现Hypertable超表的修改:
增加列
增加列时,如果没有默认值即NULL时,新增操作可以很快完成;是默认值为非空的候,就需要花大量时间填充所有分区中每条记录新增列的值。无法对已经被压缩的Hypertable进行增删列操作,如果要进行该操作需要先解压。
\d timescaletb
ALTER TABLE timescaletb ADD COLUMN test_column DOUBLE PRECISION NULL;\d timescaletb

重命名列
ALTER TABLE timescaletb RENAME COLUMN test_column TO ts_column;\d timescaletb

删除列
ALTER TABLE timescaletb DROP COLUMN ts_column;\d timescaletb

重命名表
ALTER TABLE timescaletb RENAME TO newtimescaletb;\d timescaletb\d newtimescaletb

4.修改Hypertable分区间隔
每个超表都由称为块(chunk)的子表组成。每个块被分配了一定的时间范围,并且只包含该范围内的数据。如果超表也通过空间分区,则每个块也会被分配一个空间值的子集。
普通表转化成Hypertable时可以指定分区时间间隔,关键字是chunk_time_interval。
如果创建Hypertable是没有指定分区间隔的,默认值是7天;可以通过_timescaledb.catalog查看当前设置的分区间隔。
CREATE TABLE timescaletb (time TIMESTAMPTZ NOT NULL,location TEXT NOT NULL,device TEXT NOT NULL,temperature DOUBLE PRECISION NULL,humidity DOUBLE PRECISION NULL);SELECT create_hypertable('timescaletb', 'time');

SELECT h.table_name, c.interval_lengthFROM _timescaledb_catalog.dimension cJOIN _timescaledb_catalog.hypertable hON h.id = c.hypertable_id;

可以看到没有指定 chunk_time_interval 的Hypertable 分区间隔为 604800000000,通过计算 604800000000 / 1000 / 1000 / 60 / 60 / 24 = 7天,interval_length 的单位为微秒(microsecond)。
创建Hypertable 时我们通过 chunk_time_interval 指定分区间隔为 1 天
DROP TABLE timescaletb;CREATE TABLE timescaletb (time TIMESTAMPTZ NOT NULL,location TEXT NOT NULL,device TEXT NOT NULL,temperature DOUBLE PRECISION NULL,humidity DOUBLE PRECISION NULL);SELECT create_hypertable('timescaletb','time',chunk_time_interval => INTERVAL '1 day');

SELECT h.table_name, c.interval_lengthFROM _timescaledb_catalog.dimension cJOIN _timescaledb_catalog.hypertable hON h.id = c.hypertable_id;

可通过 set_chunk_time_interval 函数修改 hypertable 分区间隔
如果是修改分区时间间隔,需要注意的是只会在新建的分区中生效,已经创建的分区不会受影响。所以,如果创建了一个时间很长的分区间隔,例如是1年的间隔,想修改成一个更小的分区间隔,那这个更小间隔只会在一年后生效,如果不想等待,那只能新建一个表并设置分区间隔,再进行数据迁移了。
SELECT set_chunk_time_interval('timescaletb', INTERVAL '12 hours');SELECT h.table_name, c.interval_lengthFROM _timescaledb_catalog.dimension cJOIN _timescaledb_catalog.hypertable hON h.id = c.hypertable_id;

自动增加分区:
查看当前分区情况,目前为空表,暂无子表:
\d+ timescaletb
插入不同时间段的数据:
INSERT INTO timescaletb(time, location, device, temperature, humidity)values('2024-04-15 01:21:27', 'FM00001', 'FM000001', 1, 1),('2024-04-15 05:21:27', 'FM00002', 'FM000002', 2, 2),('2024-04-15 15:21:27', 'FM00003', 'FM000003', 3, 3),('2024-04-15 18:21:27', 'FM00004', 'FM000004', 4, 4),('2024-04-16 11:21:27', 'FM00005', 'FM000005', 5, 5),('2024-04-16 13:21:27', 'FM00006', 'FM000006', 6, 6);

再次查看当前分区情况,已存在3个分区,每12小时1个分区:
\d+ timescaletb
查看数据:
select * from timescaletb ;select * from _timescaledb_internal._hyper_17_13_chunk;select * from _timescaledb_internal._hyper_17_14_chunk;select * from _timescaledb_internal._hyper_17_15_chunk;

执行计划
explain select * from timescaletb ;
explain select * from timescaletb where temperature>3;
explain select * from timescaletb where time>'2024-04-16';
5.Hypertable索引操作
默认情况下,创建hypertable 超表时会自动创建索引,可以通过将create_default_indexes选项设置为false来阻止创建索引。
默认索引为:
1.在所有hypertable 上,按时间降序的索引
2.在具有空间分区的hypertable 上,空间和时间字段上的联合索引
在一个 hypertable 中创建索引分为两步:
首先确定该超表的分区列有哪些,其中 time 列是所有 hypertable 表的分区列,所以创建索引必须包含该列;然后,在创建 hypertable 表时可以通过 partitioning_column 字段指定空间分区列。
创建的索引组合必须包括所有分区列,在此基础上增加其他列。
创建索引
hypertable 中创建索引也是使用标准的 SQL 语句,如CREATE INDEX / CREATE UNIQUE INDEX。
例如,在 timescaletb 超表的 time 和 device 列上建立索引 idx_device_time
\d timescaletbCREATE UNIQUE INDEX idx_device_time ON timescaletb(device, time);\d timescaletb

idx_device_time 索引中包含了所有分区列 time,而 device 不是分区列,如果创建的索引组合不包含分区列,会抛出错误,如下 idx_device 索引仅建立在 device 列上
CREATE UNIQUE INDEX idx_device ON timescaletb(device);
删除索引
hypertable 中删除索引仍是使用标准的 SQL 语句 DROP INDEX,删除 timescaletb 超表中的索引 idx_device_time。
DROP INDEX idx_device_time;\d timescaletb

已建索引的普通表转化为 hypertable,空间分区列 partitioning_column 必须包含在已建的索引中。
例如,在普通表 base_table 中已经在 device_id 和 time 列上建立了索引 idx_deviceid_time,那么创建以该表为基表的 hypertable 的分区列需要包含在索引列中,即分区列仅能为 time 与 device_id 的组合。
下例中,将已经创建好索引的 base_table 表转化为仅以 time 为分区列的 hypertable
CREATE TABLE base_table(time TIMESTAMPTZ,user_id BIGINT,device_id BIGINT,value FLOAT);CREATE UNIQUE INDEX idx_deviceid_time ON base_table(device_id, time);\d base_table

SELECT * from create_hypertable('base_table', 'time');\d base_table

也可以将已经创建好索引的 base_table 表转化为以 time 和 device_id 为分区列的 hypertable
drop table base_table;CREATE TABLE base_table(time TIMESTAMPTZ,user_id BIGINT,device_id BIGINT,value FLOAT);CREATE UNIQUE INDEX idx_deviceid_time ON base_table(device_id, time);

SELECT * FROM create_hypertable('base_table','time',partitioning_column => 'device_id',number_partitions => 4);\d base_table

但是,我们无法将不在索引组合中的 user_id 作为创建 hypertable 时的分区列
要解决这个问题,要确保在转化为超表之前建立的所有索引都要包含 hypertable 的分区列,所以将已存在的索引中加入 user_id 即可(重建索引可以重建一个索引,然后删除老的索引,避免使用 reindex 重建索引,因为该过程是阻塞的,一般大表不建议使用这个命令 )
drop table base_table;CREATE TABLE base_table(time TIMESTAMPTZ,user_id BIGINT,device_id BIGINT,value FLOAT);CREATE UNIQUE INDEX idx_deviceid_time ON base_table(device_id, time);\d base_tableSELECT * FROM create_hypertable('base_table','time',partitioning_column => 'user_id',number_partitions => 4);

DROP INDEX idx_deviceid_time;CREATE UNIQUE INDEX idx_userid_deviceid_time ON base_table(user_id, device_id, time);\d base_table

SELECT * FROM create_hypertable('base_table','time',partitioning_column => 'user_id',number_partitions => 4);\d base_table

6.删除Hypertable超表
DROP删除Hypertable只需要使用 PG 命令删除基表(普通表)即可:
\ddrop table base_table;drop table newtimescaletb;drop table timescaletb;\d

四、总结
随着业务场景越来越多样性,Halo数据库将会在下一版本将TimescaleDB集成进去,并做进一步的优化,以适应用户对时序数据库越来越高的要求。