# 规范化与完整性说明 ## 1. 这份文档是干什么的 本文件用于回答数据库课程设计中老师最常问、但在工程项目里常被弱化的几个问题: - 什么叫完整性约束 - 什么叫 1NF / 2NF / 3NF / BCNF - 什么时候冗余是错误的,什么时候冗余是可以解释的 - 如何把这些概念用到“校园生活平台”这次课设里 如果后续答辩时需要用比较学院派的语言解释设计,这份文档可以作为统一口径。 ## 2. 完整性约束是什么 数据库课程里常说的完整性,通常分为三类。 ### 2.1 实体完整性 核心问题是: - 每条记录如何被唯一标识 - 主键是否允许为空 在本课设中,典型例子有: - `profiles.id` - `facility_reservations.id` - `course_resources.id` - `user_roles(user_id, role_id)` ### 2.2 参照完整性 核心问题是: - 一条记录引用的另一条记录是否真的存在 - 删除父记录时应该怎样处理子记录 典型例子有: - 房间必须属于某个楼房 - 预约必须属于某个房间 - 课程必须属于某个专业 - 用户角色关系必须引用真实用户和真实角色 这类问题通常通过物理外键解决。 ### 2.3 用户定义完整性 核心问题是: - 某些业务规则能不能直接让数据库拒绝非法数据 典型例子有: - `student_id` 必须是 16 位数字 - `end_at > start_at` - 文件资源和外链资源字段必须互斥 - 下载计数不能小于 0 这类问题通常通过: - `CHECK` - `UNIQUE` - 枚举 - 触发器 来表达。 ## 3. 函数依赖、候选键与主属性 ### 3.1 什么叫函数依赖 数据库课程里说的“函数依赖”,通常记作: - `X -> Y` 它表示: - 如果两条记录在属性组 `X` 上取值相同,那么它们在属性组 `Y` 上也必须相同 直观理解就是: - `X` 能决定 `Y` 例如: - `roles.id -> roles.code, roles.name, roles.description` - `roles.code -> roles.id, roles.name, roles.description` - `courses.id -> courses.major_id` 这里要特别注意: - 你前面提到的术语应当是“函数依赖”,不是“关系依赖” - 对本次课程设计来说,老师最常问的是函数依赖、部分依赖、传递依赖和候选键 - 多值依赖、连接依赖通常属于 4NF / 5NF 语境,本项目目前不是答辩重点 ### 3.2 什么叫候选键 候选键可以理解为: - 能唯一标识一条元组的最小属性组 例如: - `roles` 的候选键可以解释为: - `id` - `code` - `user_roles` 的候选键可以解释为: - `(user_id, role_id)` 这里的“最小”很重要,意思是: - 去掉其中任何一个属性后,就不再能唯一标识记录 ### 3.3 什么叫主属性和非主属性 - 主属性: - 出现在任一候选键中的属性 - 非主属性: - 不出现在任何候选键中的属性 例如在 `roles(id, code, name, description, created_at, updated_at)` 中: - 若把 `id` 和 `code` 都视为候选键 - 那么主属性有: - `id` - `code` - 非主属性有: - `name` - `description` - `created_at` - `updated_at` 例如在 `user_roles(user_id, role_id, created_at)` 中: - 候选键是 `(user_id, role_id)` - 主属性有: - `user_id` - `role_id` - 非主属性有: - `created_at` ### 3.4 这些概念和范式是什么关系 之所以要先讲函数依赖和候选键,是因为: - 2NF 讨论的是非主属性是否完全依赖整个候选键 - 3NF 讨论的是非主属性是否传递依赖于候选键 - BCNF 讨论的是每个决定因素是否本身就是候选键 也就是说: - 不把“候选键 / 主属性 / 非主属性 / 函数依赖”讲清楚 - 3NF 往往就只能停留在口头判断,显得不够学院派 ### 3.5 本课设里最值得明确写出的函数依赖例子 建议在正式报告里至少把下面几类关系写清楚: 1. 平台基础: - `roles.id -> code, name, description` - `roles.code -> id, name, description` 2. 桥接关系: - `(user_id, role_id) -> created_at` - `(major_id, user_id) -> created_at` 3. 课程资源中的关键风险点: - `courses.id -> courses.major_id` - 因此若 `course_resources` 同时保存 `course_id` 和 `major_id`,就必须解释冗余与一致性保证 ## 4. 范式到底是什么意思 ### 4.1 第一范式(1NF) 直观理解: - 表中的每个字段都应该是不可再分的原子值 - 不要把一串列表塞进一个字段里 例如: - 不要在预约表里写一个 `participant_ids = "u1,u2,u3"` - 正确做法是单独建 `facility_reservation_participants` ### 4.2 第二范式(2NF) 直观理解: - 如果一个表用了复合主键,那么非主属性必须依赖整个主键,而不是只依赖其中一部分 例如桥接表: - `user_roles(user_id, role_id)` - `major_leads(major_id, user_id)` 它们本身很容易满足 2NF,因为表里几乎没有额外属性,或者额外属性直接依赖整条联系。 ### 4.3 第三范式(3NF) 直观理解: - 非主属性不能再依赖于其他非主属性 - 也就是不要出现“主键 -> A -> B”这种传递依赖 例如: - 如果 `course_id -> major_id` - 那么在 `course_resources` 里同时保留 `course_id` 和 `major_id` - 就要非常小心,因为这容易被认为带有传递依赖或冗余 ### 4.4 BCNF 可以把 BCNF 理解为比 3NF 更严格: - 每一个决定因素都应当是候选键 在这次课设里,很多桥接表天然就比较接近 BCNF,例如: - `user_roles` - `role_permissions` - `major_leads` - `facility_reservation_participants` ## 5. 冗余是不是一定错 不是。 但老师通常接受的前提是: - 你知道它是冗余 - 你知道为什么保留 - 你知道怎么保证一致性 ## 6. 本课设里三类常见“可解释冗余” ### 6.1 派生关系表 典型例子: - `department_closure` 它不是随便重复存数据,而是为了高效查询“某部门及其全部子部门”。这类表通常可以解释为: - 为层级查询服务的派生关系表 - 属于数据库设计优化,而不是粗糙重复 ### 6.2 审计快照字段 典型例子: - `audit_logs.actor_name` - `audit_logs.actor_email` - `audit_logs.actor_roles` 这类字段的目的不是减少联表,而是保留“操作发生当时”的历史语义,因此可以解释为: - 历史真实性优先的审计快照 ### 6.3 统计或范围控制冗余 典型例子: - `course_resources.major_id` - `course_resource_score_events.major_id` - `course_resource_score_events.user_id` 这类字段常用于: - 方便按专业做过滤 - 方便做排行榜和统计 但它们一定要配套解释: - 当前如何通过复合外键保证与资源主体的一致性 - 为什么保留这些冗余仍然有统计、范围控制或排行榜上的现实价值 ## 7. 三个主线模块怎么讲“范式” ### 7.1 平台基础子系统 可以这样说: - `roles`、`permissions`、`user_roles`、`role_permissions` 等核心关系基本满足 3NF - 多对多关系拆分清晰 - `department_closure` 属于派生关系表,不作为普通业务冗余看待 - `audit_logs` 中存在审计快照字段,这是有意设计 ### 7.2 功能房预约模块 可以这样说: - `facility_buildings`、`facility_rooms`、`facility_reservations`、`facility_bans` 主体上基本满足 3NF - `facility_reservation_participants` 作为桥接表设计合理 - 时间冲突、申请人参与关系、最少人数规则已经下沉到数据库层 - 因而这个模块现在更适合被当成“复杂业务约束如何数据库化”的案例,而不是“尚未完成的增强项” ### 7.3 课程资源分享模块 可以这样说: - `majors`、`courses`、`major_leads` 等主体上基本满足 3NF - 主体数据和事件数据分层合理 - `course_resources.major_id` 以及 `course_resource_score_events.major_id/user_id` 是受控冗余 - 这些受控冗余已经由复合外键锁定一致性,而不是只靠服务层维护 - 这部分是本模块最需要解释清楚的范式问题 ## 8. 老师最喜欢听到的表达 答辩时,下面这些说法通常比“工程上方便”更容易得到认可: - “核心主数据表尽量满足 3NF” - “多对多关系通过联系表拆分” - “关键引用关系采用显式物理外键” - “关键业务规则尽量通过数据库约束表达” - “对保留的冗余字段给出了一致性保障机制” - “审计表和派生关系表不简单按普通主数据表去评价” ## 9. 老师最不喜欢听到的表达 - “这个外键我懒得建,代码里判断就行” - “这个字段重复了但没关系” - “这个规则只要前端不让填错就行” - “这个表虽然很乱但是能跑” 这些说法都容易让数据库课程设计显得不够规范。 ## 10. 当前最值得优先加强的地方 结合本项目现状,数据库代码层的主线增强已经基本完成;接下来最值得优先强化的是答辩中的学院派表达: 1. 把候选键、主属性、非主属性和关键函数依赖写成更正式的表述 2. 把“受控冗余 + 复合外键锁定一致性”的逻辑讲得更顺 3. 把“事务协同”和“数据库硬约束”区分清楚 4. 把弱引用审计、派生关系表、快照字段这些设计取舍讲清楚 ## 11. 一句话总结 如果你在答辩时能把这句话讲清楚,老师通常会觉得你是按数据库课程的思路在做: “我的核心主数据关系尽量满足 3NF,多对多关系做了拆分,关键引用建立了物理外键,关键业务规则尽量下沉到数据库;对于闭包表、审计快照和统计维度冗余,我都能说明为什么保留以及如何保证一致性。”