4. 报警数据库的典型表结构设计

船舶AMS(报警监测系统)的数据库并不是“把所有报警塞进一张表”那么简单。它要同时支撑三类差异很大的工作:一是实时监视,操作员在几十毫秒内要看到某台辅机的滑油压力越限;二是历史追溯,轮机长要查清三天前主机排气温度报警前后的完整过程;三是配置管理,工程师要调整某个报警点的阈值、延时和联动动作。这三类工作对数据的要求不同,因此典型设计会把数据拆成报警点表、报警记录表、报警配置表三张核心表,再辅以设备表、测点表等基础表。理解它们的字段分工,是理解规范化与查询效率如何平衡的前提。

三张核心表的职责边界

报警点表描述“有哪些报警对象”,属于静态主数据。它回答的是:这条报警属于哪台设备、哪个测点、报警类型是什么、当前是否启用。典型字段包括报警点编号(主键)、设备编号、测点编号、报警名称、报警类别(越限、开关量、通信故障、系统故障等)、优先级、工程单位、量程上下限、是否启用、创建与修改时间。注意,报警点表不存放阈值和延时,这些属于可变的配置,放在配置表中,避免主数据被频繁更新。

报警配置表描述“这条报警按什么规则触发”,属于可版本化的参数数据。典型字段包括配置编号、报警点编号(外键)、高报阈值、高高报阈值、低报阈值、低低报阈值、报警延时(秒)、回差(死区)、触发方向、联动动作码、生效时间、版本号、审核状态。把阈值从点表中剥离,好处是同一报警点可以保留多套配置(如航行工况与停泊工况不同阈值),并且每次修改都有版本记录,便于追溯“当时为什么报”。

报警记录表描述“实际发生了什么”,属于高频写入的事件数据。典型字段包括记录编号(自增主键)、报警点编号、发生时间、确认时间、恢复时间、报警时值、报警状态(发生、确认、恢复、已归档)、确认人、来源通道、关联配置版本号。这张表增长最快,一艘船数千个报警点,一天产生数千到数万条记录很常见,因此它的索引设计和归档策略直接决定系统长期运行的流畅度。

设备表 设备编号 PK 设备名称 设备类型 安装位置 报警点表(主数据) 报警点编号 PK 设备编号 FK 测点编号 报警名称 / 类别 优先级 / 工程单位 是否启用 报警配置表(参数) 配置编号 PK 报警点编号 FK 高报 / 高高报阈值 低报 / 低低报阈值 延时 / 回差 / 方向 联动动作码 / 版本号 生效时间 / 审核状态 报警记录表(事件) 记录编号 PK(自增) 报警点编号 FK 发生时间 / 确认时间 恢复时间 报警时值 报警状态 确认人 / 来源通道 关联配置版本号 索引:报警点编号+发生时间 1 : N 1 : N(多版本) 1 : N 版本引用 设计要点 · 主数据与参数分离 · 事件表只追加不修改 · 配置留版本便于追溯 · 记录表按时间分区归档 · 外键保证引用完整性

规范化到什么程度合适

按第三范式,报警记录表里不应重复存放报警名称、设备名称,只保留报警点编号即可,查询时再关联。这在存储上最省、一致性最好,但代价是每次列表查询都要多表连接。实际工程中常见的折中做法是:主数据和配置表保持较高规范化,因为它们数据量小、修改少、要求准确;报警记录表做适度反规范化,冗余存入报警名称、设备编号、优先级等“查询时几乎总要显示”的字段。这样历史记录即使对应的报警点后来被删除或改名,旧记录仍能完整呈现当时的语义,这恰恰是事故追溯需要的。

另一个关键平衡点在索引。报警记录表最典型的查询是“某报警点在某时间段内的记录”和“某时间段内所有未恢复报警”。前者适合在(报警点编号,发生时间)上建复合索引,后者适合在(报警状态,发生时间)上建索引。索引不是越多越好:每增加一个索引,插入就要多维护一份结构,而报警记录是持续高频写入的。一般控制在两到三个核心索引,其余查询通过归档库或报表库承担。

一个可参考的建表示例

以下为教学示例,采用通用SQL语法,字段类型可按实际数据库调整:

CREATE TABLE alarm_point (
  point_id      VARCHAR(20) PRIMARY KEY,
  device_id     VARCHAR(20) NOT NULL,
  tag_code      VARCHAR(30) NOT NULL,
  alarm_name    VARCHAR(60) NOT NULL,
  alarm_class   VARCHAR(10) NOT NULL,   -- 越限/开关量/通信/系统
  priority      SMALLINT    NOT NULL,
  eng_unit      VARCHAR(10),
  enabled       SMALLINT    DEFAULT 1
);

CREATE TABLE alarm_config (
  config_id     BIGINT PRIMARY KEY,
  point_id      VARCHAR(20) NOT NULL,
  hi_limit      DECIMAL(10,2),
  hi_hi_limit   DECIMAL(10,2),
  lo_limit      DECIMAL(10,2),
  lo_lo_limit   DECIMAL(10,2),
  delay_sec     SMALLINT DEFAULT 3,
  deadband      DECIMAL(10,2) DEFAULT 0,
  action_code   VARCHAR(20),
  version_no    INT NOT NULL,
  effective_at  TIMESTAMP NOT NULL,
  audit_state   VARCHAR(10) DEFAULT '待审'
);

CREATE TABLE alarm_record (
  record_id     BIGINT PRIMARY KEY,
  point_id      VARCHAR(20) NOT NULL,
  occur_time    TIMESTAMP NOT NULL,
  ack_time      TIMESTAMP,
  clear_time    TIMESTAMP,
  alarm_value   DECIMAL(10,2),
  alarm_state   VARCHAR(10) NOT NULL,  -- 发生/确认/恢复/归档
  ack_user      VARCHAR(20),
  config_ver    INT
);
CREATE INDEX idx_rec_point_time ON alarm_record(point_id, occur_time);
CREATE INDEX idx_rec_state_time ON alarm_record(alarm_state, occur_time);

配置变更如何影响记录

假设某辅机滑油压力低报阈值原为0.25 MPa、延时3秒。工程师因设备老化将阈值下调为0.20 MPa,并把延时改为5秒。若配置表直接覆盖原值,那么三个月后复查历史报警时,就无法解释“为什么当时0.22 MPa报了警而现在不报”。正确做法是新增一条配置记录、版本号加一、写入生效时间,旧版本保留。报警记录表在写入时记录当时的config_ver,追溯时按版本号回查阈值,因果链就完整了。这也是配置表必须带版本号和生效时间、而不能只做简单更新的根本原因。

设计检查要点:报警点表是否只存静态属性;配置表是否可多版本并存;记录表是否只追加不修改;高频查询字段是否已建复合索引;历史记录是否冗余了必要的显示字段以支持追溯。

归纳起来,三张表的分工可以概括为:点表管“身份”,配置表管“规则”,记录表管“事实”。规范化保证身份与规则不重复、不失真,适度反规范化与合理索引保证事实数据写得进、查得快。二者之间的取舍不是一次定死的,而应随报警点规模、记录增长速度和追溯深度要求持续调整。