第三章 时序数据库入门

各位同学,今天我们来聊聊时序数据库。说实话,我刚接触SCADA系统那会儿,用的还是传统关系型数据库存数据。后来项目越做越大,数据量上来了,才发现——嗯,这条路走不通了。

3.1 什么是时序数据库

时序数据库,英文叫Time Series Database,简称TSDB。它专门处理带时间戳的数据。比如你工厂里的温度传感器,每秒钟上报一次温度值,这就是典型的时序数据。

我个人习惯把时序数据库想象成一个「时间轴上的记录本」。每一行数据都带着精确的时间戳,按时间顺序排列。你想想看,这和传统的关系型数据库有什么不同?

3.2 与关系型数据库的区别

关系型数据库,比如MySQL、PostgreSQL,它们擅长处理的是「实体关系」。比如用户表、订单表、商品表,这些数据之间有复杂的关联。

但时序数据不一样。它有几个特点:

  • 写入量大:每秒可能成千上万条数据
  • 很少更新:历史数据基本不会改
  • 按时间查询:查最近一小时、最近一天的数据
  • 聚合操作多:求平均值、最大值、最小值

我在项目中遇到过这样一个坑:用MySQL存传感器数据,一个月后表里就有几千万条记录。查询最近一小时的数据,居然要好几秒。后来换成InfluxDB,同样的查询,几十毫秒就搞定了。

核心区别一句话总结:

关系型数据库是「存什么查什么」,时序数据库是「按时间查什么」。

3.3 数据模型:measurement、tag、field

时序数据库的数据模型和关系型数据库完全不同。以InfluxDB为例,它有三个核心概念:

概念 类比关系型数据库 说明
measurement 表名 表示一类数据,比如temperature、pressure
tag 索引字段 用于过滤和分组,比如设备ID、位置
field 普通字段 实际存储的数值,比如温度值、压力值

举个例子。假设你有一个温度传感器,设备ID是sensor_01,安装在车间A区。数据模型是这样的:

measurement: temperature
tag: device_id = "sensor_01", location = "车间A区"
field: value = 25.6
time: 2024-01-15T10:30:00Z

为什么要分tag和field?说白了,tag是用来「筛选」的,field是用来「计算」的。tag会被索引,查询速度快;field不会被索引,但可以存大量数值。

我的经验:设计tag时要谨慎。tag的值越多,索引越大。我曾经在一个项目里把设备ID、型号、批次都设成tag,结果写入性能直线下降。后来只保留了设备ID和位置,其他信息放到field里,问题就解决了。

3.4 写入基础

写入时序数据,说白了就是往数据库里「塞」数据。以InfluxDB为例,写入格式很简单:

# 写入单条数据
INSERT temperature,device_id=sensor_01,location=车间A区 value=25.6

# 写入多条数据
INSERT temperature,device_id=sensor_01,location=车间A区 value=25.6
INSERT temperature,device_id=sensor_02,location=车间B区 value=26.1

用Python写入也很方便:

from influxdb import InfluxDBClient

client = InfluxDBClient(host='localhost', port=8086)
client.switch_database('scada_db')

json_body = [
    {
        "measurement": "temperature",
        "tags": {
            "device_id": "sensor_01",
            "location": "车间A区"
        },
        "fields": {
            "value": 25.6
        },
        "time": "2024-01-15T10:30:00Z"
    }
]

client.write_points(json_body)

注意:写入时序数据时,时间戳一定要准确。我曾经遇到过一个项目,因为时间戳格式不统一,导致数据错乱。建议统一使用UTC时间,或者至少保证所有设备的时间同步。

3.5 查询基础

查询时序数据,最常用的就是按时间范围查。InfluxDB的查询语言叫InfluxQL,和SQL有点像,但又不完全一样。

# 查询最近一小时的数据
SELECT * FROM temperature WHERE time > now() - 1h

# 查询指定设备的数据
SELECT * FROM temperature WHERE device_id = 'sensor_01'

# 查询平均值,按5分钟分组
SELECT MEAN(value) FROM temperature WHERE time > now() - 1h GROUP BY time(5m)

用Python查询:

result = client.query('SELECT * FROM temperature WHERE time > now() - 1h')
points = list(result.get_points())

for point in points:
    print(f"时间: {point['time']}, 温度: {point['value']}")

你想想看,如果换成MySQL,同样的查询要怎么写?

SELECT * FROM temperature WHERE create_time > DATE_SUB(NOW(), INTERVAL 1 HOUR)

看起来差不多,对吧?但性能差距巨大。InfluxDB内部做了大量优化,比如时间索引、列式存储、预聚合等。这些优化让时序查询快得飞起。

3.6 避坑指南

最后,分享几个我踩过的坑:

  • 不要用MySQL存时序数据:数据量一上来,查询慢得让人崩溃
  • tag不要太多:每个tag值都会创建索引,太多会影响写入性能
  • 时间戳要统一:建议用UTC,避免时区问题
  • 定期清理旧数据:时序数据占空间,设置好保留策略

总结一下:

时序数据库是SCADA系统的核心组件。它专门为时序数据设计,写入快、查询快、存储效率高。掌握好measurement、tag、field这三个概念,你就入门了。

下一章,我们实战搭建InfluxDB,写一个完整的SCADA数据采集程序。到时候,你会真正感受到时序数据库的威力。