8.7 KiB
8.7 KiB
RailwaySystem 数据库 V2 关键设计风险深度审查报告 (R-01 ~ R-17)
审查定位:数据库底层引擎兼容性、约束边界、数据一致性与应用层 Service 协同审查
技术基线:KingbaseES V009R003C018 (schema=public,database_mode=mysql)、MyBatis 3.5.x、RuoYi-Vue 基础架构
版本状态:Round 2 审查最终静态收尾版(固化架构决策 A/B,解耦 source_type 语义,清除业务选择型默认值)
风险全景概览
| 风险编号 | 风险主题 | 风险等级 | 影响范畴 | 本次 DDL 应对方案 | 应用层/Service 协同要求 | 决策状态 |
|---|---|---|---|---|---|---|
| R-01 | 旧外键阻塞 ALTER / DROP COLUMN | 高 | 迁移执行期阻断 | 在 DDL 步骤中前置显示 DROP CONSTRAINT IF EXISTS |
严格按迁移编排顺序执行 | 已关闭 |
| R-02 | identity 主键与 MyBatis 兼容性 | 中 | 写入与自增回填 | 采用标准 GENERATED BY DEFAULT AS IDENTITY |
Mapper 继续配置 useGeneratedKeys="true" |
已闭环 |
| R-03 | Kingbase 下 UNIQUE + NULL 语义 | 中 | 数据防重有效性 | 遵循 SQL 标准语义(NULL 不等),关键组合列强制非空 | 业务关键组合列强制非空或用部分索引 | 已闭环 |
| R-04 | id_card_hash 唯一约束对 NULL 行为 |
低 | 学员未采身份证防重 | 建立单列 UNIQUE,允许多行 NULL 不冲突 | 应用层空串 "" 必须显式转为 NULL 入库 |
已闭环 |
| R-05 | 学习记录幂等唯一约束中 NULL 行为 | 中 | 第三方学习数据幂等 | 采用双非空条件部分唯一索引 + 联动 CHECK | 上报接口对具备单号的数据做幂等截流 | 已闭环 |
| R-06 | 排课多维时间冲突无法靠 UNIQUE 实现 | 高 | 教学资源重叠与冲突 | 建立高频复合索引支撑区间检索,增加场地互斥 CHECK | 架构决策 A:排课 Service 事务校验 + 行锁防重 | 已冻结决策 |
| R-07 | 宿舍容量超额无法靠 DB 静态约束 | 高 | 宿舍超员分配超售 | 废除静态 bed_used 字段,加 capacity > 0 CHECK |
架构决策 A:住宿 Service 事务内 SELECT FOR UPDATE |
已冻结决策 |
| R-08 | 成绩项目权重总和 100% 无法靠 CHECK | 中 | 成绩评估规则失衡 | 数据库仅设单行 0~100 标量 CHECK,不跨行聚合 |
开班与考核规则配置 Service 校验有效和等于 100% | 已闭环 |
| R-09 | V2 表与 RuoYi Generator / BaseEntity 差异 | 中 | 代码生成与 CRUD | 数据库采用标准蛇形命名,补齐 del_flag 等审计列 |
后续重构 Entity 继承 BaseEntity 并重新映射 Mapper | 已闭环 |
| R-10 | 现有 9 个 update_time 触发器安全移除 | 中 | 数据更新时间一致性 | 003 脚本显式前置 DROP TRIGGER IF EXISTS 卸载 |
应用层 Service 或 MyBatis 拦截器显式维护更新时间 | 已闭环 |
| R-11 | 跨培训班核心关系一致性 (串班风险) | 高 | 业务逻辑穿透污染 | 不在子表反范式冗余 plan_id,保持规范化模型 |
架构决策 B:Service 强制校验链条一致性 | 已冻结决策 |
| R-12 | 班牌设备与课次教室空间一致性 | 中 | 考勤打卡位置穿透 | 班牌绑定教室,签到关联 lesson 与 board | 考勤 Service 校验班牌物理教室与课次教室完全匹配 | 已闭环 |
| R-13 | 宿舍容量必须考虑培训周期时间重叠 | 高 | 跨期住宿容量误判 | 数据库不保存跨期占用,仅靠 status 会误杀 | 住宿 Service 必须按学员参训 plan 起止区间算重叠有效人数 | 已闭环 |
| R-14 | 报到流水与参训状态变更的原子性 | 高 | 状态与报到记录脱节 | 报到记录表与参训表解耦,依赖业务事务保证 | 报到操作必须在单个本地事务内执行“插报到+改状态” | 已闭环 |
| R-15 | 成绩记录存在性与真实 0 分语义 | 中 | 成绩录入状态失真 | 移除 DEFAULT 0,加非负 CHECK,无行代表未录成绩 | 查询综合成绩时通过左连接或外层逻辑区分未评与 0 分 | 已闭环 |
| R-16 | 业务选择型字段违规设置数据库默认值 | 高 | 业务意图被系统静默猜测 | 彻底清除核验结果、考勤方式、场地类型等 DEFAULT | 调用方必须显式传入真实业务值,严禁自动代填 | 已闭环 |
| R-17 | 两处 source_type 语义混淆与臆造枚举 |
中 | 字典定义混乱与流程固化 | 区分主档来源与参训来源,解除未定字典硬编码 | 独立字典维护,学员主档与名单来源彻底解耦 | 已闭环 |
核心架构决策正式固化 (技术负责人已拍板)
架构决策 A:排课与住宿并发控制方案 (冻结)
- 技术负责人拍板结论: 一期坚决不引入外部排课引擎、时间区间调度中间件或重型规则引擎。
- 实施边界:
- 数据库层:提供针对性的多列复合索引(
idx_lesson_teacher_time,idx_lesson_classroom_time,idx_lesson_site_time),确保范围查询毫秒级响应; - 应用服务层:排课与调课业务由
TrainLessonService在@Transactional事务内执行区间交叉判断(start_time < :newEnd AND end_time > :newStart); - 并发防护:高并发场景下,使用
SELECT ... FOR UPDATE悲观锁或分布式锁锁定教师日历/教室资源/宿舍房间,确保零时间冲突、零超售。
- 数据库层:提供针对性的多列复合索引(
架构决策 B:跨培训班一致性控制规范 (防串班,冻结)
- 技术负责人拍板结论:
坚决不为了防止业务串班而在打卡、成绩、学习记录表中重复冗余增加
plan_id,全面维持数据库 V2 的 3NF 规范化设计,防止数据更新异常与列冗余。 - 实施边界:
由相关业务 Service 在执行写入前执行强一致性校验链条,任何不匹配必须当场抛出业务异常拒绝写入:
1. 考勤打卡: checkin.enrollment.plan_id == lesson.plan_course.plan_id 2. 成绩录入: student_score.enrollment.plan_id == score_item.plan_id 3. 考核项关联课程: IF score_item.plan_course_id IS NOT NULL: score_item.plan_id == plan_course.plan_id 4. 学习时长上报: study_duration.enrollment.plan_id == plan_course.plan_id
深度风险分析与防范对策
R-16: 业务选择型默认值删除与责任边界
- 问题机制:
若数据库为
train_class_report.verify_result配置DEFAULT 1(核验通过),一旦前端或接口未传该字段,数据库将自动判定该学员“核验通过”,造成假通过的安全漏洞;同样,venue_type DEFAULT 1、source_type DEFAULT 1、checkin_way DEFAULT 1等均属于业务必须明确作出的选择。 - DDL 改造方案:
全面移除以下字段的默认值,强制设为
NOT NULL,迫使应用层显式传参:train_enrollment.source_type SMALLINT NOT NULLtrain_enrollment.accommodation_required SMALLINT NOT NULLtrain_lesson.venue_type SMALLINT NOT NULLtrain_student_score.source_type SMALLINT NOT NULLtrain_student_checkin.checkin_way SMALLINT NOT NULLtrain_class_report.verify_result SMALLINT NOT NULL
- 生命周期状态说明:
生命周期的初始状态仍保留合理默认值,例如:
train_class_plan.plan_status:DEFAULT 0(未开始)train_enrollment.enrollment_status:DEFAULT 0(待报到)train_accommodation.accommodation_status:DEFAULT 0(预分配,由 1 修正为 0)train_dorm_auth.face_sync_status:DEFAULT 0(待下发)train_dorm_auth.retry_count:DEFAULT 0train_student_checkin.manual_flag:DEFAULT 0(自动)
R-17: 两个 source_type 语义彻底解耦
系统在两张核心表中存在同名字段 source_type,必须从业务与字典层面彻底隔离,杜绝混用:
-
train_student.source_type(学员长期主档来源):- 业务含义:自然人作为学员进入铁路培训系统长期资源库的注册渠道。
- 典型枚举:
1: 管理员手工录入2: Excel 花名册批量导入3: 铁路内部人力/教务业务平台同步
- 对应字典:
train_student_source
-
train_enrollment.source_type(参训名单进入来源):- 业务含义:已在库学员被选拔/加入到具体某一期培训班名单的业务途径。
- 设计准则: 清除“自主报名/单位推荐/统一调训”等未经正式拍板的臆造流程枚举,DDL 注释明确标记:“参训名单来源,具体字典值由 train_enrollment_source 定义”。
- 对应字典:后续由独立业务字典
train_enrollment_source承载,两字段严禁共用同一字典。