第三章:数据库安装与配置——关系型数据库安装、表空间创建与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太小会导致临时表空间暴涨 |
| PROCESSES | 300-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,最大50GB | ON,每次扩展500MB |
| ATS_HIST | 历史数据表(运行记录、告警日志等) | 初始50GB,最大500GB | ON,每次扩展1GB |
| ATS_INDEX | 索引数据 | 初始5GB,最大20GB | ON,每次扩展200MB |
| ATS_TEMP | 临时表空间(排序、分组操作) | 初始2GB,最大10GB | ON,每次扩展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优化、索引策略、连接池配置等内容。到时候我会分享一些我在项目上遇到的真实案例,保证让你收获满满。