分享一个工业数据处理中的技术选型和优化经验。
场景描述
某3C代工厂,产线上部署了多台视觉检测设备,每台设备每秒钟产生30-50条检测结果。
数据包含:检测时间、产品ID、缺陷类型、缺陷坐标、判定结果(良品/不良品)等字段。
业务需求:
查询任意7天的良率趋势,响应时间要求在1秒以内
查询某一批次产品的所有检测记录,响应时间要求在500ms以内
数据保留周期为1年
初始方案及问题
最初用PostgreSQL存储所有数据。表结构设计如下:
CREATE TABLE detection_results (
id BIGSERIAL PRIMARY KEY,
product_id VARCHAR(32),
batch_no VARCHAR(32),
defect_type VARCHAR(16),
result VARCHAR(8),
created_at TIMESTAMP
);
CREATE INDEX idx_created_at ON detection_results(created_at);
数据量达到500万条时,问题开始暴露:
7天趋势查询(约80万条数据)响应时间从200ms退化到15秒
批量插入性能从5000条/秒降至2000条/秒
索引膨胀,存储空间从预期50GB增长到100GB+
尝试了索引优化、分区表、查询语句优化,效果有限,最多把15秒降到12秒。
原因分析
检测数据是典型的时间序列数据,特征如下:
按时间顺序持续写入,极少更新和删除
查询几乎全部按时间范围过滤
数据量大、写入频率高
关系型数据库PostgreSQL擅长事务处理和多表关联查询,但它的存储引擎和索引结构是为通用场景设计的,不是为高频时序写入+范围查询的场景优化的。
简单说:用PostgreSQL存时序数据,就像用SUV跑F1赛道——能跑,但跑不快。
选型对比
调研了时序数据库方案后,选择了TDengine。
| 对比维度 | PostgreSQL | TDengine |
| 写入速度(条/秒) | 5000 | 30000+ |
| 7天趋势查询(80万条) | 15秒 | 200ms |
| 单批次查询(1000条) | 50ms | 20ms |
| 存储空间(500万条) | 100GB | 40GB |
| 学习成本 | 低(团队熟悉) | 中(需学习) |
TDengine的核心优势:
列式存储 + 时序索引,按时间范围查询时只读取需要的列
内置降采样和聚合函数,趋势查询不需要传输原始数据
高压缩比,存储空间减少60%
迁移方案
采用渐进式迁移,不推翻现有系统:
历史数据(3个月以上)保留在PostgreSQL,用于长期归档查询
新数据(7天内)写入TDengine,用于实时分析和趋势查询
查询层做路由:7天内实时查询走TDengine,7天以上历史查询走PostgreSQL
应用层封装统一数据源接口,上层业务无感知
核心代码示意(伪代码):
public class DataQueryService {
public List<TrendData> queryTrend(Date start, Date end) {
long daysBetween = ChronoUnit.DAYS.between(start, end);
if (daysBetween <= 7) {
return tdEngineDao.queryTrend(start, end); // 实时库
} else {
return pgDao.queryTrend(start, end); // 历史库
}
}
}
上线效果
7天趋势查询响应时间:15秒 → 200ms
存储空间:100GB → 40GB(减少60%)
写入性能:2000条/秒 → 30000条/秒
管理层看板实时刷新,不再等报表
踩坑提醒
⚠️ TDengine的SQL语法与PostgreSQL有差异,迁移时注意函数替换
⚠️ 分区键(tables)设计要合理,避免单表数据过大
⚠️ 历史数据迁移建议分批执行,避免一次性写入压力