TimescaleDB 物联网设备属性历史数据表设计及常用SQL文档
一、整体设计概述
1.1 业务背景
物联网设备数据具备写入高频、设备量大、时序性强、冷热数据分层、需多类型属性存储的特点,包含数值、布尔、字符串类设备测点数据。本方案采用TimescaleDB超表存储时序历史数据,拆分静态维度表+时序数据表,适配物联网绝大多数设备监控、数据溯源、曲线查询、阈值筛选业务场景。
1.2 核心设计原则
- 分表设计:静态设备信息普通表、动态时序数据超表,减少数据冗余
- 通用测点:单时序表兼容数值、布尔、字符串三种属性类型,无需分业务建表
- 冷热分离:热数据实时查询、冷数据自动压缩,降低存储成本
- 高性能分片:按时间自动分块,高并发写入、时间范围查询效率优异
1.3 基础技术约定
- 时间精度:毫秒级,字段类型
timestamp with time zone - 分片规则:普通物联网设备采用1天分块,高频上报设备可调整为1小时
- 压缩规则:保留7天热数据,7天后后台自动压缩
- 数据保留:默认保留1年历史数据,到期自动清理
二、完整表结构设计
2.1 设备基础维度表(普通表)
存储设备静态不变信息,避免时序表冗余静态字段,唯一主键关联时序数据表。
sql
CREATE TABLE iot_device (
device_id VARCHAR(64) PRIMARY KEY, -- 设备唯一ID
device_type VARCHAR(32) NOT NULL, -- 设备类型:温湿度/电表/水表
factory_code VARCHAR(32), -- 厂商编码
region_code VARCHAR(32), -- 区域/楼栋/点位编码
location VARCHAR(128), -- 安装位置描述
status SMALLINT DEFAULT 1, -- 设备状态:1在线 0离线
create_time TIMESTAMPTZ DEFAULT NOW(),
update_time TIMESTAMPTZ DEFAULT NOW()
);
2.2 设备属性时序超表(核心业务表)
存储设备动态上报的测点历史数据,采用通用字段设计,适配全部属性类型,转为超表做时序优化。
2.2.1 建表语句
sql
-- 1.创建原始时序普通表
CREATE TABLE iot_device_metric (
ts TIMESTAMPTZ NOT NULL, -- 时序时间戳 分片核心字段
device_id VARCHAR(64) NOT NULL, -- 关联设备唯一ID
metric_key VARCHAR(64) NOT NULL, -- 属性编码:temp/humidity/power/switch
metric_value DOUBLE PRECISION, -- 数值型测点值
str_value VARCHAR(256), -- 字符串/枚举/文本测点值
bool_value BOOLEAN, -- 布尔型测点值
raw_data JSONB, -- 原始上报整包报文
collect_ip VARCHAR(32), -- 采集网关IP
CONSTRAINT pk_iot_metric UNIQUE (ts, device_id, metric_key)
);
-- 2.转为Timescale超表,按时间分片,分区间隔1天
SELECT create_hypertable(
'iot_device_metric',
'ts',
chunk_time_interval => INTERVAL '1 day'
);
-- 3.开启表压缩配置,定义压缩规则
ALTER TABLE iot_device_metric SET (
timescaledb.compress,
timescaledb.compress_orderby = 'ts',
timescaledb.compress_segmentby = 'device_id, metric_key'
);
-- 4.添加自动压缩策略:7天前数据自动后台压缩
SELECT add_compression_policy(
'iot_device_metric',
INTERVAL '7 days'
);
-- 5.添加数据保留策略:保留1年数据,到期自动清理
SELECT add_retention_policy(
'iot_device_metric',
INTERVAL '1 year'
);
-- 6.创建业务索引,优化查询性能
CREATE INDEX idx_metric_device_ts ON iot_device_metric (device_id, ts DESC);
CREATE INDEX idx_metric_key_ts ON iot_device_metric (metric_key, ts DESC);
2.2.2 字段详细说明
| 字段名 | 字段作用 | 适用业务场景 |
|---|---|---|
| ts | 时序主键、超表分片依据 | 时间范围查询、历史曲线绘制、数据排序 |
| device_id | 设备唯一标识 | 单设备历史数据查询、设备关联筛选 |
| metric_key | 属性编码 | 区分温度、湿度、电压、开关等测点属性 |
| metric_value | 数值型存储字段 | 温湿度、电流、功率、液位等数值测点 |
| str_value | 字符串存储字段 | 告警文本、设备档位、运行模式等枚举文本 |
| bool_value | 布尔存储字段 | 开关启停、阀门状态、设备在线离线 |
| raw_data | JSONB原始报文 | 数据溯源、临时字段扩展、异常排查 |
2.2.3 关键参数释义
- chunk_interval = 1 day:并非每日新建物理表,底层自动生成Chunk数据块,对外仅展示一张逻辑表,每日数据存入独立数据块,拆分压力、提升查询效率。
- compress_orderby = ts:压缩时按时间排序存储,贴合时序查询习惯,压缩后读取速度无明显损耗。
- compress_segmentby = device_id, metric_key:压缩时将同一设备、同一属性数据打包存储,大幅提升压缩比(最高可达20:1),适配物联网筛选查询逻辑。
- 压缩策略7天:近7天热数据不压缩,保障高频查询性能;7天前冷数据后台自动压缩,节省硬盘空间。
2.3 存储补充说明
PostgreSQL/TimescaleDB中NULL空字段几乎不占用存储空间,系统通过空值位图标记空字段,单个空字段仅占用1bit空间。单条测点仅一种数据类型,其余字段为空,不会造成空间浪费,是物联网最优字段设计方案。
三、生产常用查询SQL
3.1 基础时间范围查询
3.1.1 单设备单属性历史数据(绘制曲线)
sql
-- 查询指定设备近24小时温度数据
SELECT ts, metric_value
FROM iot_device_metric
WHERE device_id = 'DEV001'
AND metric_key = 'temp'
AND ts > NOW() - INTERVAL '24 hours'
ORDER BY ts ASC;
3.1.2 按时间粒度聚合统计
sql
-- 按小时聚合,查询设备当日平均温度、最高温度
SELECT time_bucket(INTERVAL '1 hour', ts) AS hour_time,
AVG(metric_value) AS avg_temp,
MAX(metric_value) AS max_temp,
MIN(metric_value) AS min_temp
FROM iot_device_metric
WHERE device_id = 'DEV001' AND metric_key = 'temp'
AND ts > CURRENT_DATE
GROUP BY hour_time
ORDER BY hour_time;
3.2 属性值筛选查询(物联网高频告警场景)
3.2.1 数值阈值筛选(异常告警)
sql
-- 查询近2小时温度大于35℃的异常数据
SELECT device_id,ts,metric_value
FROM iot_device_metric
WHERE metric_key = 'temp'
AND metric_value > 35
AND ts > NOW() - INTERVAL '2 hours'
ORDER BY ts DESC;
3.2.2 布尔状态筛选
sql
-- 查询当前处于开启状态的阀门设备
SELECT DISTINCT ON (device_id) device_id,ts,bool_value
FROM iot_device_metric
WHERE metric_key = 'valve_switch' AND bool_value = true
ORDER BY device_id,ts DESC;
3.2.3 字符串枚举筛选
sql
-- 查询存在故障告警的设备记录
SELECT device_id,ts,str_value
FROM iot_device_metric
WHERE metric_key = 'alarm_status'
AND str_value = '故障'
AND ts > NOW() - INTERVAL '1 day';
3.3 最新数据查询(设备实时状态)
3.3.1 单设备所有属性最新数据
vbnet
SELECT DISTINCT ON (metric_key)
metric_key, ts, metric_value, str_value, bool_value
FROM iot_device_metric
WHERE device_id = 'DEV001'
ORDER BY metric_key, ts DESC;
3.3.2 所有设备单属性最新数据
sql
-- 查询全部设备最新温度数据
SELECT DISTINCT ON (device_id)
device_id, ts, metric_value
FROM iot_device_metric
WHERE metric_key = 'temp'
ORDER BY device_id, ts DESC;
3.3.3 高性能查询所有设备所有属性最新数据(生产推荐)
vbnet
SELECT t.*
FROM (
SELECT device_id, metric_key, MAX(ts) AS max_ts
FROM iot_device_metric
GROUP BY device_id, metric_key
) latest
JOIN iot_device_metric t
ON t.device_id = latest.device_id
AND t.metric_key = latest.metric_key
AND t.ts = latest.max_ts
ORDER BY t.device_id, t.metric_key;
3.4 关联查询(设备基础信息+测点数据)
vbnet
-- 查询指定区域设备最新温度,附带设备安装位置
SELECT d.device_id,d.location,m.metric_value,m.ts
FROM iot_device d
JOIN (
SELECT DISTINCT ON (device_id) device_id,metric_value,ts
FROM iot_device_metric
WHERE metric_key = 'temp'
ORDER BY device_id,ts DESC
) m ON d.device_id = m.device_id
WHERE d.region_code = 'AREA_001';
3.5 运维管理SQL
3.5.1 查看压缩状态
sql
SELECT chunk_name,range_start,range_end,is_compressed
FROM timescaledb_information.chunks
WHERE hypertable_name = 'iot_device_metric'
ORDER BY range_start DESC;
3.5.2 手动触发压缩(临时运维)
ini
SELECT compress_chunk(chunk)
FROM timescaledb_information.chunks
WHERE hypertable_name = 'iot_device_metric'
AND is_compressed = false
AND range_start < NOW() - INTERVAL '7 days';
四、生产最佳实践注意事项
- 写入优化:优先批量写入数据,避免单条频繁插入,适配MQTT、HTTP上报业务,减少数据库IO压力。
- 分片调整:秒级高频上报设备,修改分片间隔为1小时;低频设备可调整为7天。
- 索引原则:禁止随意新增索引,保留文档内两个核心索引即可,避免写入卡顿。
- 空值使用:严格遵循一个属性仅一种数据类型,其余字段置空,无需担心空间占用。
- 数据清理:禁止手动删除chunk数据,使用自带保留策略,秒级清理过期数据,无事务冗余。
(注:文档部分内容可能由 AI 生成)
五、连续聚合(Continuous Aggregate)
5.1 核心痛点
随着设备数量和数据量增长,直接对原始表进行时间聚合查询会面临性能问题:
sql
-- 慢查询:扫描全表进行小时级聚合
SELECT
device_id,
time_bucket('1 hour', ts) AS bucket,
AVG(metric_value) AS avg_value
FROM iot_device_metric
WHERE device_id = 'DEV001'
AND ts >= NOW() - INTERVAL '1 year'
GROUP BY device_id, bucket;
问题表现:
- 需扫描海量原始数据,IO 开销大
- 聚合计算在查询时实时执行,响应慢(秒级甚至超时)
- 高并发查询时数据库负载急剧上升
5.2 解决方案:TimescaleDB 连续聚合
使用连续聚合(Continuous Aggregate)预先计算并存储聚合结果,实现查询秒级响应。
5.2.1 创建 10 分钟粒度物化视图
sql
-- 1. 删除旧的
DROP MATERIALIZED VIEW IF EXISTS ca_device_metric_1h CASCADE;
-- 2. 创建 10 分钟 bucket 的物化视图
CREATE MATERIALIZED VIEW ca_device_metric_10min
WITH (timescaledb.continuous, timescaledb.materialized_only=true)
AS
SELECT
device_id,
metric_key,
time_bucket('10 minutes', ts) AS bucket,
AVG(metric_value) AS avg_value,
COUNT(*) AS cnt,
MIN(metric_value) AS min_value,
MAX(metric_value) AS max_value
FROM iot_device_metric
GROUP BY device_id, metric_key, bucket
WITH NO DATA;
-- 3. 创建索引
CREATE INDEX ON ca_device_metric_10min (device_id, metric_key, bucket DESC);
-- 4. 添加策略:10 分钟 bucket 需要至少覆盖 2 个 bucket(20 分钟)
SELECT add_continuous_aggregate_policy('ca_device_metric_10min',
START_OFFSET => INTERVAL '20 minutes',
END_OFFSET => INTERVAL '0 minutes',
SCHEDULE_INTERVAL => INTERVAL '10 minutes');
-- 5. 手动刷新初始化
CALL refresh_continuous_aggregate('ca_device_metric_10min', NULL, NOW());
-- 6. 验证
SELECT COUNT(*) FROM ca_device_metric_10min;
SELECT
device_id,
bucket,
avg_value,
cnt
FROM ca_device_metric_10min
ORDER BY bucket DESC
LIMIT 20;
5.3 适用场景
| 场景 | 说明 |
|---|---|
| 监控大屏 | 实时展示设备指标趋势(10 分钟粒度) |
| 报表统计 | 快速统计过去 24 小时/7 天的平均值、最大值 |
| 告警分析 | 基于聚合数据进行阈值判断 |
| 数据导出 | 快速导出历史统计数据,避免全表扫描 |
5.4 关键参数说明
| 参数 | 值 | 说明 |
|---|---|---|
START_OFFSET |
20 minutes |
可查询的最旧聚合数据偏移量(覆盖 2 个 bucket) |
END_OFFSET |
0 minutes |
实时数据可查,偏移量为 0 |
SCHEDULE_INTERVAL |
10 minutes |
每 10 分钟自动刷新一次聚合结果 |
5.5 连续聚合 vs 普通物化视图
| 特性 | 连续聚合 | 普通物化视图 |
|---|---|---|
| 自动更新 | 写入时自动维护聚合结果 | 需手动 REFRESH |
| 实时性 | 准实时(由策略决定) | 取决于刷新频率 |
| 适用场景 | 时序数据、持续写入 | 静态/低频更新数据 |
| 运维成本 | 低(自动维护) | 高(需调度刷新) |
连续聚合是时序数据场景的首选方案,在写入时自动维护聚合结果,无需手动调度刷新任务。