TimescaleDB 物联网设备属性历史数据表设计及常用SQL文档

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';

四、生产最佳实践注意事项

  1. 写入优化:优先批量写入数据,避免单条频繁插入,适配MQTT、HTTP上报业务,减少数据库IO压力。
  2. 分片调整:秒级高频上报设备,修改分片间隔为1小时;低频设备可调整为7天。
  3. 索引原则:禁止随意新增索引,保留文档内两个核心索引即可,避免写入卡顿。
  4. 空值使用:严格遵循一个属性仅一种数据类型,其余字段置空,无需担心空间占用。
  5. 数据清理:禁止手动删除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
实时性 准实时(由策略决定) 取决于刷新频率
适用场景 时序数据、持续写入 静态/低频更新数据
运维成本 低(自动维护) 高(需调度刷新)

连续聚合是时序数据场景的首选方案,在写入时自动维护聚合结果,无需手动调度刷新任务。

相关推荐
小小小小宇1 小时前
Go 后端锁机制详解
后端
挖坑的张师傅1 小时前
你的仓库 Agent Ready 了吗?
后端
客场消音器2 小时前
如何使用codex进行UI重构,让AI开发的前端页面不再千篇一律
前端·后端·微信小程序
Full Stack Developme2 小时前
spring-beans 解析
java·后端·spring
苏三说技术2 小时前
为什么大厂都不推荐在MySQL中使用NULL值?
后端
techdashen2 小时前
Rust 模块和文件不是一回事:一次讲清 `mod`、`use`、`pub use`
开发语言·后端·rust
爱勇宝3 小时前
别焦虑,也别躺平:给年轻程序员的一封信
前端·后端·架构
Full Stack Developme3 小时前
Spring 发展历史
java·后端·spring
ClouGence3 小时前
TiCDC 够用吗?聊聊 TiDB 同步的几个关键问题
数据库·分布式·后端