4. 报警数据库的典型表结构设计
船舶AMS(报警监测系统)的数据库并不是“把所有报警塞进一张表”那么简单。它要同时支撑三类差异很大的工作:一是实时监视,操作员在几十毫秒内要看到某台辅机的滑油压力越限;二是历史追溯,轮机长要查清三天前主机排气温度报警前后的完整过程;三是配置管理,工程师要调整某个报警点的阈值、延时和联动动作。这三类工作对数据的要求不同,因此典型设计会把数据拆成报警点表、报警记录表、报警配置表三张核心表,再辅以设备表、测点表等基础表。理解它们的字段分工,是理解规范化与查询效率如何平衡的前提。
三张核心表的职责边界
报警点表描述“有哪些报警对象”,属于静态主数据。它回答的是:这条报警属于哪台设备、哪个测点、报警类型是什么、当前是否启用。典型字段包括报警点编号(主键)、设备编号、测点编号、报警名称、报警类别(越限、开关量、通信故障、系统故障等)、优先级、工程单位、量程上下限、是否启用、创建与修改时间。注意,报警点表不存放阈值和延时,这些属于可变的配置,放在配置表中,避免主数据被频繁更新。
报警配置表描述“这条报警按什么规则触发”,属于可版本化的参数数据。典型字段包括配置编号、报警点编号(外键)、高报阈值、高高报阈值、低报阈值、低低报阈值、报警延时(秒)、回差(死区)、触发方向、联动动作码、生效时间、版本号、审核状态。把阈值从点表中剥离,好处是同一报警点可以保留多套配置(如航行工况与停泊工况不同阈值),并且每次修改都有版本记录,便于追溯“当时为什么报”。
报警记录表描述“实际发生了什么”,属于高频写入的事件数据。典型字段包括记录编号(自增主键)、报警点编号、发生时间、确认时间、恢复时间、报警时值、报警状态(发生、确认、恢复、已归档)、确认人、来源通道、关联配置版本号。这张表增长最快,一艘船数千个报警点,一天产生数千到数万条记录很常见,因此它的索引设计和归档策略直接决定系统长期运行的流畅度。
规范化到什么程度合适
按第三范式,报警记录表里不应重复存放报警名称、设备名称,只保留报警点编号即可,查询时再关联。这在存储上最省、一致性最好,但代价是每次列表查询都要多表连接。实际工程中常见的折中做法是:主数据和配置表保持较高规范化,因为它们数据量小、修改少、要求准确;报警记录表做适度反规范化,冗余存入报警名称、设备编号、优先级等“查询时几乎总要显示”的字段。这样历史记录即使对应的报警点后来被删除或改名,旧记录仍能完整呈现当时的语义,这恰恰是事故追溯需要的。
另一个关键平衡点在索引。报警记录表最典型的查询是“某报警点在某时间段内的记录”和“某时间段内所有未恢复报警”。前者适合在(报警点编号,发生时间)上建复合索引,后者适合在(报警状态,发生时间)上建索引。索引不是越多越好:每增加一个索引,插入就要多维护一份结构,而报警记录是持续高频写入的。一般控制在两到三个核心索引,其余查询通过归档库或报表库承担。
一个可参考的建表示例
以下为教学示例,采用通用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,追溯时按版本号回查阈值,因果链就完整了。这也是配置表必须带版本号和生效时间、而不能只做简单更新的根本原因。
归纳起来,三张表的分工可以概括为:点表管“身份”,配置表管“规则”,记录表管“事实”。规范化保证身份与规则不重复、不失真,适度反规范化与合理索引保证事实数据写得进、查得快。二者之间的取舍不是一次定死的,而应随报警点规模、记录增长速度和追溯深度要求持续调整。