第四章:数据库设计与建模

数据库设计这事儿,说实话,是智能制造平台的地基。地基没打好,后面盖再漂亮的楼都得塌。我见过太多项目,前期图省事随便搞搞表结构,结果数据量一上来,查询慢得像蜗牛,改又改不动,最后只能推倒重来。

今天咱们就聊聊,半导体智能制造平台里,数据库到底该怎么设计。

4.1 关系型数据库:PostgreSQL vs MySQL

先说说关系型数据库。PostgreSQL 和 MySQL,到底选哪个?

我个人习惯用 PostgreSQL。为什么?因为半导体制造的数据类型太杂了。你想想看,有设备参数、工艺配方、良率数据、批次信息……PostgreSQL 对 JSON、数组、自定义类型的支持,比 MySQL 强太多。

举个例子,设备参数里经常有「温度曲线」这种数据,是一串时间-温度对。PostgreSQL 可以直接用数组或者 JSONB 存,查询起来特别方便。MySQL 虽然也能存 JSON,但功能弱不少。

当然,MySQL 也有它的优势。部署简单,运维成本低,社区资源丰富。如果你的团队里 DBA 资源紧张,MySQL 可能更合适。

我的建议: 如果项目规模不大(比如单厂、数据量在 TB 级以下),MySQL 够用。如果要做多厂协同、数据量大、数据类型复杂,直接上 PostgreSQL。

4.2 表结构设计实战

好,假设我们选了 PostgreSQL。那表该怎么设计?

我直接给出一套我在项目中用过的核心表结构,你们可以参考。

设备信息表(equipment)

CREATE TABLE equipment (
    id SERIAL PRIMARY KEY,
    equipment_id VARCHAR(50) UNIQUE NOT NULL,  -- 设备编号
    equipment_name VARCHAR(100) NOT NULL,       -- 设备名称
    equipment_type VARCHAR(50),                 -- 设备类型
    location VARCHAR(100),                      -- 位置
    status VARCHAR(20) DEFAULT 'idle',          -- 状态
    install_date DATE,                          -- 安装日期
    last_maintenance TIMESTAMP,                 -- 上次维护时间
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

工艺配方表(recipe)

CREATE TABLE recipe (
    id SERIAL PRIMARY KEY,
    recipe_id VARCHAR(50) UNIQUE NOT NULL,
    recipe_name VARCHAR(100) NOT NULL,
    product_id VARCHAR(50),                     -- 关联产品
    parameters JSONB,                           -- 工艺参数(JSON格式)
    version INT DEFAULT 1,                      -- 版本号
    is_active BOOLEAN DEFAULT TRUE,
    created_by VARCHAR(50),
    created_at TIMESTAMP DEFAULT NOW()
);

这里有个坑,我必须要说。参数用 JSONB 存,确实灵活,但查询性能会受影响。我的做法是:高频查询的字段单独拎出来建索引,低频查询的放 JSONB 里。

生产批次表(lot)

CREATE TABLE lot (
    id SERIAL PRIMARY KEY,
    lot_id VARCHAR(50) UNIQUE NOT NULL,
    product_id VARCHAR(50) NOT NULL,
    quantity INT NOT NULL,
    current_step VARCHAR(50),                   -- 当前工序
    status VARCHAR(20) DEFAULT 'in_progress',
    start_time TIMESTAMP,
    end_time TIMESTAMP,
    equipment_id VARCHAR(50),                   -- 当前所在设备
    recipe_id VARCHAR(50)                       -- 当前使用的配方
);
小技巧: 批次表里加一个 current_step 字段,可以避免每次查询都要去工序表里 JOIN。虽然有点冗余,但查询速度快很多。这就是「空间换时间」的思路。

4.3 时序数据库选型:InfluxDB vs TimescaleDB

关系型数据库搞定了业务数据,但半导体制造里还有一类数据——时序数据。设备传感器每秒都在产生数据:温度、压力、流量、振动……这些数据的特点是:写入量大、查询范围广、很少更新。

用关系型数据库存时序数据?不是不行,但你会后悔的。我试过用 PostgreSQL 存传感器数据,一个月后单表就上亿条,查询慢得让人崩溃。

那选 InfluxDB 还是 TimescaleDB?

对比项 InfluxDB TimescaleDB
存储引擎 自研 TSM 引擎 基于 PostgreSQL
写入性能 极高(百万点/秒) 高(十万级/秒)
查询语法 类 SQL(Flux) 标准 SQL
与关系型数据联动 需要额外开发 天然支持 JOIN
运维复杂度 中等 低(PG 生态)

我个人建议:如果团队熟悉 SQL,选 TimescaleDB。它本质上是 PostgreSQL 的扩展,你可以直接用 SQL 查询时序数据,还能跟业务表做 JOIN。InfluxDB 虽然写入性能更强,但学习成本高,而且跟业务数据联动比较麻烦。

4.4 时序数据建模

时序数据库的建模思路跟关系型数据库不一样。核心原则是:按时间分区,按标签索引

以 TimescaleDB 为例,传感器数据表可以这样设计:

CREATE TABLE sensor_data (
    time TIMESTAMPTZ NOT NULL,
    equipment_id VARCHAR(50) NOT NULL,
    sensor_type VARCHAR(50) NOT NULL,
    value DOUBLE PRECISION,
    unit VARCHAR(20)
);

-- 转换为超表(按时间分区)
SELECT create_hypertable('sensor_data', 'time');

-- 建索引
CREATE INDEX idx_equipment_sensor 
ON sensor_data (equipment_id, sensor_type, time DESC);

这里要注意:不要把 equipment_id 和 sensor_type 拆成多个表。时序数据库的设计理念是「宽表」,把所有传感器的数据放一张表里,通过标签来区分。这样查询时只需要扫描一个分区,效率极高。

避坑指南: 我曾经把每个传感器单独建一张表,结果一个月后数据库里有上千张表,管理起来简直是噩梦。后来全部合并成一张超表,查询性能反而提升了 3 倍。记住:时序数据库不怕数据量大,怕的是表太多。

4.5 数据字典与 ER 图

数据字典这东西,很多人觉得可有可无。但我告诉你,没有数据字典的项目,三个月后就是一团乱麻

我习惯用 Markdown 写数据字典,放在项目的 docs 目录下,跟代码一起版本管理。格式大概这样:

## 设备信息表(equipment)

| 字段名 | 类型 | 说明 | 示例 |
|--------|------|------|------|
| equipment_id | VARCHAR(50) | 设备编号,主键 | EQ-001 |
| equipment_name | VARCHAR(100) | 设备名称 | 光刻机A |
| status | VARCHAR(20) | 状态:idle/running/down | running |
| parameters | JSONB | 设备参数 | {"temp": 25.0} |

至于 ER 图,我推荐用 PlantUML 画,纯文本,方便版本管理。下面是我用 SVG 画的一个核心 ER 图:

equipment 设备信息表 PK: equipment_id recipe 工艺配方表 PK: recipe_id lot 生产批次表 PK: lot_id sensor_data 传感器时序数据 超表(按时间分区) 使用 N:1 N:1 1:N

4.6 数据备份与恢复策略

备份这事儿,平时没人关心,出事了才追悔莫及。我见过一个工厂,数据库硬盘坏了,才发现备份脚本三个月前就失效了……那场面,真是欲哭无泪。

我的备份策略很简单,就三条:

  1. 全量备份 + 增量备份:每周日做全量备份,每天做增量备份。PostgreSQL 用 pg_dump,TimescaleDB 也一样。
  2. 异地备份:备份文件至少存两份,一份本地,一份远程(比如对象存储)。
  3. 定期演练:每个月至少做一次恢复演练,确保备份文件可用。

具体的备份脚本,我贴一个 PostgreSQL 的:

#!/bin/bash
# 全量备份脚本
BACKUP_DIR="/data/backup/postgres"
DB_NAME="smart_fab"
DATE=$(date +%Y%m%d)

pg_dump -h localhost -U admin -Fc $DB_NAME > $BACKUP_DIR/full_${DATE}.dump

# 删除30天前的备份
find $BACKUP_DIR -name "*.dump" -mtime +30 -delete

恢复命令也很简单:

pg_restore -h localhost -U admin -d smart_fab full_20240101.dump
注意: 恢复前一定要先确认目标数据库的版本跟备份时的版本一致。我吃过这个亏,用 pg_restore 恢复时版本不匹配,报了一堆错,折腾了大半天。

对于 TimescaleDB,备份方式跟 PostgreSQL 完全一样,因为它本身就是 PG 的扩展。InfluxDB 的话,可以用它的 backup 命令,但恢复起来稍微麻烦点,建议用对象存储做快照。

好了,数据库设计这块就聊到这儿。记住一句话:设计时多花一小时,运维时少花一天。别图省事,该建的索引建上,该写的文档写好,该做的备份做全。这些都是基本功,但基本功往往决定了一个项目能走多远。


专注资料整理