# 运营航班状态数据库设计 v2 状态:提案,待 ACM2-29 评审确认 日期:2026-09-08 适用部署:现场提供数据库时使用 Oracle 11g;现场不提供时使用自建 PostgreSQL。 ## 1. 目标和边界 本设计为 msgexchange-v2 提供一个可恢复、可查询、可审计的运营航班当前状态库。系统接收共享 MySQL 信箱中的 SIS 报文,解析后在自有数据库中形成当前航班状态,并通过 outbox 投递 Kafka 和出站信箱。 本系统不是 AODB 主数据编辑系统,不负责票务、旅客、机队维护或资源排班优化。上游 AODB/CIIMS 仍是业务报文来源;本系统的“权威”仅表示本系统已成功处理的当前状态和待发送事件。 设计目标: - 航班主表、资源明细、快照版本、处理终态和 outbox 在一个本地事务中提交。 - 保留 SIS 重复元素的顺序、源序号、资源号和全部属性,不因页面列数或现场样例缩减数据。 - 明确区分“字段未出现”“设置值”“显式清除”“集合替换”。 - 同一业务模型支持 PostgreSQL 和 Oracle 11g,数据库方言只存在于基础设施适配层。 - 在迁移、重放、回填失败和进程崩溃后,可以判断哪些业务已经提交,避免重复应用。 ## 2. 核心不变量 1. `FLIGHT_SCHD` 是当前航班状态的唯一写入目标;Redis 不参与动态状态权威。 2. 只有主泵写入航班状态和快照成员。跨实例保护使用 `PIPELINE_LOCK` 行锁,但生产仍只允许一个活动实例。 3. 一条消息的航班变更、明细变更、快照版本、outbox 事件和 `PROC_STATE=SUCCEEDED` 同事务提交;任一写入失败全部回滚。 4. 快照差删只删除上一代在同一 `FDAY` 域内的成员;`ADFT` 和已经迁移到其他日期的航班不能被误删。 5. 消息重放不得重复推进版本、重复生成业务事件或重复改变当前态。 6. 合法的重复输入不得静默丢弃。容量不足、未知属性、非法结构必须显式失败并记录原因。 7. 共享 MySQL 的成功回填属于提交后的补偿任务;回填失败不能把已成功的业务事务重新标记为失败。 ## 3. 逻辑模型 ```mermaid erDiagram FLIGHT_SCHD ||--o{ FLIGHT_GATE : has FLIGHT_SCHD ||--o{ FLIGHT_CHECKIN : has FLIGHT_SCHD ||--o{ FLIGHT_BELT : has FLIGHT_SCHD ||--o{ FLIGHT_STAND_PLAN : has FLIGHT_SCHD ||--o{ FLIGHT_CHUTE : has FLIGHT_SCHD ||--o{ FLIGHT_DELAY : has FLIGHT_SCHD ||--o{ FLIGHT_BRIDGE_OP : records FLIGHT_SCHD ||--o{ FLIGHT_CHOCK_OP : records FLIGHT_SCHD ||--o{ FLIGHT_ROUTE_POINT : has SCHD_GEN ||--o{ SCHD_GEN_FLID : contains ``` ### 3.1 航班主表:`FLIGHT_SCHD` 一行代表一个 `FLID` 的当前状态。保留现有 SIS 标量字段和派生字段,但不再把重复资源展开为 `GATE1/GATE2` 这类固定槽位。 建议补充: | 字段 | 含义 | |---|---| | `FLID` | 本地航班主键;单机场单 AODB 部署下可继续使用。 | | `FDAY` | DNLD 快照归属日,可为空;不等同于航班业务日期。 | | `LAST_MESSAGE_ID` | 最后影响该航班的消息 ID,用于审计和恢复判断。 | | `STATE_VERSION` | 有效状态变更版本;无副作用重放不得推进。 | | `SODT_RAW` 等 | 上游原始字符串,保证兼容和对拍。 | | `SCHEDULED_AT` 等 | 按机场业务时区解析后的查询字段。 | | `CREATED_AT`、`UPDATED_AT` | 本系统写入时间。 | 时间解析必须保留原值。SIS 使用本地机场时间格式时,不能直接当作 UTC;机场时区来自配置,解析失败不能静默转换。 ### 3.2 重复资源表 每个明细表至少保留以下通用字段: ```text FLID 主表外键 ORDINAL 本次集合中的输入顺序 SOURCE_SEQ 源报文序号,如 GTNO/CKNO/ASNO/CSNO RECORD_VERSION 产生该记录的状态版本 CREATED_AT UPDATED_AT ``` 业务字段按 SIS 原样保存。例如: | 表 | 主要字段 | |---|---| | `FLIGHT_GATE` | `GATE`、`PGOT`、`PGCT`、`GOTM`、`GCTM`、`GTYP` | | `FLIGHT_CHECKIN` | `CHKC`、`CCLS`、`PCOT`、`PCCT`、`COTM`、`CCTM`、`CTYP` | | `FLIGHT_BELT` | `BELT`、`BCLS`、`PCOT`、`PCCT`、`FBAG`、`LBAG`、`BTYP` | | `FLIGHT_STAND_PLAN` | `PSST`、`STST`、`STET` | | `FLIGHT_CHUTE` | `CHUT`、等级、计划/实际时刻、类型字段 | | `FLIGHT_DELAY` | `CODE`、`STRT`、`DURA`、原文说明 | | `FLIGHT_BRIDGE_OP` | `ASNO`、`ABDG`、`ABOP`、`AOTM` | | `FLIGHT_CHOCK_OP` | `CSNO`、`CHID`、`CHST`、`CHTM` | | `FLIGHT_ROUTE_POINT` | `ROUTE_KIND`、`RTNO`、`APCD`、`SCAT`、`SCDT` | 主键推荐为 `(FLID, ORDINAL)`。`SOURCE_SEQ` 是源数据,不默认唯一;相同资源号不代表同一条分配记录,因此不能按 `GATE`、`CHKC` 或 `ABDG` 全局去重。 `FDIV`、`FRET`、`FLAB` 当前是 XSD 中的单值结构,可以保留在主表的明确前缀列中;必须定义显式清除语义。`SRVT`、`VIPF`、`MAFL` 如暂时不需要 SQL 查询,可使用专用集合载荷表保存原始文本;Oracle 11g 使用 `CLOB`,PostgreSQL 使用 `TEXT`,应用层负责结构校验。 ### 3.3 快照版本 ```text SCHD_GEN FDAY 主键 VERSION 单调递增版本 LAST_MESSAGE_ID 生成该版本的消息 CONTENT_DIGEST 可选,内容摘要 UPDATED_AT SCHD_GEN_FLID FDAY FLID PRIMARY KEY (FDAY, FLID) ``` `VERSION` 只是顺序令牌;重放判断必须结合 `LAST_MESSAGE_ID` 或明确的快照身份,不能只比较 `VERSION == expected + 1`。 ### 3.4 处理和投递表 保留 `PROC_STATE`、`MSG_EVENT`、`PUMP_JOB`、`REQ_TRACK` 等现有管道表。建议在 `MSG_EVENT` 中记录 `CMINMSGS_ID`、`STATE_VERSION` 和 `IDEMPOTENCY_KEY`,便于确认同一业务事件是否已经入队。 ## 4. 消息写入语义 解码层把 XML 转换为显式命令,仓储层不再猜测字符串含义: ```text Unchanged 报文没有该字段,不修改数据库 Set(value) 写入或覆盖值 Clear 按协议删除该值 Replace(list) 用完整集合替换旧集合 Apply(item, key) 按协议中的源序号更新单条记录 ``` 通用规则: - 外层标量字段默认是字段级增量合并:未出现不修改,出现则覆盖。 - DNLD 对航班集合使用完整快照替换;集合缺失表示“按 DNLD 规则处理”,不能自动推广为清空所有旧资源。 - 集合出现且被协议定义为快照时,在同一事务中删除该航班该集合的旧明细,再批量写入新明细。 - `GTNO=0`、`CKNO=0` 等清除标记只对协议明确规定的集合生效。 - 非法 JSON、未知字段、超长文本和超过协议容量的输入不得截断后成功。 成功提交后的出站载荷必须从同一份 `nextState` 生成,不能让仓储先丢字段、事件再使用原始输入。 ## 5. 事务流程 ```text 解析并校验报文(锁外) ↓ 取得 PIPELINE_LOCK 行锁 ↓ 复核消息状态和快照身份 ↓ 读取当前航班与 SCHD_GEN ↓ 计算 nextState ↓ 写主表、明细表、快照成员和版本 ↓ 从 nextState 生成 MSG_EVENT ↓ PROC_STATE = SUCCEEDED ↓ 提交 ↓ 补偿回填共享 MySQL / 投递下游 ``` CAS 失败直接回滚并进入可重试的 `FAILED(INFRA)`;不能用相同版本号推断“已经是我的提交”。 提交之后的回填失败只创建或更新补偿记录。重启时先检查 `PROC_STATE` 和 outbox,已经成功的消息不能重新执行航班业务变更。 ## 6. PostgreSQL 与 Oracle 11g 两种数据库使用相同的仓储接口、领域模型和测试样例,分开提供迁移和 SQL 实现。 | 能力 | PostgreSQL | Oracle 11g | |---|---|---| | Upsert | `INSERT ... ON CONFLICT` | `MERGE` 或受控的 UPDATE/INSERT | | 长文本 | `TEXT` | `CLOB` | | 时间 | `TIMESTAMPTZ` | `TIMESTAMP` 配合明确时区转换策略 | | 自增 ID | identity/sequence | sequence | | 锁 | `SELECT ... FOR UPDATE` | `SELECT ... FOR UPDATE` | | 迁移 | PG 专用 Flyway location | Oracle 11g 专用 Flyway location,经目标版本实测 | Oracle 11g 的 `VARCHAR2` 受 4000 字节限制,空字符串按 `NULL` 处理。因此应用命令必须保留 presence/operation 信息,不能把“缺失、空串、NULL、清除”当作同一个状态。JDK 25、Oracle 11.2 补丁、JDBC 驱动、Flyway 和连接池的组合必须在现场环境实测,不能用 PostgreSQL 通过代替 Oracle 验收。 ## 7. 迁移和发布 1. 先增加明细表和主表追踪字段,不删除现有集合列。 2. 从旧列、完整 DNLD 或可信历史输入回填明细表。 3. 逐航班比较数量、顺序、源序号、属性、空值和出站载荷。 4. 切换读路径和写路径到无损模型,保留旧结构一个回退窗口。 5. 确认对拍、回放、备份恢复和查询性能通过后,再删除旧的损失性列和转换代码。 已经执行过直接删除旧集合列的实例无法从新槽位列推回第三个登机门、原始序号或多次操作;需要旧库备份、完整 DNLD 或原始报文恢复。 ## 8. 验收标准 - 三个及以上登机门、非连续源序号、相同资源号不同属性都能完整读回。 - 多次靠桥/撤桥、上/下轮挡和多条延误记录不丢失顺序或资源身份。 - 未出现、显式清空、集合替换、序号删除四种语义有独立测试。 - 主表、明细、`SCHD_GEN`、outbox 和成功终态原子提交;任意一步失败全部回滚。 - 同一快照重放不增加版本和事件;不同快照争用同一版本时只能一个成功。 - 共享信箱回填失败不会重新执行业务状态变更。 - PostgreSQL 与 Oracle 11g 各自通过新建库、升级库、回滚和并发锁测试。 - 查询视图可以提供首个/第二个资源和总数,但完整明细仍可查询。 - 对拍工具按字段、顺序、空值和集合内容比较,不通过忽略差异来“洗绿”。 ## 9. 实施顺序 | 阶段 | 内容 | |---|---| | P0 | 修复快照 CAS、提交后回填状态边界,建立 16 类结构的语义矩阵。 | | P1 | 建立无损明细表、领域命令和 nextState 生成;完成 DNLD 与一个 FLOP 纵向链路。 | | P2 | 完成旧数据回填、双读对拍和兼容查询视图。 | | P3 | 分别完成 PostgreSQL 和 Oracle 11g 的 DDL/DML、迁移和集成测试。 | | P4 | 根据真实查询计划增加索引,确认回退窗口关闭后删除旧损失性结构。 | 本设计对应 ACM2-29 的后续实现;在评审确认前,不应将当前 V1.2.0 固定槽位结构作为生产保真方案。