第三章:数据库安装与配置——关系型数据库安装、表空间创建与ATS专用表结构设计

各位同学,今天我们进入ATS系统最底层的支撑环节——数据库。说实话,很多刚入行的工程师觉得数据库就是装个软件、建几张表,没什么技术含量。但我得告诉你,ATS系统里数据库要是没搞好,轻则界面卡顿,重则全线列车定位丢失。我在项目上见过太多次了。

咱们这一章,就聊聊关系型数据库的安装、表空间规划,以及ATS专用表结构该怎么设计。我会结合Oracle和PostgreSQL两种主流数据库来讲,毕竟国内轨道交通项目里,这两家占了九成以上。

3.1 数据库选型:Oracle还是PostgreSQL?

先说说选型。我个人习惯是:如果业主预算充足、对运维团队的技术能力有信心,我会推荐Oracle。为什么呢?Oracle的RAC(Real Application Clusters)在高可用场景下非常成熟,而且它的AWR报告对性能调优帮助极大。我在广州某线路项目上,就是靠AWR定位到一条慢SQL,把ATS的告警响应时间从8秒降到了0.5秒。

但如果你预算有限,或者团队里没有专职DBA,PostgreSQL是更好的选择。PG的流复制、逻辑复制功能已经非常成熟,而且它对硬件的要求比Oracle低不少。我记得在成都的一个项目上,我们用PG扛住了日均300万条列车运行记录,跑了两年没出过问题。

核心建议:

  • Oracle:适合大型线网、多线路集中控制、有专职DBA的场景
  • PostgreSQL:适合单线路、中小规模、运维团队精简的场景

3.2 数据库安装要点

安装过程我就不逐条念文档了,网上教程一大堆。我只说几个关键点,这些是我踩过坑的地方。

3.2.1 Oracle安装注意事项

Oracle安装时,字符集一定要选对。ATS系统里大量使用中文站名、信号机名称,如果字符集选成了AL32UTF8以外的编码,后面导入数据时会出现乱码。我曾经在某个项目上,因为安装时选了ZHS16GBK,结果导入一条包含“滘”字的站名时直接报错,折腾了两天才发现是字符集的问题。

另外,Oracle的SGA和PGA大小要提前规划好。对于ATS系统,我建议:

参数建议值说明
SGA_TARGET物理内存的40%不要超过60%,否则操作系统会频繁swap
PGA_AGGREGATE_TARGET物理内存的15%ATS的排序操作较多,PGA太小会导致临时表空间暴涨
PROCESSES300-500根据并发连接数调整,ATS通常需要200-400个连接

3.2.2 PostgreSQL安装注意事项

PostgreSQL安装相对简单,但有几个参数必须改。默认的配置是给开发环境用的,生产环境必须调优。我建议重点关注这几个:

# postgresql.conf 关键参数
shared_buffers = 1GB          # 物理内存的25%
work_mem = 32MB               # 排序操作内存,不要太大,否则并发时内存会爆
maintenance_work_mem = 256MB  # 维护操作(如VACUUM)专用内存
effective_cache_size = 4GB    # 告诉优化器操作系统缓存有多大
wal_buffers = 16MB            # WAL日志缓冲区,写密集型场景要调大

我的经验:PostgreSQL的work_mem参数特别容易踩坑。如果你把work_mem设得太大,比如256MB,当有100个并发查询时,光排序操作就要吃掉25GB内存。嗯,这里要注意,work_mem是每个会话、每个排序操作都会分配一次。我建议从32MB开始,观察临时文件大小再逐步调整。

3.3 表空间创建与规划

表空间设计,说白了就是给数据分地盘。ATS系统的数据有几种不同类型,它们的访问频率、数据量、生命周期都不一样,混在一起会出问题。

3.3.1 Oracle表空间规划

我习惯把ATS数据分成四个表空间:

表空间名称用途数据文件大小自动扩展
ATS_DATA实时数据表(列车位置、信号状态等)初始10GB,最大50GBON,每次扩展500MB
ATS_HIST历史数据表(运行记录、告警日志等)初始50GB,最大500GBON,每次扩展1GB
ATS_INDEX索引数据初始5GB,最大20GBON,每次扩展200MB
ATS_TEMP临时表空间(排序、分组操作)初始2GB,最大10GBON,每次扩展100MB

创建表空间的SQL示例:

-- 创建ATS_DATA表空间
CREATE TABLESPACE ATS_DATA
DATAFILE '/u01/oradata/ats/ats_data01.dbf' SIZE 10G
AUTOEXTEND ON NEXT 500M MAXSIZE 50G
EXTENT MANAGEMENT LOCAL AUTOALLOCATE
SEGMENT SPACE MANAGEMENT AUTO;

-- 创建ATS_HIST表空间
CREATE TABLESPACE ATS_HIST
DATAFILE '/u01/oradata/ats/ats_hist01.dbf' SIZE 50G
AUTOEXTEND ON NEXT 1G MAXSIZE 500G
EXTENT MANAGEMENT LOCAL AUTOALLOCATE
SEGMENT SPACE MANAGEMENT AUTO;

警告:千万不要把所有数据都放在USERS表空间里。我曾经在一条线路上看到,运维人员把所有ATS表都建在默认的USERS表空间,结果历史数据把表空间撑爆了,导致实时数据写入失败,全线ATS界面卡死。嗯,这个教训很深刻。

3.3.2 PostgreSQL表空间规划

PostgreSQL的表空间概念和Oracle类似,但实现方式不同。PG的表空间实际上是一个目录路径。我建议这样规划:

-- 创建表空间目录(需要操作系统权限)
mkdir -p /pgdata/ats_data
mkdir -p /pgdata/ats_hist
mkdir -p /pgdata/ats_index

-- 在PG中创建表空间
CREATE TABLESPACE ats_data LOCATION '/pgdata/ats_data';
CREATE TABLESPACE ats_hist LOCATION '/pgdata/ats_hist';
CREATE TABLESPACE ats_index LOCATION '/pgdata/ats_index';

PostgreSQL没有临时表空间的概念,临时文件默认放在base/pgsql_tmp目录下。如果你发现临时文件增长很快,建议把work_mem调大一些。

3.4 ATS专用表结构设计

好了,数据库装好了,表空间也建好了,接下来就是重头戏——ATS专用表结构设计。这部分内容,我建议你仔细看,因为表结构设计的好坏,直接决定了ATS系统的性能和可维护性。

3.4.1 核心表设计原则

ATS系统的表结构设计,有几个原则必须遵守:

  • 实时数据与历史数据分离:实时表只保留当前状态,历史表记录变化轨迹
  • 分区表策略:历史数据按时间分区,方便数据清理和查询优化
  • 索引设计要克制:不是每个字段都加索引,索引过多会影响写入性能
  • 字段类型要精准:能用INTEGER就别用VARCHAR,能用SMALLINT就别用INTEGER

3.4.2 列车位置表(TRAIN_POSITION)

这是ATS系统最核心的表,记录每列车的实时位置信息。我设计时一般包含这些字段:

-- Oracle版本
CREATE TABLE TRAIN_POSITION (
    TRAIN_ID        VARCHAR2(10)    NOT NULL,   -- 列车ID
    LINE_ID         VARCHAR2(5)     NOT NULL,   -- 线路编号
    SECTION_ID      VARCHAR2(10),               -- 区段编号
    POSITION_X      NUMBER(10,2),               -- X坐标(米)
    POSITION_Y      NUMBER(10,2),               -- Y坐标(米)
    SPEED           NUMBER(5,2),                -- 当前速度(km/h)
    DIRECTION       CHAR(1),                    -- 运行方向(U/D)
    OCCUPY_STATUS   CHAR(1)         DEFAULT '0',-- 占用状态(0/1)
    UPDATE_TIME     TIMESTAMP       NOT NULL,   -- 更新时间
    CONSTRAINT PK_TRAIN_POS PRIMARY KEY (TRAIN_ID, LINE_ID)
)
TABLESPACE ATS_DATA;

-- PostgreSQL版本
CREATE TABLE TRAIN_POSITION (
    TRAIN_ID        VARCHAR(10)     NOT NULL,
    LINE_ID         VARCHAR(5)      NOT NULL,
    SECTION_ID      VARCHAR(10),
    POSITION_X      NUMERIC(10,2),
    POSITION_Y      NUMERIC(10,2),
    SPEED           NUMERIC(5,2),
    DIRECTION       CHAR(1),
    OCCUPY_STATUS   CHAR(1)         DEFAULT '0',
    UPDATE_TIME     TIMESTAMP       NOT NULL,
    PRIMARY KEY (TRAIN_ID, LINE_ID)
)
TABLESPACE ats_data;

设计思路:为什么主键用(TRAIN_ID, LINE_ID)而不是单独一个自增ID?因为ATS系统查询列车位置时,永远是根据列车ID和线路ID来查的。用联合主键可以避免一次索引回表操作,查询速度能提升30%以上。我在深圳某项目上做过测试,这个设计让界面刷新延迟从200ms降到了120ms。

3.4.3 列车运行记录表(TRAIN_RUN_LOG)

这张表记录列车的历史运行轨迹,数据量非常大。一条线路一天可能产生几百万条记录。所以,分区是必须的。

-- Oracle分区表(按天分区)
CREATE TABLE TRAIN_RUN_LOG (
    LOG_ID          NUMBER(12)      NOT NULL,
    TRAIN_ID        VARCHAR2(10)    NOT NULL,
    LINE_ID         VARCHAR2(5)     NOT NULL,
    SECTION_ID      VARCHAR2(10),
    POSITION_X      NUMBER(10,2),
    POSITION_Y      NUMBER(10,2),
    SPEED           NUMBER(5,2),
    DIRECTION       CHAR(1),
    RECORD_TIME     TIMESTAMP       NOT NULL,
    CONSTRAINT PK_TRAIN_LOG PRIMARY KEY (LOG_ID, RECORD_TIME)
)
TABLESPACE ATS_HIST
PARTITION BY RANGE (RECORD_TIME)
INTERVAL (NUMTODSINTERVAL(1, 'DAY'))
(
    PARTITION P_INIT VALUES LESS THAN (TO_DATE('2024-01-01', 'YYYY-MM-DD'))
);

-- 创建索引(注意:索引也要分区)
CREATE INDEX IDX_TRAIN_LOG_TIME ON TRAIN_RUN_LOG(RECORD_TIME) LOCAL TABLESPACE ATS_INDEX;
CREATE INDEX IDX_TRAIN_LOG_TRAIN ON TRAIN_RUN_LOG(TRAIN_ID) LOCAL TABLESPACE ATS_INDEX;

注意:Oracle的INTERVAL分区功能在12c及以上版本才支持。如果你用的是11g,需要手动创建分区。我曾经在一条老旧线路上遇到11g数据库,只能写存储过程每月自动创建分区。嗯,那段时间真是噩梦。

3.4.4 信号设备状态表(SIGNAL_STATUS)

信号机、道岔、轨道电路等设备的状态,也需要实时记录。这张表的特点是:写入频繁,但数据量相对较小。

CREATE TABLE SIGNAL_STATUS (
    DEVICE_ID       VARCHAR2(15)    NOT NULL,   -- 设备编号
    DEVICE_TYPE     CHAR(2)         NOT NULL,   -- 设备类型(SG=信号机, SW=道岔, TC=轨道电路)
    LINE_ID         VARCHAR2(5)     NOT NULL,
    STATION_ID      VARCHAR2(10),               -- 所属车站
    STATUS          VARCHAR2(20),               -- 状态描述
    STATUS_CODE     CHAR(2),                    -- 状态编码(00=正常, 01=故障, 02=维护)
    UPDATE_TIME     TIMESTAMP       NOT NULL,
    CONSTRAINT PK_SIGNAL_STA PRIMARY KEY (DEVICE_ID)
)
TABLESPACE ATS_DATA;

-- 创建索引
CREATE INDEX IDX_SIGNAL_LINE ON SIGNAL_STATUS(LINE_ID) TABLESPACE ATS_INDEX;
CREATE INDEX IDX_SIGNAL_STATION ON SIGNAL_STATUS(STATION_ID) TABLESPACE ATS_INDEX;

3.4.5 告警信息表(ALARM_LOG)

告警信息是运维人员最关注的数据。这张表的设计要兼顾写入性能和查询效率。

CREATE TABLE ALARM_LOG (
    ALARM_ID        NUMBER(12)      NOT NULL,
    ALARM_TYPE      VARCHAR2(20)    NOT NULL,   -- 告警类型
    ALARM_LEVEL     CHAR(1)         NOT NULL,   -- 告警级别(1=紧急, 2=重要, 3=一般)
    SOURCE_DEVICE   VARCHAR2(15),               -- 告警来源设备
    ALARM_DESC      VARCHAR2(200),              -- 告警描述
    OCCUR_TIME      TIMESTAMP       NOT NULL,   -- 发生时间
    CLEAR_TIME      TIMESTAMP,                  -- 清除时间
    CLEAR_USER      VARCHAR2(20),               -- 清除人
    STATUS          CHAR(1)         DEFAULT '0',-- 状态(0=未确认, 1=已确认, 2=已清除)
    CONSTRAINT PK_ALARM_LOG PRIMARY KEY (ALARM_ID)
)
TABLESPACE ATS_HIST
PARTITION BY RANGE (OCCUR_TIME)
INTERVAL (NUMTODSINTERVAL(7, 'DAY'))
(
    PARTITION P_INIT VALUES LESS THAN (TO_DATE('2024-01-01', 'YYYY-MM-DD'))
);

-- 常用查询索引
CREATE INDEX IDX_ALARM_TIME ON ALARM_LOG(OCCUR_TIME) LOCAL TABLESPACE ATS_INDEX;
CREATE INDEX IDX_ALARM_LEVEL ON ALARM_LOG(ALARM_LEVEL) LOCAL TABLESPACE ATS_INDEX;
CREATE INDEX IDX_ALARM_STATUS ON ALARM_LOG(STATUS) LOCAL TABLESPACE ATS_INDEX;

我的建议:告警表按周分区是个不错的选择。为什么不是按天?因为运维人员经常需要查一周内的告警趋势,按周分区可以让一周的数据落在一个分区里,查询效率更高。当然,如果你的系统告警量特别大(比如日均10万条以上),那就按天分区。

3.5 数据库初始化脚本与最佳实践

最后,我分享一个数据库初始化的最佳实践。不要手动一条条执行SQL,而是写一个完整的初始化脚本。这样既保证一致性,也方便后续的版本管理。

-- 00_init_database.sql
-- ATS数据库初始化脚本
-- 版本:v1.0
-- 日期:2024-01-01

-- 1. 创建表空间
-- (表空间创建语句见3.3节)

-- 2. 创建用户
CREATE USER ATS_ADMIN IDENTIFIED BY "YourPassword123"
DEFAULT TABLESPACE ATS_DATA
TEMPORARY TABLESPACE ATS_TEMP
QUOTA UNLIMITED ON ATS_DATA
QUOTA UNLIMITED ON ATS_HIST
QUOTA UNLIMITED ON ATS_INDEX;

GRANT CONNECT, RESOURCE, DBA TO ATS_ADMIN;

-- 3. 创建表结构
-- (表创建语句见3.4节)

-- 4. 创建序列(用于自增ID)
CREATE SEQUENCE SEQ_ALARM_ID
START WITH 1
INCREMENT BY 1
NOCACHE
NOCYCLE;

-- 5. 创建存储过程(用于数据清理)
-- 略,后续章节会详细讲解

COMMIT;

总结一下:数据库安装与配置是ATS系统的基石。表空间规划要区分实时数据、历史数据、索引和临时数据;表结构设计要遵循实时与历史分离、分区策略、精准字段类型等原则。这些做好了,后面的调优工作会轻松很多。

下一章,我们会聊聊数据库的性能调优,包括SQL优化、索引策略、连接池配置等内容。到时候我会分享一些我在项目上遇到的真实案例,保证让你收获满满。