【个人学习||数据库】

《计算机数据库》满分掌握指南

默认假设:本文按中国高校中最常见的《数据库系统概论 / 数据库原理 / 数据库系统》课程体系写作,面向基础一般、希望先建立整体框架,再把考试成绩稳定提升到 80 分以上的学习者。全篇以关系数据库 为主线,适度补充 MySQL 工程直觉NoSQL 入门对比,重点覆盖课程考试最核心、最常考、最能提分的内容。

阅读方式建议:第一次通读,先抓"数据库到底解决什么问题、各模块因果关系是什么";第二次再盯公式、定义、题型和易错点;第三次结合刷题与背诵,把理解转成得分。


学习前说明

很多同学学数据库,会在两个地方同时卡住。

第一,看起来什么都在讲,但不知道主线是什么。教材里既有概念模型、关系代数、SQL、范式、索引、事务、恢复、安全、分布式,又有一堆定义、符号、规则,结果学到后面只记住了零碎术语,脑中没有整体图景。

第二,会写一点 SQL,但不会解释数据库为什么这样设计。一遇到考试里的"为什么要规范化""为什么会产生幻读""为什么 B+ 树适合做索引""为什么日志能恢复""为什么视图能增强安全性",就容易答不完整。

这份文档不按教材章节死背,而是按"问题从哪里来,系统为什么这样设计,设计又带来了什么后果"的因果顺序来讲。因为数据库不是一堆互相孤立的知识点,它本质上是在解决四个持续出现的问题:

  1. 数据怎么描述,才能让人和机器都能理解。
  2. 数据怎么存和怎么查,才能又正确又高效。
  3. 很多人同时读写时,怎么尽量不乱。
  4. 系统出故障时,怎么尽量恢复。

如果你把这四个问题抓住,数据库这门课的大部分内容都会自动归位。


学科一句话概括

计算机数据库,就是研究"如何用一种可组织、可查询、可共享、可恢复、可约束的方式管理大量数据"的学科。

再说得更直白一点:

  • 文件能存数据,但不擅长高效查询、多人共享和一致性控制。
  • 数据库不只是"存起来",而是要同时解决建模、查询、约束、并发、恢复、优化这六件事。
  • 所以数据库系统不是单纯的数据仓库,而是一套关于"数据如何被可靠使用"的工程与理论体系。

学科定位

1. 这门学科研究什么

数据库研究的是:现实世界中的信息,怎样抽象成数据结构;这些数据怎样被组织、存储、查询、更新、共享和保护;当系统规模变大、并发变高、故障出现时,如何保持正确与高效。

2. 它解决什么核心问题

这门学科真正解决的核心问题,不是"怎么记住 SQL 语法",而是下面这些更本质的问题:

  • 现实中的对象和关系,如何变成结构化数据。
  • 多张表之间如何表达联系,才能避免混乱和冗余。
  • 用户如何提出查询需求,系统如何高效执行。
  • 大量数据如何存储在磁盘上,如何通过索引快速定位。
  • 多个事务同时操作同一批数据时,如何尽量保证一致性。
  • 系统崩溃、断电、宕机后,如何把数据恢复回来。

3. 为什么它在课程体系或考试中重要

数据库的重要性有两层。

第一层是课程层面。它是数据结构、操作系统、计算机网络之后的一门综合课。你会发现:

  • 没有数据结构,就很难理解索引为什么会快。
  • 没有操作系统直觉,就很难理解锁、并发和恢复。
  • 没有计算机网络直觉,就很难理解客户端、服务端、分布式数据库与复制。

第二层是工程层面。后端开发、数据分析、数据工程、测试、运维、系统设计几乎都绕不开数据库。考试考的是概念,但工作里你迟早会面对:

  • 表该怎么设计;
  • 索引该怎么加;
  • 查询为什么慢;
  • 为什么会死锁;
  • 为什么一条 UPDATE 会影响很多行;
  • 为什么数据能在崩溃后恢复。

所以数据库是少数几门"课程知识和工程实践高度重叠"的课。

4. 学习这门学科最容易卡住的地方

数据库最容易卡住,不是因为它难在计算,而是难在层次很多、抽象跨度大

小白常见的卡点有:

  • 把"数据库""数据库管理系统""表""文件""模式"混在一起。
  • 只会背 1NF、2NF、3NF 定义,却不会判断函数依赖和主属性。
  • SQL 会写简单查询,但一遇到连接、分组、子查询就乱。
  • 知道事务四大特性 ACID,但不知道为什么会出现脏读、不可重复读、幻读。
  • 听过 B+ 树、日志、MVCC、死锁这些词,却无法把它们放进一条完整因果链里。

5. 这门学科与相近学科的因果关联

数据库与其他课程不是平行关系,而是互相支撑。

相关学科 它提供什么前置能力 数据库反过来怎样使用这些能力
数据结构 树、哈希、堆、查找、排序 索引结构、哈希文件、连接算法、优化器估价
操作系统 进程、并发、锁、缓冲、磁盘、日志 事务并发控制、缓冲区管理、崩溃恢复
计算机网络 客户端/服务端、请求响应、分布式通信 数据库连接、主从复制、分布式事务
软件工程 需求建模、模块边界、可维护性 E-R 建模、范式设计、约束设计
离散数学 关系、集合、逻辑、推理 关系模型、关系代数、关系演算、依赖推理
概率统计 成本估计、选择率、分布 查询优化中的代价模型与统计信息

一个非常关键的认识是:数据库是把离散数学的关系思想、数据结构的组织方法、操作系统的并发与恢复机制、软件工程的建模思想融合在一起的课程。


学科知识地图

下面这张图不是教材目录,而是按因果顺序排列的"知识地图"。请先看顺序,再看模块名。

模块 重要程度 模块核心问题 为什么必须先学它 会影响后面哪些模块
模块 1:数据库系统基础与三级模式 ★★★★★ 数据库到底是什么,DB、DBMS、模式、实例分别是什么 这是全课语言层,先分清对象才不会概念混乱 所有模块
模块 2:数据模型、E-R 模型与关系模型 ★★★★★ 现实世界如何抽象成可存储的数据结构 没有建模,就谈不上设计表和约束 SQL、范式、设计题
模块 3:关系代数、关系演算与查询思维 ★★★★ 查询本质上在做哪些集合和逻辑运算 先懂"查什么",再学"怎么写 SQL"更稳 SQL、优化、考试证明题
模块 4:SQL 核心操作体系 ★★★★★ 如何定义、查询、更新、控制数据库对象 是考试与实操的高频中心 设计、优化、权限、视图
模块 5:关系规范化与数据库设计 ★★★★★ 怎样减少冗余、避免异常、让结构合理 解决"为什么表要这样拆"的根问题 设计题、综合题
模块 6:存储结构、文件组织与索引 ★★★★★ 数据到底怎样放在磁盘里,为什么索引能加速 解释数据库性能的核心 查询优化、工程实践
模块 7:查询处理与优化 ★★★★ SQL 是怎样被翻译、选择执行路径的 解释"同样的 SQL 为什么快慢不同" 索引题、优化题
模块 8:事务与并发控制 ★★★★★ 多个用户同时操作时怎么尽量不乱 数据库正确性的核心 恢复、锁、隔离级别
模块 9:故障恢复机制 ★★★★ 崩溃后为什么还能恢复数据 解释日志、检查点、undo/redo 工程实践、综合题
模块 10:完整性、安全性、视图与授权 ★★★★ 怎样防止非法数据和非法访问 考试喜欢单独出定义与应用题 SQL、设计、安全
模块 11:分布式数据库与 NoSQL 概览 ★★★ 当单机关系数据库不够时怎么办 用来建立现代数据库视野 面试、扩展题
模块 12:综合设计与工程落地 ★★★★ 如何把前面知识串成一套完整系统思维 最能体现"真正学会" 综合题、项目题

模块前后依赖关系

可以把整门课理解成下面这条链:

  1. 先搞清 数据库系统到底是什么
  2. 再学会 如何建模现实世界
  3. 然后理解 查询本质是什么
  4. 接着掌握 SQL 如何表达查询和更新
  5. 有了表之后,会发现 冗余和异常问题,于是需要范式和设计方法。
  6. 表一旦大起来,就要解决 存得下、查得快,所以需要索引和存储结构。
  7. 查询越来越复杂时,还要考虑 系统怎样选更优执行路径
  8. 多人同时改数据时,会出现 一致性问题,于是需要事务与并发控制。
  9. 机器一旦崩溃,又要考虑 恢复机制
  10. 系统要真正上线,还离不开 完整性、安全性和权限控制
  11. 当单机顶不住时,再进入 分布式与 NoSQL 的世界。

如果你把这条链看懂了,数据库不再是"十几个彼此无关的章节",而是一套连续演化的问题解决过程。


推荐学习顺序

顺序一:框架优先

如果你现在几乎是零基础,建议按下面顺序走:

  1. 先看"数据库系统基础"
  2. 再看"E-R 模型与关系模型"
  3. 再看"SQL 核心查询"
  4. 接着看"范式与设计"
  5. 再看"索引与存储"
  6. 然后学"事务与并发"
  7. 最后补"恢复、安全、分布式"

这个顺序的原因很简单:

  • 如果你先学 SQL 而不懂模型,就会把数据库当作"写语法题"。
  • 如果你先学范式而不懂函数依赖,就会变成死背定义。
  • 如果你先学事务而不懂更新和共享,就很难体会为什么会冲突。
  • 如果你先学恢复而不懂事务,就很难理解日志到底在保护什么。

顺序二:考试优先

如果离考试已经不远,要快速拿分,建议优先级如下:

  1. 关系模型 + 码与约束 + 函数依赖 + 规范化
  2. SQL 查询、连接、嵌套、分组、视图
  3. 事务 ACID、并发问题、隔离级别、封锁协议
  4. 索引、B+ 树、聚簇与非聚簇
  5. 恢复、日志、检查点
  6. 完整性、安全性、授权
  7. 分布式与 NoSQL 作为加分项

顺序三:工程理解优先

如果你除了考试,还想把数据库真正用起来,可以这样走:

  1. 数据库基础
  2. SQL
  3. 表设计与范式
  4. 索引与执行计划
  5. 事务与锁
  6. 恢复与日志
  7. 复制与扩展
  8. NoSQL 与架构比较

核心模块详解,适合小白看懂的比喻

下面的每个核心模块,都统一按以下顺序展开:

  1. 本模块解决什么问题
  2. 核心概念、术语、符号
  3. 用通俗比喻或苏格拉底问答解释"为什么"
  4. 关键规则、定理、方法与适用条件
  5. 易混点与常见误区
  6. 考试常见问法
  7. 典型例题
  8. 解题思路
  9. 简短总结

为了方便后续扩写,先列出模块标题:

模块 1:数据库系统基础与三级模式结构

1. 本模块解决的问题

这一模块解决的是最根本的问题:我们到底在学什么对象

很多人一开始就把"数据库""表""数据库软件""MySQL""模式""实例"混成一锅。结果后面一遇到"三级模式两级映像""逻辑独立性""模式变化不影响应用程序"等说法,就全靠背。

这一模块要回答的是:

  • 数据和数据库有什么区别?
  • 数据库和数据库管理系统有什么区别?
  • 数据库系统为什么比文件系统更高级?
  • 为什么教材反复强调"数据独立性"?
  • 三级模式结构到底在抽象什么?

如果这一模块没学稳,后面很多定义都会像空中楼阁。

2. 核心概念及专业术语
术语 含义 直白解释
数据(Data) 对现实世界事物的符号记录 例如学号、姓名、成绩、日期
数据库(DB) 长期存储在计算机内、有组织、可共享的数据集合 不是单条记录,而是一整套结构化数据
数据库管理系统(DBMS) 管理数据库的软件系统 例如 MySQL、Oracle、PostgreSQL
数据库系统(DBS) 数据库 + DBMS + 应用程序 + 管理员 + 硬件环境 真正运行起来的完整生态
模式(Schema) 数据库的逻辑结构与特征描述 更像"蓝图"
实例(Instance) 某一时刻数据库中的实际数据 更像"蓝图上的当前住户情况"
外模式 用户看到的局部逻辑结构 不同用户看到不同子集
模式 全体数据的逻辑结构 全局逻辑定义
内模式 数据在物理存储层的组织方式 数据在磁盘上怎么放
两级映像 外模式/模式映像、模式/内模式映像 用来隔离变化
逻辑独立性 逻辑结构变化尽量不影响应用 例如加列、拆表后尽量少改程序
物理独立性 物理存储变化尽量不影响逻辑层 例如换索引、换存储方式,程序不该跟着重写
3. 核心概念的通俗理解
先讲直觉:为什么文件系统不够

假设学校最早用 Excel 管理学生数据。

  • 教务处有一个 Excel
  • 财务处有一个 Excel
  • 宿舍管理也有一个 Excel

这时候会发生什么?

  • 同一个学生的信息在多个文件里重复出现
  • 修改手机号时,可能改了教务处却没改财务处
  • 两个人同时改同一个文件时容易冲突
  • 想查"所有选了数据库课程且成绩大于 85 的大二学生"会很麻烦
  • 文件一多,就不知道哪个才是最新版

于是我们发现,仅靠文件存数据,最大的问题不是"存不下",而是:

  • 冗余太多
  • 共享太差
  • 一致性难保证
  • 查询与更新效率低
  • 应用程序和数据绑定太死

所以数据库系统本质上是从"随便存"升级到"系统化管理"。

苏格拉底式追问:为什么要区分 DB、DBMS、DBS

问:数据库不是 MySQL 吗?

答:不是。MySQL 是数据库管理系统,是"管理工具";数据库是被它管理的数据集合。

问:那数据库系统又是什么?

答:如果只有 MySQL 软件,没有表、没有用户、没有服务器、没有管理员、没有业务程序,那还不算一个真正运转的数据库系统。数据库系统是完整运行环境。

问:为什么要分这么细?

答:因为不同层次出问题时,处理方法不同。

  • 如果是数据内容错了,是数据库层问题。
  • 如果是事务锁冲突,是 DBMS 机制问题。
  • 如果是程序 SQL 写错,是应用层问题。
  • 如果是服务器磁盘坏了,是硬件环境问题。

只有把层次分清,分析问题才不乱。

三级模式结构的比喻

可以把三级模式理解成"同一栋大楼的三个视角":

  • 外模式:每个住户看到自己房间的布局。学生只看成绩和课表,财务只看收费信息。
  • 模式:整栋楼的总设计图。所有房间怎么组成、管道怎么接、楼层如何划分。
  • 内模式:施工和材料图。钢筋、水管、电线、承重墙如何布置。

为什么要分三层?

因为现实中经常发生变化:

  • 用户界面会变
  • 表结构会变
  • 存储方式会变

如果三者直接绑死,任何一层变化都会引发整个系统重写。三级模式的目的,就是隔离变化

4. 关键规则、方法与因果关系
4.1 数据独立性的本质

数据库系统追求的一个核心目标是:变化局部化

所谓数据独立性,本质上是要让不同层次的变化,尽量只在本层处理,不要级联影响全部系统。

类型 含义 典型变化 理想影响
物理独立性 内模式变化不影响模式 文件组织改变、索引调整、存储位置变化 应用程序尽量不改
逻辑独立性 模式变化不影响外模式 增加属性、拆分关系、重构逻辑结构 用户视图和程序尽量少改

为什么物理独立性通常更容易实现,而逻辑独立性更难?

因为物理层变化通常不改变"数据含义",只是改"怎么存";而逻辑层变化会直接影响"数据长什么样",当然更容易冲击应用。

4.2 数据库系统的主要特点

教材常写四条,你要会背,更要会解释:

  1. 数据结构化
  2. 数据共享性高,冗余度低,易扩充
  3. 数据独立性高
  4. 数据由 DBMS 统一管理和控制

为什么结构化重要?

因为只有当数据结构被统一定义,系统才能做约束检查、查询优化、并发控制和恢复。

为什么共享性高会带来新问题?

因为一旦共享,多个用户会同时读写,数据库就必须处理并发控制和安全问题。这说明数据库的很多复杂机制,其实是"共享"带来的副作用。

4.3 DBMS 的主要功能

DBMS 不只是"能执行 SQL",它至少要承担:

  • 数据定义功能:定义模式、表、约束、视图
  • 数据操纵功能:查询、插入、删除、更新
  • 数据库运行管理:并发控制、恢复、安全性检查
  • 数据组织、存储和管理:文件、页、索引、缓冲
  • 数据库建立与维护:备份、重构、性能监控

你会发现,后面整门课其实就是在一条条拆解这些功能。

5. 易混点与常见误区
误区 1:把数据库等同于某个软件

错因:把工具当对象。

纠正:

  • 数据库是数据集合
  • DBMS 是管理数据库的软件
  • 数据库系统是更大的整体
误区 2:把模式和实例混淆

错因:以为"学生表"本身就是数据。

纠正:

  • 模式描述结构,例如"学生表有学号、姓名、年龄"
  • 实例描述当前内容,例如"2026 年 4 月 14 日这张表里有 3000 条学生记录"
误区 3:把外模式理解成前端页面

外模式不是网页,而是用户看到的数据逻辑视图。一个报表、一个应用程序需要的数据子集,都可以看作外模式。

误区 4:两级映像不是多余概念

很多人觉得"映像"只是背诵项。其实它解决的是变化如何隔离的问题,没有映像就没有数据独立性。

6. 考试常见问法
  1. 什么是数据库、DBMS、数据库系统?三者有何联系与区别?
  2. 数据库系统有哪些主要特点?
  3. 什么是三级模式结构?什么是两级映像?
  4. 什么是数据独立性?逻辑独立性和物理独立性有何区别?
  5. 文件系统与数据库系统相比有哪些优缺点?
  6. 模式与实例的区别是什么?
7. 典型例题

例题 1:简述数据库系统与文件系统相比的优势。

参考分析:

文件系统也能存数据,为什么还需要数据库系统?因为当数据规模变大、使用者增多、查询条件复杂时,文件系统很容易出现冗余、共享差、一致性差、难维护、难恢复的问题。数据库系统通过模式定义、统一管理、并发控制、恢复机制、约束机制,提高了数据组织性和可管理性。

标准答题点:

  • 数据结构化程度高
  • 共享性高,冗余低
  • 数据独立性高
  • 统一控制数据的安全性、完整性、并发和恢复

例题 2:说明三级模式结构如何支持数据独立性。

答案思路:

  1. 说明三级模式分别对应用户层、逻辑层、物理层
  2. 说明外模式/模式映像支持逻辑独立性
  3. 说明模式/内模式映像支持物理独立性
  4. 强调"隔离变化"的核心作用
8. 解题思路

本模块的题目,通常不难算,但容易答空。标准写法建议按下面模板:

  1. 先下定义
  2. 再说作用
  3. 最后说区别或联系

例如答"数据独立性"时,不要只写一句"应用与数据分离",而要写:

  • 什么是数据独立性
  • 为什么需要它
  • 分为哪两类
  • 分别靠什么机制实现
9. 简短总结

这个模块最重要的不是记住名词,而是明白:数据库系统的价值,在于把数据从"散乱文件"提升为"可抽象、可共享、可控制、可恢复"的系统对象。

而三级模式结构的核心意义,不是背三层名字,而是理解:数据库的设计目标之一,就是让变化不要牵一发而动全身。

模块 2:数据模型、E-R 模型与关系模型

1. 本模块解决的问题

数据库不是先有表,再有数据;而是先有对现实世界的理解,再把它抽象成表。

这一模块解决的问题是:

  • 现实世界中的"人、事、物、关系"怎样变成数据结构?
  • 为什么数据库设计不是随便建几张表就行?
  • 什么叫实体、属性、联系、码、外键、参照完整性?
  • 为什么关系模型会成为主流?

一句话:这一模块在教你如何把真实世界翻译成数据库世界

2. 核心概念及术语
2.1 数据模型的三层理解
层次 关注点 典型模型
概念模型 从用户视角描述现实世界 E-R 模型
逻辑模型 从数据库视角描述数据结构 关系模型、层次模型、网状模型、对象模型
物理模型 从存储视角描述数据落盘方式 页、记录、索引、文件组织
2.2 E-R 模型核心术语
术语 定义 通俗解释
实体(Entity) 可区分、可描述的客观对象 学生、课程、教师
实体型 一类实体的共同特征 "学生"这一类对象
实体集 同类实体的集合 所有学生
属性(Attribute) 描述实体特征的数据项 学号、姓名、年龄
联系(Relationship) 实体之间的关联 学生选课程
联系型 联系的类别 "选修"这种关系
码(Key) 能唯一标识实体的属性组 学号
弱实体 必须依赖其他实体才能存在的实体 订单明细依赖订单
2.3 联系的基数
联系类型 含义 例子
1:1 一个实体最多对应一个另一个实体 一个人对应一个身份证号
1:N 一个实体对应多个另一个实体 一个班级有多个学生
M:N 多个实体彼此多对多 多个学生选多门课程
2.4 关系模型核心术语
术语 定义 说明
关系(Relation) 一组元组的集合 通常可理解为一张表
元组(Tuple) 关系中的一行 一条记录
属性(Attribute) 关系中的一列 字段
域(Domain) 属性的取值范围 年龄应是整数且合理范围
候选码 能唯一标识元组的最小属性组 学号;若手机号也唯一也可能是候选码
主码 从候选码中选出的主要标识 主键
主属性 包含在任一候选码中的属性 判定 2NF、3NF 常用
外码 引用其他关系主码的属性 选课表中的学号、课程号
参照完整性 外码取值必须有效或为空 不能选不存在的课程
3. 先讲直觉,再讲定义
3.1 为什么要先建模,再建表

很多初学者会直接这样想:

  • 学生一张表
  • 课程一张表
  • 成绩一张表

听上去没错,但问题是:为什么是这三张?字段怎么定?多对多怎么办?哪些字段能做主键?哪些关系需要单独建表?

这些问题如果不先建模,就容易凭感觉拍脑袋。建模的目的,就是先在概念层澄清:

  • 现实里有哪些核心对象
  • 它们之间是什么关系
  • 哪些信息属于对象自身,哪些属于对象之间的联系
3.2 苏格拉底式提问:为什么"选课"往往要单独建表

问:学生和课程之间有什么关系?

答:一个学生能选多门课,一门课也能被很多学生选。

问:那这是 1:N 还是 M:N?

答:是 M:N。

问:为什么不能把课程号直接写在学生表里?

答:因为一个学生可能选多门课,字段会重复、扩展性差,也不符合关系模型"一格一值"的要求。

问:为什么不能把学生号数组写在课程表里?

答:同理,一列里塞多个值会破坏原子性,也很难查询。

问:那怎么办?

答:把"学生选课程"这个联系本身抽出来,形成一张中间关系表,例如 SC(学号, 课程号, 成绩)

这就是 E-R 到关系模型最经典的落地过程。

3.3 为什么关系模型成为主流

历史上数据库模型不只关系模型一种,还出现过层次模型、网状模型等。但关系模型最终成为主流,是因为它有几个极强的优点:

  • 结构简单,统一用二维表表达
  • 以集合论和逻辑为基础,理论清晰
  • 数据独立性较强
  • 查询语言表达能力强
  • 易于进行约束、优化和标准化

换句话说,关系模型不是"最贴近现实"的模型,但它是最适合统一管理与查询的模型。

4. 关键规则、定理、方法
4.1 关系的三个基本性质

教材常考关系的性质。要会背,也要会解释。

  1. 每个属性值必须是原子的、不可再分
  2. 行顺序通常无关
  3. 列顺序通常无关
  4. 元组不能完全重复

为什么"一格一值"如此重要?

因为数据库的查询、连接、比较、排序、索引都默认一个格子里放一个可比较的值。如果一个格子里塞"张三,李四,王五",那关系运算和 SQL 操作都会变得混乱。

4.2 三类完整性约束
完整性类型 含义 常见实现方式
实体完整性 主码不能为空且唯一 主键约束
参照完整性 外码要么为空,要么引用有效主码 外键约束
用户定义完整性 业务规则约束 CHECK、触发器、程序逻辑

为什么完整性约束重要?

因为数据库不是"能存就行",而是要防止非法数据进入系统。约束的作用就是把"错误尽量拦在数据库入口"。

4.3 E-R 图转换为关系模式的常见规则

这是考试设计题高频内容。

规则 1:一个实体型通常转换成一个关系模式。

例如:

  • 学生实体 Student(学号, 姓名, 专业)
  • 课程实体 Course(课程号, 课程名, 学分)

规则 2:1:1 联系,可并入任一方,也可单独成表。

通常看联系是否有自己的属性,或者哪一方参与更完全。

规则 3:1:N 联系,通常把 1 方主键作为外键放到 N 方。

例如"班级-学生",把班级号放到学生表。

规则 4:M:N 联系,必须单独建表。

例如:

text 复制代码
SC(学号, 课程号, 成绩)

这里主键常取 (学号, 课程号),同时它们也是外键。

4.4 码的判断方法

考试常让你判断候选码、主码、外码。思路是:

  1. 先看什么属性或属性组能唯一标识一行
  2. 再看是否"最小"
  3. 能唯一且最小的,就是候选码
  4. 从候选码中选一个做主码

要注意:超码不一定是候选码。因为超码可能还包含多余属性。

5. 易混点与常见误区
误区 1:实体和关系混淆

"学生"是实体,"学生选课"是联系。很多人会把"选课"误认为实体。判断标准是:它是否独立存在,还是描述对象之间的关系。

误区 2:关系模型里的"关系"不是"联系"

这是一个经典语言陷阱。

  • E-R 模型中的"联系"指实体之间的关联
  • 关系模型中的"关系"指一张二维表所表示的数据集合
误区 3:候选码与主码混淆

候选码可能有多个,主码通常只选一个。主码是"被选中担当主标识"的候选码。

误区 4:外键不是"另一张表任意字段"

外键是指向其他关系主码(或候选码)的属性,不是随便引用一个字段就算外键。

误区 5:多对多关系不拆表

这是数据库设计里最常见的初学者错误之一。

6. 考试常见问法
  1. 什么是数据模型?概念模型、逻辑模型、物理模型有什么区别?
  2. 简述 E-R 模型的基本组成。
  3. 如何把 E-R 图转换为关系模式?
  4. 什么是主码、候选码、外码?
  5. 什么是实体完整性、参照完整性?
  6. 为什么关系模型成为主流?
  7. 画出给定业务场景的 E-R 图,并转换为关系模式。
7. 典型例题

例题:某教学系统包含如下语义。

  • 一个学生有学号、姓名、专业
  • 一门课程有课程号、课程名、学分
  • 一个学生可选多门课程,一门课程可被多个学生选修
  • 选修关系包含成绩属性

要求:

  1. 画出 E-R 模型要点
  2. 转换为关系模式
  3. 指出主键和外键

解答:

概念层:

  • 实体 Student
  • 实体 Course
  • 联系 Select

联系类型:

  • StudentCourse 是 M:N
  • Select 带有属性 成绩

关系模式:

text 复制代码
Student(学号, 姓名, 专业)
Course(课程号, 课程名, 学分)
SC(学号, 课程号, 成绩)

键:

  • Student 主键:学号
  • Course 主键:课程号
  • SC 主键:(学号, 课程号)
  • SC.学号 外键引用 Student(学号)
  • SC.课程号 外键引用 Course(课程号)
8. 解题思路

E-R 设计题可按固定模板做:

  1. 找对象:哪些是实体
  2. 找描述:实体有什么属性
  3. 找关系:实体之间如何关联
  4. 判断基数:1:1、1:N、M:N
  5. 看联系是否带属性
  6. 转表:实体成表,1:N 放外键,M:N 单独成表
  7. 标主键、外键、必要约束
9. 简短总结

这个模块最重要的收获是:数据库设计不是先写 SQL,而是先做抽象。

E-R 模型告诉你现实世界怎么被理解,关系模型告诉你这种理解如何落成二维表。后面一切 SQL、范式、约束、优化,都是建立在"表已经被设计成合理结构"的前提上。

模块 3:关系代数、关系演算与查询思维

1. 本模块解决的问题

很多同学学 SQL 只会背语法,却不知道查询本质上到底在做什么。于是:

  • 一复杂就乱
  • 一换写法就懵
  • 一看到"用关系代数表达查询"就失分

这一模块解决的是:查询的本质是什么

关系代数和关系演算之所以重要,不是因为工作中天天手写这些符号,而是因为它们帮你建立"查询逻辑骨架"。当你理解了选择、投影、连接、除法这些操作,SQL 就不再是死记硬背,而变成一种表达方式。

2. 核心概念、术语与符号
2.1 关系代数的常见操作
运算 符号 含义 直白理解
选择 σ 从行中筛条件 WHERE
投影 π 只取某些列 SELECT 某些列
合并两个同结构关系 两个结果集合并去重
- 一个集合减去另一个 "有 A 没 B"
笛卡尔积 × 两关系全部组合 所有可能配对
连接 按条件把相关元组拼起来 多表查询核心
重命名 ρ 给关系或属性改名 方便表达
除法 ÷ 表示"对全部都满足" 典型"选了所有课程的人"
2.2 关系演算

关系演算更偏逻辑表达,分为:

  • 元组关系演算
  • 域关系演算

你可以把它理解为:关系代数强调"怎么一步步操作",关系演算强调"满足什么条件的结果才算答案"。

3. 通俗理解与因果推理
3.1 为什么查询可以抽象成集合运算

关系模型的理论基础,是把表看成元组集合。既然是集合,就自然会有:

  • 选一些元素
  • 去掉一些元素
  • 保留部分属性
  • 把两个集合按某种规则配对

所以查询不是魔法,而是集合运算的组合。

3.2 苏格拉底式提问:为什么连接如此重要

问:为什么数据库通常不用一张大表把所有信息都塞进去?

答:因为那样会冗余大、更新难、结构混乱。

问:可一旦拆成多张表,不就信息分散了吗?

答:对,所以查询时就要把分散的信息重新按条件拼起来。

问:这就是连接?

答:对。连接本质上是在说:"只把真正相关的行配成对"。如果没有连接,规范化之后的数据就很难重新组合利用。

3.3 为什么"除法"难但重要

很多学生一看到关系代数中的除法就怕。其实它对应的是一类很常考的语义:

  • 选修了所有必修课的学生
  • 拥有某部门全部权限的角色
  • 覆盖了某集合中全部元素的对象

也就是说,除法本质不是数学里的除法,而是在处理"对全部都满足"的查询。

4. 关键规则、方法与例子
4.1 选择与投影

Student(Sno, Sname, Major, Age)

查询"年龄大于 20 的学生姓名和专业":

关系代数:

πSname,Major(σAge>20(Student)) \pi_{Sname, Major}(\sigma_{Age > 20}(Student)) πSname,Major(σAge>20(Student))

这里要看懂两个层次:

  • 先用 σ 选出满足条件的行
  • 再用 π 取出需要的列

这和 SQL 的逻辑是一致的。

4.2 连接

设:

  • Student(Sno, Sname, Major)
  • SC(Sno, Cno, Grade)
  • Course(Cno, Cname, Credit)

查询"选修了数据库课程的学生姓名":

关系代数可写成:

πSname(Student⋈Student.Sno=SC.Sno(SC⋈SC.Cno=Course.CnoσCname=′数据库′(Course))) \pi_{Sname} \Big( Student \bowtie Student.Sno = SC.Sno \big( SC \bowtie SC.Cno = Course.Cno \sigma_{Cname='数据库'}(Course) \big) \Big) πSname(Student⋈Student.Sno=SC.Sno(SC⋈SC.Cno=Course.CnoσCname=′数据库′(Course)))

SQL 可写成:

sql 复制代码
SELECT DISTINCT s.Sname
FROM Student s
JOIN SC sc ON s.Sno = sc.Sno
JOIN Course c ON sc.Cno = c.Cno
WHERE c.Cname = '数据库';

要注意:SQL 只是更接近自然语言的表达,而关系代数更像数学骨架。

4.3 并、差、交的思想

查询"既是学生会成员又是班干部的学生"。

如果两个结果集合结构相同,可先分别求出,再求交集。由于基本关系代数通常没有专门的交运算,可以用差来表示:

R∩S=R−(R−S) R \cap S = R - (R - S) R∩S=R−(R−S)

这也是考试里常见的小技巧。

4.4 除法的经典语义

SC(Sno, Cno) 记录学生选课,Required(Cno) 记录所有必修课程。

若要查询"选修了全部必修课程的学生",可写成:

πSno,Cno(SC)÷πCno(Required) \pi_{Sno, Cno}(SC) \div \pi_{Cno}(Required) πSno,Cno(SC)÷πCno(Required)

理解方式:

  • 被除数里是"谁选了哪些课"
  • 除数里是"所有必须覆盖的课"
  • 结果是"能够覆盖除数全部元素的学生"
4.5 关系演算的直觉

关系演算你不必一开始钻符号,先抓住一句话:

关系代数偏过程,关系演算偏条件。

比如"找出所有年龄大于 20 的学生名",元组关系演算会强调:

  • 找出那些元组 t
  • 它属于 Student
  • 且满足 t.Age > 20
  • 输出 t.Sname
5. 易混点与常见误区
误区 1:选择和投影记反

一个记忆法:

  • 选择 σ 是"挑行"
  • 投影 π 是"取列"
误区 2:自然连接和等值连接混淆
  • 等值连接:按显式相等条件连接,结果可能保留重复连接列
  • 自然连接:自动按同名属性相等连接,并去掉重复列

考试里没写清,最好不要擅自把两者混同。

误区 3:并、差要求结构兼容

不是任意两张表都能并或差。通常要求对应属性个数相同、类型相容。

误区 4:除法不会翻译语义

看到"全部""所有""每一个",就要警惕是不是除法型查询。

6. 考试常见问法
  1. 用关系代数表示给定查询。
  2. 说明选择、投影、连接、除法的意义。
  3. 用关系代数写出多表连接查询。
  4. 比较关系代数与关系演算。
  5. 给定关系模式,完成"查询所有满足某条件对象"的关系代数表达。
7. 典型例题

例题:设有关系

  • Student(Sno, Sname, Dept)
  • Course(Cno, Cname)
  • SC(Sno, Cno, Grade)

求"选修了课程名为'数据库'且成绩大于 80 分的学生姓名"。

关系代数解法:

πSname(Student⋈σGrade>80(SC)⋈σCname=′数据库′(Course)) \pi_{Sname} \Big( Student \bowtie \sigma_{Grade > 80}(SC) \bowtie \sigma_{Cname='数据库'}(Course) \Big) πSname(Student⋈σGrade>80(SC)⋈σCname=′数据库′(Course))

如果要更严谨,可补全连接条件:

πSname((Student⋈Student.Sno=SC.SnoσGrade>80(SC))⋈SC.Cno=Course.CnoσCname=′数据库′(Course)) \pi_{Sname} \Big( (Student \bowtie_{Student.Sno = SC.Sno} \sigma_{Grade > 80}(SC)) \bowtie_{SC.Cno = Course.Cno} \sigma_{Cname='数据库'}(Course) \Big) πSname((Student⋈Student.Sno=SC.SnoσGrade>80(SC))⋈SC.Cno=Course.CnoσCname=′数据库′(Course))

8. 解题思路

关系代数题建议按下面模板:

  1. 明确最终要输出什么列
  2. 明确需要哪些表
  3. 先做条件过滤,再做连接,最后投影
  4. 涉及"全部都满足"时考虑除法
  5. 涉及集合比较时考虑并、差、交

一个很实用的口诀是:

先找表,再筛行,再连表,后取列。

9. 简短总结

关系代数和关系演算并不是为了折磨初学者,而是在帮你看透:查询不是在背 SQL 关键字,而是在做集合与逻辑运算。

只要你脑子里先有"筛行、取列、连接、覆盖全部"的骨架,SQL 再复杂也不容易乱。

模块 4:SQL 核心操作体系

1. 本模块解决的问题

如果说关系代数告诉你"查询本质是什么",那 SQL 解决的就是"如何用工业界真正使用的语言来表达这些操作"。

SQL 是数据库课程里最容易得分、也最容易失分的部分。容易得分,是因为它题型稳定;容易失分,是因为:

  • 语法细节多
  • 执行顺序容易混
  • NULL 语义特殊
  • 连接、分组、子查询经常混在一起

这一模块的目标,不只是让你会写 SQL,而是让你明白:每一条 SQL 都在表达一个清晰的数据需求。

2. 核心概念、术语与分类
分类 全称 主要作用 常见命令
DDL Data Definition Language 定义数据库对象 CREATEALTERDROP
DML Data Manipulation Language 修改数据 INSERTUPDATEDELETE
DQL Data Query Language 查询数据 SELECT
DCL Data Control Language 权限控制 GRANTREVOKE
TCL Transaction Control Language 事务控制 COMMITROLLBACKSAVEPOINT

SQL 课程考试中,最核心的是:

  • 单表查询
  • 多表连接
  • 分组聚合
  • 嵌套子查询
  • 视图
  • 更新与删除
  • 授权与事务基础
3. 先讲用途,再讲细节
3.1 SQL 为什么比关系代数更适合人用

关系代数很严谨,但不够贴近人类表达。SQL 更像你在和系统说:

  • 从哪些表里找
  • 满足什么条件
  • 最后展示哪些字段
  • 要不要排序
  • 要不要分组统计

所以 SQL 的优势在于"可表达复杂查询,同时相对可读"。

3.2 SQL 的逻辑执行顺序

很多初学者看 SQL 文本顺序是:

sql 复制代码
SELECT ...
FROM ...
WHERE ...
GROUP BY ...
HAVING ...
ORDER BY ...

但数据库在逻辑上更接近这样理解:

  1. FROM
  2. JOIN
  3. WHERE
  4. GROUP BY
  5. HAVING
  6. SELECT
  7. DISTINCT
  8. ORDER BY
  9. LIMIT(如果有)

为什么要知道这个顺序?

因为它直接解释很多常见错误。

例如:

  • 为什么 WHERE 里不能直接用聚合结果?因为聚合还没发生。
  • 为什么 HAVING 可以过滤分组结果?因为它发生在分组之后。
  • 为什么别名有时不能在 WHERE 用?因为 SELECTWHERE 更晚。
3.3 苏格拉底式提问:为什么先 FROMSELECT

问:SQL 不是先写 SELECT 吗,为什么理解时要先 FROM

答:因为数据库得先知道"去哪里找数据",才能决定"从结果里拿什么列"。

问:那为什么语法上把 SELECT 放前面?

答:因为对人类来说,更习惯先说"我要什么",再说"从哪里取"。语法顺序照顾人类表达,逻辑顺序照顾数据库执行。

这就是"写法顺序"和"理解顺序"不一致的原因。

4. 关键规则、方法、公式与例子
4.1 DDL:定义数据对象
sql 复制代码
CREATE TABLE Student (
    Sno CHAR(10) PRIMARY KEY,
    Sname VARCHAR(50) NOT NULL,
    Gender CHAR(1),
    Age INT CHECK (Age >= 0),
    Dept VARCHAR(50)
);

这里每个约束都不是装饰:

  • PRIMARY KEY 保证实体完整性
  • NOT NULL 保证必要属性不能为空
  • CHECK 保证用户定义完整性
4.2 基础查询
sql 复制代码
SELECT Sno, Sname, Dept
FROM Student
WHERE Dept = '计算机学院';

这条语句在做什么?

  • Student 表取数据
  • 只保留系别为"计算机学院"的行
  • 最后显示学号、姓名、院系三列
4.3 排序、去重与别名
sql 复制代码
SELECT DISTINCT Dept AS 学院
FROM Student
ORDER BY 学院;

这里要会解释:

  • DISTINCT 去除重复结果
  • AS 起别名,提升可读性
  • ORDER BY 只影响显示顺序,不改变表中存储顺序
4.4 聚合与分组

常见聚合函数:

  • COUNT
  • SUM
  • AVG
  • MAX
  • MIN

例子:统计每个院系人数。

sql 复制代码
SELECT Dept, COUNT(*) AS 人数
FROM Student
GROUP BY Dept;

为什么必须 GROUP BY Dept

因为你一边想看"每个院系",一边想做"人数统计"。那就必须先按院系分组,再对每组统计。

4.5 WHEREHAVING 的区别

例子:统计平均成绩大于 80 的课程。

sql 复制代码
SELECT Cno, AVG(Grade) AS avg_grade
FROM SC
GROUP BY Cno
HAVING AVG(Grade) > 80;

为什么不能写在 WHERE

因为 AVG(Grade) 是分组后的结果,而 WHERE 发生在分组之前。

4.6 多表连接
sql 复制代码
SELECT s.Sname, c.Cname, sc.Grade
FROM Student s
JOIN SC sc ON s.Sno = sc.Sno
JOIN Course c ON sc.Cno = c.Cno;

连接的本质是什么?

  • 不是"把表简单拼起来"
  • 而是"按相关条件找到真正匹配的行,再组合展示"

常见连接类型:

连接类型 作用 直观理解
内连接 只保留匹配成功的记录 交集式拼接
左连接 保留左表全部记录 左边都要,右边能配上就配
右连接 保留右表全部记录 与左连接对称
全连接 保留两边全部记录 一般教材提及,部分数据库实现不同
4.7 子查询

例子:查询成绩高于平均分的选课记录。

sql 复制代码
SELECT *
FROM SC
WHERE Grade > (
    SELECT AVG(Grade)
    FROM SC
);

子查询常见三类:

  1. 标量子查询:返回单个值
  2. 列子查询:返回一列值
  3. 存在型子查询:用 EXISTS 判断是否存在
4.8 INEXISTSANYALL

这几个很容易混。

sql 复制代码
SELECT Sname
FROM Student
WHERE Sno IN (
    SELECT Sno
    FROM SC
    WHERE Grade > 90
);

这表示"学生学号属于高分选课记录中的学号集合"。

EXISTS 更强调"只要存在符合条件的关联记录即可":

sql 复制代码
SELECT s.Sname
FROM Student s
WHERE EXISTS (
    SELECT 1
    FROM SC sc
    WHERE sc.Sno = s.Sno
      AND sc.Grade > 90
);
4.9 NULL 的特殊性

NULL 不是 0,不是空字符串,也不是假。

它表示"未知"或"缺失"。

所以:

sql 复制代码
WHERE Age = NULL

是错误用法,应该写:

sql 复制代码
WHERE Age IS NULL

因为 NULL 不能用普通比较运算去判断。

4.10 视图
sql 复制代码
CREATE VIEW CS_Student AS
SELECT Sno, Sname, Dept
FROM Student
WHERE Dept = '计算机学院';

视图的作用:

  • 简化复杂查询
  • 隐藏底层复杂结构
  • 提供安全隔离
  • 支持逻辑独立性

视图像一扇"定制窗口",用户看到的是被加工后的逻辑结果。

5. 易混点与常见误区
误区 1:把 WHEREHAVING 当同义词

不是。

  • WHERE 过滤原始行
  • HAVING 过滤分组结果
误区 2:忘记连接条件,造成笛卡尔积

这是 SQL 题的大坑。多表查询一旦漏了连接条件,结果会爆炸式增长。

误区 3:COUNT(*)COUNT(列名)COUNT(DISTINCT 列名) 混淆
  • COUNT(*) 统计行数,包含空值行
  • COUNT(列名) 只统计该列非空值数量
  • COUNT(DISTINCT 列名) 统计该列非空且去重后的数量
误区 4:分组后 SELECT 列乱写

一般规则是:分组查询中,SELECT 里出现的非聚合列,必须出现在 GROUP BY 中。

误区 5:NOT INNULL 组合导致结果异常

如果子查询结果中含 NULLNOT IN 常会带来意外结果。考试里通常不会挖太深,但要知道这类陷阱。

误区 6:把视图当成独立存储数据的表

普通视图通常不存实际数据,存的是定义;它是逻辑层对象,不是物理复制。

6. 考试常见问法
  1. 写出完成指定查询要求的 SQL 语句。
  2. 比较 WHEREHAVING 的区别。
  3. 说明内连接、外连接的差异。
  4. 解释视图的作用及优点。
  5. 使用子查询完成"高于平均值""属于某集合""存在某条件"等题目。
  6. 设计带主键、外键、约束的建表语句。
7. 典型例题

例题 1:查询每门课程的平均成绩,并输出平均成绩大于 85 的课程号和平均分。

sql 复制代码
SELECT Cno, AVG(Grade) AS AvgGrade
FROM SC
GROUP BY Cno
HAVING AVG(Grade) > 85;

为什么这样写:

  • 题目要"每门课程",说明要按课程分组
  • 题目要"平均成绩",说明要聚合
  • 条件作用于聚合结果,所以用 HAVING

例题 2:查询没有选修任何课程的学生姓名。

写法一:

sql 复制代码
SELECT Sname
FROM Student s
WHERE NOT EXISTS (
    SELECT 1
    FROM SC sc
    WHERE sc.Sno = s.Sno
);

这类题在考试中很常见,因为它考查你是否理解"存在性"。

8. 解题思路

SQL 题可以按固定步骤拆:

  1. 明确目标列:题目最后要显示什么
  2. 确定来源表:需要哪些表
  3. 判断关系:表之间如何连接
  4. 加条件:是原始行条件还是聚合结果条件
  5. 看是否需要分组、排序、去重
  6. 若一句不好写,考虑子查询

一个非常实用的自检问题是:

"我现在筛的是原始记录,还是分组后的结果?"

这能帮你在 WHEREHAVING 之间做出正确选择。

9. 简短总结

SQL 真正难的地方,不是背命令,而是把需求拆成:

  • 数据从哪来
  • 如何关联
  • 先筛什么
  • 后统计什么
  • 最终展示什么

只要你始终按"来源表 → 条件 → 分组 → 输出"的顺序思考,SQL 就会从语法题变成逻辑题。

模块 5:关系规范化与数据库设计

1. 本模块解决的问题

前面你已经知道如何把现实世界抽象成表,但新的问题立刻出现了:

  • 表是不是只要能装下数据就算设计好了?
  • 为什么有的表一改就牵一大片?
  • 为什么有的表插入、删除时会出现"顺带丢信息"的怪现象?
  • 为什么教材要反复讲函数依赖和 1NF、2NF、3NF、BCNF?

这一模块解决的,本质上是:如何设计"结构好、冗余少、异常少、维护成本低"的关系模式。

如果说 E-R 模型回答"应该有哪些表",那么规范化回答的就是"这些表该拆到什么程度才合理"。

2. 核心概念及术语
2.1 什么是数据冗余与更新异常
概念 含义 典型表现
数据冗余 同一事实被重复存储 教师姓名在多行重复出现
插入异常 因结构不合理导致不能正常插入 还没有学生选课,就无法录入课程信息
删除异常 删除一条记录导致无关信息丢失 删除最后一个选课学生,课程信息也没了
更新异常 一处事实需多处更新,易不一致 教师换办公室,很多行都要改
2.2 函数依赖相关术语

设关系模式为 R(U)XY 为属性集。

若在任意合法关系实例中,只要两个元组在 X 上相同,就一定在 Y 上相同 ,则称 Y 函数依赖于 X,记为:

X→Y X \rightarrow Y X→Y

常见概念:

术语 含义 说明
函数依赖 X 唯一决定 Y 数据语义层面的约束
平凡函数依赖 Y \subseteq X 总是成立
完全函数依赖 Y 依赖 X,且不依赖 X 的任何真子集 判定 2NF 核心
部分函数依赖 Y 依赖 X,但也依赖 X 的真子集 2NF 要消除
传递函数依赖 X -> Y, Y -> ZY 不是候选码时,X 间接决定 Z 3NF 关注重点
2.3 码与闭包
术语 含义
超码 能唯一标识元组的属性组
候选码 最小超码
主码 被选作主标识的候选码
属性闭包 X^+ 在函数依赖集 F 下,由 X 能推出的全部属性集合

闭包是考试里判断候选码、分解是否合理的高频工具。

3. 先讲直觉,再讲规范化
3.1 为什么"能用"不等于"设计好"

考虑一张大表:

text 复制代码
选课信息(学号, 学生姓名, 专业, 课程号, 课程名, 教师号, 教师姓名, 教师办公室, 成绩)

这张表看起来什么都能查,但它的问题很严重:

  • 学生姓名、专业会随着选课记录重复出现
  • 课程名、教师姓名、办公室也会重复出现
  • 教师办公室一变,所有相关行都要改
  • 如果一门新课刚创建、还没人选,就没地方存
  • 如果某门课最后一个学生退课,那课程和教师信息可能跟着丢

所以问题不在于"查不查得到",而在于"数据事实被混杂在了一张表里"。

3.2 苏格拉底式追问:为什么拆表能减少异常

问:为什么一张大表会冗余?

答:因为不同层级的事实被放在同一张表中。

问:什么叫不同层级的事实?

答:例如:

  • 学生事实:学号决定学生姓名、专业
  • 课程事实:课程号决定课程名、教师号
  • 教师事实:教师号决定教师姓名、办公室
  • 选课事实:(学号, 课程号) 决定成绩

问:这些事实为什么不能都混放?

答:因为它们的决定因素不同。把由不同决定因素控制的信息放一张表里,就会导致重复与异常。

问:那拆表的依据是什么?

答:就看"谁决定谁",也就是函数依赖。

这就是规范化的根:按照依赖结构拆表

4. 关键规则、定理、方法
4.1 Armstrong 公理

函数依赖推理的经典规则:

  1. 自反律 :若 Y \subseteq X,则 X -> Y
  2. 增广律 :若 X -> Y,则 XZ -> YZ
  3. 传递律 :若 X -> YY -> Z,则 X -> Z

常用推论:

  • 合并律
  • 分解律
  • 伪传递律

这些规则不是为了炫技,而是为了帮助你:

  • 求闭包
  • 判断候选码
  • 判断某分解是否保留依赖
4.2 如何求属性闭包

X^+ 的步骤:

  1. 初始把 X 放进闭包
  2. 扫描依赖集中所有 A -> B
  3. A 已包含在闭包中,则把 B 加入闭包
  4. 重复直到闭包不再扩大

闭包的最常见用途:

  • 如果 X^+ 包含了关系中的全部属性,则 X 是超码
  • 若再检查其最小性,可判断是否为候选码
4.3 第一范式 1NF

定义: 关系中每个属性值都必须是不可再分的原子值。

直觉: 一格只能放一个值。

例如把"选修课程"写成:

text 复制代码
学生(学号, 姓名, 课程列表)

其中 课程列表 里塞多个课程号,这就不满足 1NF。

易错点:

  • "电话有区号和号码"不等于一定不满足 1NF,关键看数据库是否把它当作不可再分的原子整体使用。
4.4 第二范式 2NF

定义: 在 1NF 基础上,所有非主属性都必须完全函数依赖于每一个候选码,不允许对候选码的真子集存在部分依赖。

为什么会有部分依赖问题?

因为当主键是组合键时,某些属性可能只依赖其中一部分。

例子:

text 复制代码
SCInfo(学号, 课程号, 学生姓名, 课程名, 成绩)

若主键是 (学号, 课程号),则:

  • 学号 -> 学生姓名
  • 课程号 -> 课程名
  • (学号, 课程号) -> 成绩

这里 学生姓名课程名 只依赖组合键的一部分,所以不满足 2NF。

解决方法: 拆成:

text 复制代码
Student(学号, 学生姓名)
Course(课程号, 课程名)
SC(学号, 课程号, 成绩)
4.5 第三范式 3NF

定义: 在 2NF 基础上,不存在非主属性对候选码的传递依赖。

例子:

text 复制代码
Course(课程号, 课程名, 教师号, 教师姓名, 教师办公室)

若:

  • 课程号 -> 教师号
  • 教师号 -> 教师姓名, 教师办公室

那么就有:

  • 课程号 -> 教师姓名, 教师办公室

这是一种通过 教师号 发生的传递依赖。

为什么有问题?

因为教师信息本质上由教师号决定,不应混在课程表里重复存。

拆分:

text 复制代码
Course(课程号, 课程名, 教师号)
Teacher(教师号, 教师姓名, 教师办公室)
4.6 BCNF

定义: 对关系中每一个非平凡函数依赖 X -> YX 都必须是超码。

BCNF 比 3NF 更严格。

为什么还需要 BCNF?

因为有些关系虽然满足 3NF,但仍可能因某些"主属性之间的依赖"存在异常。BCNF 进一步要求:凡是能决定别人的,自己必须足够强,至少得是超码。

4.7 规范化不是越高越好

这是特别重要的工程直觉。

教材从理论上常追求更高范式,但现实系统中并不总是越分越细越好。因为:

  • 拆得越细,连接成本可能越高
  • 读多写少场景下,有时会适度反规范化
  • 报表系统常为了查询效率做冗余设计

所以你要明白:

  • 考试里看理论最优
  • 工程里看成本平衡
4.8 无损连接分解与保持依赖

这是考试综合题常考的两个标准。

无损连接分解:

把一个关系分成多个关系后,再连接回来,不应丢信息,也不应凭空产生假元组。

保持依赖:

原关系上的函数依赖,最好能在分解后的各子关系上继续检查和维持,而不需要每次重新连接全部表。

一个常见判断规则:

R 分解为 R1R2,若 (R1 ∩ R2) -> R1(R1 ∩ R2) -> R2,则该分解通常是无损连接的。

5. 易混点与常见误区
误区 1:把"字段重复出现"直接等同于不规范

不是。重复现象只是症状,根因是依赖结构不合理。

误区 2:2NF 与 3NF 分不清

一个抓手:

  • 2NF 解决"依赖主键的一部分"
  • 3NF 解决"依赖一个非主属性再间接依赖主键"
误区 3:主属性、非主属性概念模糊

主属性是"属于某个候选码的属性",不是"主键中的属性才算"。如果一个属性出现在任何候选码里,它就是主属性。

误区 4:BCNF 一定优于 3NF

理论上更纯净,但工程上不一定更合适。考试可写"BCNF 更严格",不要轻易写"总是更好"。

误区 5:规范化只靠背定义

真正做题时,关键是先找函数依赖,再判断候选码,再看异常来源。

6. 考试常见问法
  1. 什么是函数依赖?什么是完全函数依赖、部分函数依赖、传递函数依赖?
  2. 如何求某属性集的闭包?
  3. 如何判断候选码?
  4. 说明 1NF、2NF、3NF、BCNF 的含义。
  5. 给定关系模式和依赖集,判断其属于第几范式。
  6. 对关系模式进行规范化分解,并说明理由。
  7. 判断分解是否无损连接、是否保持依赖。
7. 典型例题

例题:设有关系模式

text 复制代码
R(学号, 课程号, 学生姓名, 课程名, 教师号, 教师姓名, 成绩)

已知函数依赖:

  • 学号 -> 学生姓名
  • 课程号 -> 课程名, 教师号
  • 教师号 -> 教师姓名
  • (学号, 课程号) -> 成绩

求:

  1. 候选码
  2. 最高范式
  3. 合理分解

解题:

第一步,判断候选码。

显然仅有 学号 不够,仅有 课程号 不够。因为成绩由"学生-课程"共同决定,所以 (学号, 课程号) 能决定全部属性,是候选码。

第二步,判断范式。

  • 学生姓名 只依赖 学号,对组合键存在部分依赖,不满足 2NF。
  • 所以最高只到 1NF。

第三步,分解。

text 复制代码
Student(学号, 学生姓名)
Course(课程号, 课程名, 教师号)
Teacher(教师号, 教师姓名)
SC(学号, 课程号, 成绩)
8. 解题思路

范式题建议固定流程:

  1. 写出函数依赖集
  2. 求候选码
  3. 区分主属性与非主属性
  4. 判断是否存在部分依赖
  5. 判断是否存在传递依赖
  6. 若要上 BCNF,检查每个决定因素是否为超码
  7. 分解时说明每一步消除了哪类异常
9. 简短总结

规范化的本质,不是为了追求"表越多越高级",而是为了让每条数据事实都待在"真正由它的决定因素负责"的地方。

一句最值得记住的话是:

谁决定谁,就说明谁应该和谁待在一起;如果不同决定因素控制的信息硬塞一张表,异常迟早出现。

模块 6:存储结构、文件组织与索引

1. 本模块解决的问题

前面我们讨论的是"逻辑上怎么设计表",这一模块要下沉到更接近机器的层面:

  • 数据最终是怎么放在磁盘上的?
  • 为什么明明是一张表,有时查得很快,有时很慢?
  • 为什么加了索引就可能快很多?
  • 为什么索引不是越多越好?
  • 为什么 B+ 树会成为关系数据库索引主流?

这一模块是数据库课程中最能把"理论"和"工程直觉"接起来的一部分。

2. 核心概念及术语
术语 含义 通俗解释
页(Page)/块(Block) 磁盘和缓冲管理的基本单位 数据不是按行零散读,而是按页成块读
记录(Record) 一行数据在物理层的存储表示 一条元组落盘后的样子
文件组织 记录在磁盘中的组织方式 无序堆文件、顺序文件、散列文件
索引(Index) 由关键字到记录位置的辅助结构 目录,不是数据本身
聚簇索引 数据记录按索引键顺序组织 索引顺序和物理存储更一致
非聚簇索引 索引与数据分离 通过索引再定位数据
稠密索引 每个搜索键值都有索引项 更细致
稀疏索引 只为部分块/页建立索引项 更省空间
B 树 / B+ 树 多路平衡查找树 适合磁盘 I/O 场景
选择性 索引列区分度高低 越能过滤,索引越有价值
3. 先讲直觉:为什么数据库性能问题大多和 I/O 有关

CPU 很快,但磁盘访问相对慢得多。数据库查询的核心成本,往往不是算,而是

如果你要在 1000 万行记录里找一条数据:

  • 没有索引,可能要一页一页翻
  • 有索引,就像先看目录再定位章节

所以索引加速的本质不是"让数据库更聪明",而是减少不必要的数据页访问

苏格拉底式提问:为什么不能直接用二叉搜索树

问:查找结构最经典的不就是二叉搜索树吗?

答:在内存里可以,但数据库主要面对磁盘。

问:磁盘场景有什么特殊?

答:一次磁盘 I/O 读的是一个页,不是一个节点。如果一棵树每层只存一个关键字,树会太高,每查一次都要多次 I/O。

问:那怎么办?

答:让每个节点装很多关键字和很多孩子指针,降低树高。

这就引出了 B 树和 B+ 树。

4. 关键规则、方法与机制
4.1 文件组织方式

常见文件组织:

组织方式 特点 优点 缺点
堆文件 记录无序存放 插入简单快 查询常需全表扫描
顺序文件 按某关键字有序 范围查询好 插入删除维护成本高
散列文件 通过散列函数定位 等值查询快 范围查询差

为什么数据库不会只靠一种组织方式?

因为不同查询模式不同:

  • 等值查找适合散列
  • 范围查找适合有序结构
  • 写入频繁时无序插入更灵活
4.2 索引为什么能快

索引的本质是:

  1. 先对关键字做有组织的保存
  2. 让查找先定位到较小范围
  3. 再访问真正数据

你可以把它理解成书本目录:

  • 没目录:从第一页翻到最后一页
  • 有目录:先找到章节页码,再翻到目标页
4.3 B+ 树为什么适合数据库

B+ 树有几个关键优势:

  1. 多路分支,树高低

    一个节点能存很多键,几层就能覆盖大量数据。

  2. 所有关键字都出现在叶子节点

    便于范围扫描,结构稳定。

  3. 叶子节点通常顺序链接

    适合区间查询,例如 BETWEEN、排序、前缀范围。

  4. 磁盘友好

    节点大小可设计得接近一个页,I/O 利用率高。

一个经典直觉是:

  • 二叉树像小门店,一层只能接待两个方向
  • B+ 树像大型交通枢纽,一层就能分流很多方向
4.4 B+ 树查询过程

假设对 学号 建立 B+ 树索引。

查询 学号 = '2026001' 时:

  1. 先从根节点比较关键字范围
  2. 走向对应子节点
  3. 重复直到叶子节点
  4. 在叶子上找到对应记录指针,或直接找到整行数据(视聚簇情况而定)

树高通常很低,所以需要的 I/O 次数也不多。

4.5 聚簇索引与非聚簇索引

这是课程和面试都特别爱考的点。

类型 特点 直观理解
聚簇索引 数据本身按索引键组织 书的正文按目录顺序排版
非聚簇索引 索引与数据分离 目录单独记录"去哪里找正文"

如果是聚簇索引:

  • 范围查询通常表现更好
  • 按索引顺序读取数据更自然

如果是非聚簇索引:

  • 查到索引后往往还要"回表"
  • 但建立灵活,一张表可有多个辅助索引
4.6 组合索引与最左前缀

设有组合索引 (A, B, C)

常见经验是:

  • 用到 A,通常可利用索引
  • 用到 A, B,通常也可利用
  • 只用 BC,通常不理想

为什么?

因为组合索引内部先按 A 排,再在 A 相同的组内按 B 排,再按 C 排。前缀顺序决定了可利用方式。

4.7 索引为什么不是越多越好

因为索引不是免费午餐。

每建一个索引:

  • 占用额外空间
  • 插入、删除、更新时要维护
  • 优化器选择成本增加

所以只有当一个列经常用于:

  • 查询过滤
  • 连接条件
  • 排序
  • 分组

且区分度较高时,索引才更值得。

4.8 哪些情况下索引可能失效

这是实战和考试都很有价值的点。

常见情形:

  • 对索引列做函数运算
  • 使用前导模糊查询,如 LIKE '%abc'
  • 隐式类型转换
  • 组合索引不满足前缀规律
  • 查询结果比例过大,优化器认为全表扫描更划算
4.9 MySQL/InnoDB 的一点工程直觉

如果你学的是通用数据库课程,知道这几点就已经很加分:

  • InnoDB 主键索引通常是聚簇索引
  • 二级索引叶子节点常保存主键值
  • 所以通过二级索引查整行时,往往还需要根据主键再去聚簇索引取数据,这就是"回表"
  • 若查询列都在索引里,可能形成"覆盖索引",减少回表成本
5. 易混点与常见误区
误区 1:有索引就一定快

错。若选择性太差、返回行太多、条件写法不利于索引使用,索引不一定比全表扫描快。

误区 2:索引等于排序后的整张表

索引是辅助结构,不等于对数据简单排序后的复制。

误区 3:散列索引适合范围查询

散列主要擅长等值查询,不擅长区间。

误区 4:B+ 树只适合等值查询

不对。B+ 树对范围查询尤其友好,这恰恰是它比哈希更通用的重要原因。

误区 5:主键只是逻辑概念

在某些数据库实现中,主键与物理组织关系非常密切,尤其是在聚簇索引结构下。

6. 考试常见问法
  1. 数据库存储为什么按页而不是按元组管理?
  2. 说明堆文件、顺序文件、散列文件的特点。
  3. 为什么 B+ 树适合作为数据库索引?
  4. 聚簇索引与非聚簇索引有何区别?
  5. 为什么索引可以提高查询效率?
  6. 哪些情况下索引不一定有效?
  7. 比较 B+ 树索引与哈希索引的适用场景。
7. 典型例题

例题:为什么数据库索引通常使用 B+ 树而不是普通二叉搜索树或哈希结构?

答案思路:

  1. 和二叉搜索树比:

    • B+ 树多路分支,树高低
    • 更适合磁盘页 I/O
    • 更稳定,不易因树高过大而性能差
  2. 和哈希比:

    • B+ 树支持范围查询、排序、前缀匹配
    • 哈希更偏等值查询,通用性差
  3. B+ 树叶子节点链式组织,适合顺序扫描

8. 解题思路

关于索引的题,不要只答"快"。一定要答出:

  1. 快在什么地方
  2. 为什么快
  3. 适用什么查询
  4. 代价是什么

例如答"为什么索引快"时,可以写:

  • 通过辅助结构减少全表扫描
  • 降低磁盘 I/O 次数
  • B+ 树高度低且范围查询友好
  • 但索引要占空间、维护有成本
9. 简短总结

这个模块真正要建立的认知是:

数据库慢,往往不是因为不会算,而是因为找得太笨。索引的价值,就在于把"海量乱找"变成"有目录地定位"。

而 B+ 树之所以重要,不是因为它名字高级,而是因为它非常符合数据库"磁盘页 + 大数据量 + 范围查询"的现实需求。

模块 7:查询处理与优化

1. 本模块解决的问题

同样一条 SQL,为什么有时几毫秒,有时几秒甚至更久?

这不是玄学,而是因为数据库并不是机械地执行你写下的文本,而是要经历:

  • 解析
  • 重写
  • 生成执行计划
  • 比较多个候选方案
  • 选择代价更低的物理执行路径

这一模块解决的是:SQL 是如何从"文字"变成"执行动作"的,以及系统为什么会选某种执行方案。

2. 核心概念及术语
术语 含义 直白解释
查询解析 把 SQL 变成语法树 看懂你写了什么
查询重写 等价变换查询表达 尝试更合理的表达方式
逻辑计划 关注"做什么运算" 选择、投影、连接、聚合
物理计划 关注"具体如何执行" 用索引扫描还是全表扫描,用哪种连接算法
优化器 负责比较候选计划并择优 像路线规划系统
代价模型 估算计划成本的模型 一般考虑 I/O、CPU、内存、网络等
选择率 条件过滤后保留数据比例 过滤越强,选择率通常越低
执行计划 实际选择的执行方案 可通过 EXPLAIN 等查看
3. 通俗理解与因果推理
3.1 为什么数据库不能"按你写的顺序死执行"

假设你写了多表连接查询,数据库如果完全按字面顺序生硬执行,可能非常慢。因为:

  • 先连大表再过滤,数据量会爆炸
  • 先过滤再连接,可能更省
  • 先走索引再回表,有时比全表扫好
  • 但如果条件不够选择性,全表扫反而更划算

所以数据库必须像下棋一样,先评估走法,再落子。

3.2 优化器像什么

可以把优化器理解成导航软件。

你给它的是目的地:"我要查到结果"。

它要决定的是:

  • 走高速还是走国道
  • 先过桥还是先穿隧道
  • 哪段路虽然短,但拥堵严重

SQL 文本像"目的地",执行计划像"路线方案"。

3.3 苏格拉底式提问:为什么"先过滤再连接"通常更好

问:连接为什么会贵?

答:因为连接是在比较两边记录并尝试匹配,参与连接的数据越多,工作量越大。

问:那如果能先减少输入规模呢?

答:连接成本就会下降。

问:所以优化器喜欢把选择操作往下推?

答:对,这叫选择下推。它是最经典、最重要的查询优化思想之一。

4. 关键规则、方法与例子
4.1 查询处理的基本流程
  1. 词法语法分析

    检查 SQL 是否写得合法。

  2. 语义检查

    表、列、权限、类型是否正确。

  3. 查询重写

    把 SQL 转成关系代数式,并尝试等价变换。

  4. 生成多个执行计划

  5. 成本估计并择优

  6. 执行与返回结果

4.2 常见等价变换

常考思想包括:

  • 选择下推:尽量早过滤
  • 投影下推:尽量早减少列数
  • 连接重排序:选择更合适的连接顺序
  • 将复杂查询改写为更高效形式

为什么投影下推也有价值?

因为即使行数不变,列数减少也会降低中间结果体积,减少 I/O 和内存占用。

4.3 常见扫描方式
扫描方式 特点 适用情形
全表扫描 直接扫描整张表 无合适索引,或结果比例很大
索引扫描 通过索引定位目标 条件选择性较高
索引范围扫描 按区间扫描索引 范围查询、排序
4.4 常见连接算法
算法 思想 适用场景
嵌套循环连接 外表每一行去匹配内表 小表配索引、简单直接
排序-合并连接 两边按连接键排序后线性合并 两边已排序或适合排序
哈希连接 对较小表构建哈希,再探测另一表 等值连接常见高效

要会解释它们的差异:

  • 嵌套循环适合理解和小规模场景
  • 排序-合并适合有序数据与范围合并
  • 哈希连接擅长等值连接
4.5 统计信息为什么重要

优化器不是先知,它依赖统计信息估算:

  • 表有多少行
  • 列值分布如何
  • 某条件大概过滤多少
  • 哪个索引可能更划算

如果统计信息不准,优化器也可能选错路径。

4.6 执行计划怎么看

虽然不同数据库展示方式不同,但核心关注点一般包括:

  • 是否全表扫描
  • 是否使用索引
  • 连接顺序如何
  • 使用了哪种连接算法
  • 估算行数大不大
  • 是否出现临时表、排序、回表等额外开销

如果你学的是 MySQL,可以记住:

  • EXPLAIN 能帮你观察 SQL 可能的执行方式
  • 看它有没有走索引、访问类型如何、预估扫描多少行
5. 易混点与常见误区
误区 1:SQL 写出来能跑就行

数据库课程不是只看"能不能跑",还看"为什么这样跑"。

误区 2:优化就是加索引

优化还包括:

  • 改写 SQL
  • 调整连接顺序
  • 减少不必要列
  • 提前过滤
  • 调整统计信息
误区 3:优化器永远最聪明

优化器依赖统计信息和代价模型,它做的是"估计最优",不一定绝对最优。

误区 4:哈希连接一定最好

不是。不同场景下,嵌套循环和排序-合并也可能更优。

6. 考试常见问法
  1. 查询处理大致经过哪些步骤?
  2. 什么是查询优化?为什么需要查询优化?
  3. 说明选择下推、投影下推的思想。
  4. 比较嵌套循环连接、排序-合并连接、哈希连接。
  5. 为什么统计信息会影响优化结果?
  6. 为什么同样的 SQL 在不同情况下性能可能差异很大?
7. 典型例题

例题:为什么把选择操作尽量提前执行,通常可以提升查询效率?

答题思路:

  • 选择操作能先过滤掉大量无关元组
  • 后续连接、排序、分组的输入规模减小
  • 中间结果变小,I/O、内存、CPU 成本下降
  • 因此优化器通常倾向于做选择下推

例题:比较三种常见连接算法。

答题时可按"原理 + 适用场景 + 优缺点"三段来写:

  • 嵌套循环:实现简单,小表适合,若内表无索引可能代价高
  • 排序-合并:适合已排序数据或大规模顺序合并
  • 哈希连接:适合等值连接,但需要额外哈希结构与内存
8. 解题思路

查询优化题很少要求你算得极细,更多是考原理说明。答题模板:

  1. 指出问题:为什么慢
  2. 说明根因:数据量大、路径不合理、中间结果过大
  3. 给出优化原则:提前过滤、减少列、合理索引、选好连接顺序
  4. 若题目给具体环境,再结合场景落地
9. 简短总结

这一模块最值得建立的意识是:

SQL 只是"你想要什么",执行计划才是"数据库准备怎么做"。

真正理解数据库的人,不会只停在 SQL 文本表面,而会进一步追问:它为什么这么跑,它还有没有更好的跑法。

模块 8:事务与并发控制

1. 本模块解决的问题

数据库之所以复杂,很大程度上不是因为要"存数据",而是因为要在很多人同时访问同一批数据时尽量保证不乱。

这一模块解决的是:

  • 什么是事务,为什么事务是数据库最关键的抽象之一?
  • 为什么并发会带来脏读、丢失修改、不可重复读、幻读?
  • 数据库如何通过锁、协议、MVCC 等机制减少混乱?
  • 隔离级别到底在平衡什么?

如果说索引解释"为什么会快",那事务解释的就是"为什么还能尽量正确"。

2. 核心概念与术语
2.1 事务

事务(Transaction)是数据库中一个不可再分的逻辑工作单位

例如银行转账:

  1. A 账户减 100
  2. B 账户加 100

这两个操作要么都成功,要么都失败,不能只做一半。

2.2 ACID
属性 含义 直白解释
A:Atomicity 原子性 事务要么全做,要么全不做 不允许做一半
C:Consistency 一致性 事务执行前后,数据库应保持一致规则 不允许破坏约束
I:Isolation 隔离性 并发事务尽量互不干扰 别人没提交前,不应随便影响我
D:Durability 持久性 提交后的结果应被持久保存 崩溃后也尽量不能丢
2.3 并发异常
异常 含义 例子
丢失修改 两事务都改同一数据,后提交覆盖前者结果 A 改成 100,B 改成 200,最终只剩 200
脏读 读取到别人尚未提交的数据 读到后来被回滚的数据
不可重复读 同一事务两次读同一行结果不同 中间被其他事务更新
幻读 同一事务两次按条件查询,结果行数不同 中间有新行插入/删除
2.4 调度与可串行化

事务并发执行会形成调度(Schedule)。

理想目标是:虽然事务并发执行,但最终效果要尽量等价于某种串行顺序执行。这叫可串行化思想。

3. 通俗理解与因果推理
3.1 为什么需要事务

没有事务,数据库就只是"会存数据的共享文件"。一旦业务操作跨多步,就很容易中间失败、状态不一致。

转账是最经典例子,但真实系统里到处都是事务:

  • 下单:扣库存、生成订单、写支付记录
  • 选课:占名额、写选课记录、更新课程人数
  • 发工资:批量更新员工账户余额

事务的价值,就是把"多步动作"封装成一个可靠单位。

3.2 为什么共享越强,并发问题越多

问:数据库为什么比文件系统复杂?

答:因为数据库强调共享。

问:共享为什么会引发并发问题?

答:因为多个用户会同时读写同一份数据。

问:如果所有人排队串行执行,不就不乱了吗?

答:是的,但性能太差。

问:那数据库真正的挑战是什么?

答:在"尽量并发"和"尽量正确"之间找平衡。

这就是隔离级别存在的根本原因。

3.3 脏读、不可重复读、幻读到底差在哪

你可以这样区分:

  • 脏读:我读到了别人还没正式生效的数据
  • 不可重复读:我重复读同一行,内容变了
  • 幻读:我按同样条件重新查,行的集合变了

行内容变化和"多了一行/少了一行",这是理解不可重复读与幻读的核心区别。

4. 关键规则、协议与方法
4.1 事务状态

事务通常经历:

  1. 活动
  2. 部分提交
  3. 提交成功
  4. 失败
  5. 回滚终止

理解这点有助于你把原子性和恢复机制连起来看。

4.2 封锁(锁)机制

最经典的并发控制手段是加锁。

基本锁类型:

锁类型 含义 作用
S 锁(共享锁) 允许读,不允许别人改 多个事务可同时读
X 锁(排他锁) 读写都要独占 别人不能再读写该对象

兼容关系:

已持有 / 请求 S X
S 兼容 不兼容
X 不兼容 不兼容
4.3 两段锁协议(2PL)

两段锁协议思想:

  • 第一阶段只加锁,不解锁
  • 第二阶段只解锁,不加锁

它的核心作用是帮助保证冲突可串行化。

更严格的形式是严格两段锁协议:

  • 排他锁通常持有到事务结束

这样有助于避免级联回滚,提高恢复性。

4.4 死锁

两个事务互相等待对方释放资源,就会形成死锁。

例子:

  • 事务 T1 先锁 A,再申请 B
  • 事务 T2 先锁 B,再申请 A

两边都等,对谁都不让,就卡住了。

解决思路:

  • 预防
  • 避免
  • 检测后解除

数据库工程中常见的是"检测 + 回滚某个事务"。

4.5 时间戳与乐观思想

除了加锁,还有基于时间戳排序、乐观并发控制等思想。课程考试里不一定要求很深,但要知道:

  • 加锁是悲观控制:先假设冲突会发生
  • 乐观控制是先放行,提交时再检查冲突
4.6 隔离级别

SQL 标准常见四级:

隔离级别 可能出现的问题 说明
读未提交 脏读、不可重复读、幻读 隔离最弱
读已提交 不可重复读、幻读 避免脏读
可重复读 幻读(标准语义下) 同一行可重复读一致
串行化 基本避免并发异常 最强但并发度最低

隔离级别本质上是在平衡:

  • 正确性
  • 并发性
  • 性能
4.7 MVCC 的直觉

MVCC(多版本并发控制)可以先记住一句话:

不是所有读都要用锁去互相阻塞,也可以通过保留数据的多个版本,让读者看到适合自己的"时间切片"。

这带来的好处是:

  • 读写冲突减少
  • 并发性能更好

但代价是:

  • 系统实现更复杂
  • 需要版本链、undo 信息等配合
4.8 可串行化图的考试直觉

有些课程会考"冲突可串行化判断"。

基本思路:

  1. 看不同事务间对同一数据项是否有冲突操作
  2. 若一个事务的冲突操作在前,就画一条边
  3. 若优先图无环,则通常冲突可串行化

这类题的本质是在判断并发调度是否可视作某种串行顺序。

5. 易混点与常见误区
误区 1:ACID 只要背英文缩写就够了

不够。一定要能联系具体业务说明每个属性在保护什么。

误区 2:不可重复读和幻读是一回事

不是。

  • 不可重复读偏"同一行内容变了"
  • 幻读偏"满足条件的行集合变了"
误区 3:隔离级别越高越好

并不总是。隔离越强,往往并发越低、成本越高。

误区 4:有锁就一定安全

锁本身也会带来:

  • 阻塞
  • 死锁
  • 吞吐下降

所以数据库不是简单"多加锁就完事"。

误区 5:事务只和转账有关

事务适用于一切"多步操作必须整体正确"的业务。

6. 考试常见问法
  1. 什么是事务?事务的四大特性是什么?
  2. 什么是脏读、不可重复读、幻读?
  3. 隔离级别有哪些?各自能避免哪些问题?
  4. 什么是共享锁、排他锁?
  5. 什么是两段锁协议?它有什么作用?
  6. 什么是死锁?如何处理?
  7. 什么是可串行化?
  8. 简述 MVCC 的基本思想。
7. 典型例题

例题 1:说明为什么银行转账必须作为事务执行。

解答要点:

  • 转账包含扣款和加款两个步骤
  • 若只完成一半,会破坏数据一致性
  • 原子性保证要么都成功,要么都失败
  • 持久性保证提交后不会因故障轻易丢失

例题 2:区分脏读、不可重复读、幻读。

答题模板:

  • 脏读:读到未提交修改
  • 不可重复读:同一事务两次读同一行,内容发生变化
  • 幻读:同一事务两次按条件查询,返回行集合发生变化

例题 3:为什么两段锁协议能提高调度正确性?

答题思路:

  • 先说两段锁的定义
  • 再说它限制锁的获取与释放顺序
  • 再说这种限制有助于保证冲突可串行化
8. 解题思路

事务题通常按"现象 → 原因 → 机制 → 代价"答最稳。

例如答"为什么要有隔离级别"时:

  1. 先说并发会带来异常
  2. 再说完全串行虽安全但效率低
  3. 所以数据库设置不同隔离级别做权衡
  4. 最后补充各级别可避免的问题
9. 简短总结

这个模块最重要的理解是:

事务让多步操作变成一个可靠单位,并发控制让多个事务可以在"尽量不乱"的前提下同时推进。

数据库真正难,也真正精彩的地方,就在这里:它既不能完全放任并发,也不能把所有人都堵死,只能在正确性和性能之间不断做精细平衡。

模块 9:故障恢复机制

1. 本模块解决的问题

前面讲事务时,你已经知道事务要求"提交后尽量不丢"。可新的问题来了:

  • 如果数据库刚写到一半机器突然断电怎么办?
  • 如果内存里的修改还没刷盘,系统崩了怎么办?
  • 如果已经写了一部分磁盘,另一部分还没写,怎么恢复到正确状态?
  • 为什么"日志"能帮助恢复?

这一模块解决的核心是:数据库发生故障后,如何把数据恢复到一致、可信的状态。

2. 核心概念及术语
术语 含义 通俗解释
故障恢复 故障发生后重建正确数据库状态 把系统从"半路出错"拉回可用状态
事务故障 单个事务出错 一笔业务自己失败
系统故障 数据库系统宕机、断电等 整个服务突然停掉
介质故障 磁盘损坏等物理介质问题 数据文件本体出大问题
日志(Log) 记录事务更新信息的顺序文件 像黑匣子
Undo 撤销未完成事务的影响 把不该留下的改动擦掉
Redo 重做已提交事务的影响 把该落地但还没完全落地的改动补齐
检查点(Checkpoint) 恢复时的已知安全位置 像游戏存档点
WAL 先写日志,再写数据 恢复正确性的关键纪律
3. 先讲直觉:为什么恢复不是"重新启动数据库"那么简单

假设事务 T1 已经执行:

  1. 账户 A 扣 100
  2. 账户 B 加 100

如果系统在第一步写盘后、第二步写盘前突然断电,此时磁盘中的数据是不一致的。

重新开机并不能自动解决这个问题。数据库必须知道:

  • 哪些事务已经提交,必须保留
  • 哪些事务没完成,必须撤销
  • 哪些修改已经写到磁盘
  • 哪些修改只存在于内存或部分页中

所以恢复系统的核心不是"重启",而是根据历史记录做出正确回放或回滚

苏格拉底式提问:为什么日志必须先于数据写盘

问:如果我先把数据写到磁盘,再慢慢写日志,不行吗?

答:不行。

问:为什么?

答:因为如果数据先写了一半,日志还没写,系统崩溃后你就不知道该撤销还是重做什么。

问:所以必须先把"发生了什么"记下来?

答:对。这就是 WAL 的本质:先留下证据,再实施改动。

4. 关键机制、规则与方法
4.1 故障类型
故障类型 特点 恢复重点
事务故障 单个事务逻辑错误、违反约束、被回滚 Undo
系统故障 断电、宕机,内存丢失但磁盘日志仍在 Undo + Redo
介质故障 磁盘损坏、文件破坏 备份恢复 + 日志重演
4.2 日志记录内容

数据库日志通常记录:

  • 事务开始
  • 事务修改了哪个数据项
  • 修改前值
  • 修改后值
  • 提交或回滚标记

为什么要同时记录前值和后值?

  • 前值支持 Undo
  • 后值支持 Redo
4.3 Undo 与 Redo 的因果逻辑

可以用一句话记:

  • 未提交的,撤销
  • 已提交的,重做

为什么"已提交的还要重做"?

因为"事务提交"不代表所有数据页都已经真正刷到磁盘。提交时系统常常只是保证日志足够安全。若系统随后崩溃,那些还没真正落盘的修改就需要 Redo。

4.4 检查点

恢复如果每次都从日志开头扫,会非常慢。于是数据库会定期做检查点:

  • 将某时刻之前的关键状态落地
  • 记录一个恢复起点

这样故障恢复时,不必从远古日志开始重放。

检查点的价值不是"防故障",而是缩短恢复时间

4.5 缓冲区管理与脏页

数据库通常先在内存缓冲区修改页,再择机刷盘。

这就会产生:

  • 脏页:内存中已修改但尚未写回磁盘的页

恢复之所以复杂,就是因为:

  • 事务提交顺序
  • 数据页刷盘顺序
  • 日志写入顺序

这三者不一定一致。

4.6 恢复过程的一般思路

很多教材把恢复过程拆成类似以下思路:

  1. 分析:确定哪些事务已提交,哪些未完成
  2. Redo:重做需要保留的已提交事务
  3. Undo:撤销未提交事务的影响

不同数据库实现会更复杂,但考试里抓住这条主线就够了。

4.7 影子分页(Shadow Paging)

有些课程还会提影子分页思想:

  • 不直接覆盖旧页
  • 修改时生成新页
  • 最后通过指针切换生效

它也能支持恢复,但现代主流数据库更常结合日志机制实现高效恢复。

4.8 备份与日志恢复的关系

介质故障时,仅靠在线日志未必够,因为数据文件本体可能都没了。这时要:

  1. 先恢复最近备份
  2. 再用日志把备份之后的更新重演出来

所以高可用和可恢复,不只靠事务日志,还靠备份体系。

5. 易混点与常见误区
误区 1:事务提交就代表数据一定已经写入所有数据页

不一定。提交更多是"逻辑上生效",而物理页刷盘可能稍后进行。

误区 2:恢复只和系统崩溃有关

单个事务失败也属于恢复处理范围。

误区 3:Undo 和 Redo 含义记反

记忆法:

  • Undo 撤回不该留下的
  • Redo 补上应该留下但还没彻底落地的
误区 4:检查点能消除日志

检查点主要是缩小恢复扫描范围,不是让日志完全失去意义。

6. 考试常见问法
  1. 为什么数据库需要恢复机制?
  2. 什么是事务故障、系统故障、介质故障?
  3. 什么是日志?日志中通常记录什么信息?
  4. 什么是 Undo、Redo?
  5. 为什么要采用先写日志后写数据的原则?
  6. 什么是检查点?其作用是什么?
  7. 备份与日志恢复如何配合?
7. 典型例题

例题:说明 WAL 原则的含义及作用。

作答模板:

  • 含义:在数据页写回磁盘前,必须先将对应日志写入稳定存储
  • 原因:防止系统崩溃后缺乏恢复依据
  • 作用:支持 Undo/Redo,保证恢复正确性

例题:系统故障后为什么既要 Undo 又要 Redo?

答题思路:

  • 因为系统故障时内存丢失,磁盘状态可能"半新半旧"
  • 未提交事务的影响不应保留,所以要 Undo
  • 已提交事务的影响可能尚未完全写盘,所以要 Redo
8. 解题思路

恢复题要按"故障类型 → 影响 → 恢复动作"来写。

例如:

  • 事务故障:回滚本事务
  • 系统故障:分析后对未提交事务 Undo、对已提交事务 Redo
  • 介质故障:用备份重建,再重放日志
9. 简短总结

恢复机制的核心思想就一句话:

数据库之所以敢先在内存里工作、敢并发执行、敢延迟刷盘,是因为它把"发生了什么"可靠地记进了日志。

日志让系统即使在故障后,也不必靠运气,而是能按证据恢复。

模块 10:完整性、安全性、视图与授权

1. 本模块解决的问题

即使数据库设计得很好、查询很快、并发控制也做了,仍然还有两个现实问题:

  • 用户会不会把非法数据写进来?
  • 不同用户能不能看到或修改不该碰的数据?

这一模块解决的是:如何让数据库既"数据正确",又"访问合规"。

2. 核心概念与术语
术语 含义 常见实现
完整性约束 限制数据必须满足的规则 主键、外键、唯一、检查
安全性 防止非法或越权访问数据库 授权、认证、视图、审计
视图 从基本表导出的虚拟表 简化查询、增强安全
角色 权限的集合 便于统一授权
触发器 某事件发生时自动执行的程序 自动检查、联动处理
存储过程 预定义在数据库中的程序 复用逻辑、封装操作
3. 先讲直觉:为什么"程序员注意点"不够

有人会说,完整性和安全性让应用程序自己检查不就行了?

这思路不完整。

因为:

  • 应用程序可能有多个入口
  • 程序可能有漏洞
  • 运维、管理员、脚本都可能直接访问数据库
  • 如果约束只写在应用层,数据库本身就成了无防线仓库

所以数据库必须自己具备"底线规则"。

苏格拉底式提问:为什么视图能增强安全性

问:视图不是只是方便写查询吗?

答:不只是。

问:它怎么增强安全?

答:因为你可以只给用户一个视图,而不给底层表。

问:这样有什么好处?

答:用户只能看到经过筛选和加工后的数据,而不是整个原始表。

例如学生查询成绩系统,可以只给:

sql 复制代码
CREATE VIEW MyScore AS
SELECT Sno, Cno, Grade
FROM SC;

再结合行级过滤或应用层控制,就能显著减少暴露面。

4. 关键规则、机制与方法
4.1 完整性约束

前面提过三大完整性,这里系统化整理:

类型 保护什么 例子
实体完整性 每行必须可唯一标识 主键不能为空且唯一
参照完整性 关系之间引用必须合法 选课表中的学号必须存在于学生表
用户定义完整性 业务自定义规则 成绩应在 0 到 100 之间

SQL 中常见表达:

sql 复制代码
CREATE TABLE SC (
    Sno CHAR(10),
    Cno CHAR(10),
    Grade INT CHECK (Grade BETWEEN 0 AND 100),
    PRIMARY KEY (Sno, Cno),
    FOREIGN KEY (Sno) REFERENCES Student(Sno),
    FOREIGN KEY (Cno) REFERENCES Course(Cno)
);
4.2 安全控制的几个层次

数据库安全性不是单一机制,而是多层协同:

  1. 身份认证:你是谁
  2. 权限控制:你能做什么
  3. 视图隔离:你能看见什么
  4. 审计与日志:你做过什么
  5. 加密与备份保护:数据被拿走后还能不能看懂
4.3 授权与回收

常见 SQL:

sql 复制代码
GRANT SELECT, INSERT
ON Student
TO user_a;
sql 复制代码
REVOKE INSERT
ON Student
FROM user_a;

课程考试里通常考的是思想:

  • 权限应最小化
  • 按角色授权优于按用户零散授权
  • 需要时可回收权限
4.4 视图的三大价值
  1. 简化查询

    把复杂连接封装起来。

  2. 逻辑独立性

    底层表结构调整时,视图可作为缓冲层。

  3. 安全性

    用户只接触需要的数据,不直接面对全部原表。

4.5 触发器与存储过程

课程中常作为"了解 + 会解释用途"的内容:

  • 触发器:响应 INSERTUPDATEDELETE 自动执行
  • 存储过程:把一段数据库逻辑封装在服务端

它们的优点:

  • 集中控制规则
  • 降低重复逻辑
  • 可做审计和自动维护

但也要知道其代价:

  • 逻辑可能隐藏得更深
  • 调试和迁移成本上升
5. 易混点与常见误区
误区 1:安全性和完整性是一回事

不是。

  • 完整性关注"数据本身是否合法"
  • 安全性关注"谁能访问和如何访问"
误区 2:视图一定存数据

普通视图通常不物理存储数据,只保存定义。

误区 3:外键只是程序员约定

外键是数据库层约束,不是口头规范。

误区 4:权限越细越安全

过细的权限体系若难以维护,也可能带来配置错误。合理分层、角色化更重要。

6. 考试常见问法
  1. 什么是完整性约束?有哪些类型?
  2. 什么是实体完整性、参照完整性?
  3. 什么是视图?视图有哪些作用?
  4. 数据库安全性通常通过哪些机制实现?
  5. 什么是授权和回收权限?
  6. 触发器和存储过程的用途是什么?
7. 典型例题

例题:说明视图在数据库系统中的作用。

标准答题点:

  • 简化用户操作
  • 屏蔽底层复杂结构
  • 提供逻辑独立性
  • 增强安全性,只暴露部分数据

例题:说明参照完整性为什么重要。

答题思路:

  • 先定义:外键值必须引用有效主码或为空
  • 再说明:否则会出现"选课记录引用不存在学生"这类脏数据
  • 最后说明:数据库通过外键约束维护关系正确性
8. 解题思路

本模块题目多偏概念说明。最稳答法是:

  1. 定义
  2. 作用
  3. 实现方式
  4. 例子

例如答"数据库安全性"时,不要只写"防止非法访问",最好继续补:

  • 通过认证、授权、视图、审计等机制实现
  • 目的是保证数据机密性、可控性、可追踪性
9. 简短总结

完整性是在防止"错误数据进入系统",安全性是在防止"错误的人碰到数据"。而视图、约束、授权等机制,正是数据库作为"受控系统"而不是"裸数据仓库"的体现。

模块 11:分布式数据库与 NoSQL 概览

1. 本模块解决的问题

当数据量变大、访问量变高、业务类型变复杂时,单机关系数据库就可能遇到瓶颈。于是新问题出现:

  • 一台机器存不下怎么办?
  • 并发太高扛不住怎么办?
  • 数据分布在多个节点时,如何保持一致?
  • 为什么会出现键值数据库、文档数据库、列族数据库、图数据库?

这一模块是数据库课程的现代扩展部分,帮助你把"传统数据库原理"连接到真实系统世界。

2. 核心概念及术语
概念 含义 直白解释
分布式数据库 数据分布在多个节点上统一管理 数据不再只在一台机器上
复制 同一份数据保存多份副本 为了高可用和读扩展
分片 / 分区 把数据拆开存到不同节点 为了容量和写扩展
一致性 多副本或多节点之间数据是否一致 看到的是不是同一版本
可用性 系统是否能持续响应请求 挂一台还能不能用
分区容错性 网络分裂时系统能否继续工作 分布式系统必须面对的问题
NoSQL 非传统关系模型数据库统称 面向特定场景的多样化方案
3. 通俗理解与因果推理
3.1 为什么会从单机走向分布式

单机数据库强在:

  • 事务语义清晰
  • 一致性强
  • 管理集中

但它的限制也明显:

  • 磁盘、CPU、内存都有上限
  • 单点故障风险高
  • 海量流量时扩展成本高

于是系统就会做两件事:

  1. 复制:同一份数据多放几份,提高可用性和读取能力
  2. 分片:把不同数据拆开放到不同机器,提升容量和并行度
3.2 CAP 直觉

CAP 不是考试中最适合被神化的概念,但它很有助于你建立分布式直觉。

它强调,在网络分区存在的情况下,系统往往要在:

  • 一致性(C)
  • 可用性(A)
  • 分区容错性(P)

之间做取舍。

因为分布式系统一旦网络不可靠,就不可能像单机那样"默认大家立刻看到同一状态"。

3.3 为什么会有 NoSQL

问:关系数据库不好吗?

答:很好,但不是所有场景都最适合它。

问:哪些场景会逼出新类型数据库?

答:

  • 数据模式经常变化
  • 超大规模写入
  • 极高并发缓存访问
  • 图关系复杂
  • 追求横向扩展优先

于是出现了不同偏好的数据库:

  • 键值型:追求极快读写
  • 文档型:结构灵活
  • 列族型:适合大规模分布式存储
  • 图数据库:擅长关系路径查询
4. 关键机制与方法
4.1 复制

复制常见作用:

  • 提高可用性
  • 支持读扩展
  • 灾难恢复

常见模式:

  • 主从复制
  • 多主复制

课程考试里一般要求知道:

  • 写入通常先到主节点
  • 再传播到从节点
  • 如果是异步复制,主从间可能短暂不一致
4.2 分片

分片是把数据按某种规则拆到不同节点。

常见分片键:

  • 用户 ID
  • 地区
  • 时间

分片收益:

  • 单机压力下降
  • 容量提升
  • 并行处理能力增强

但新问题也随之而来:

  • 跨分片查询更复杂
  • 跨分片事务更难
  • 热点分片可能失衡
4.3 分布式事务直觉

单机事务已经很复杂,跨节点事务更难。因为:

  • 节点可能失败
  • 网络可能延迟或分裂
  • 多方需要达成一致

课程中常提两阶段提交(2PC):

  1. 准备阶段:询问各参与者是否能提交
  2. 提交阶段:协调者统一决定提交或回滚

它提高了一致性,但也会带来阻塞和协调开销。

4.4 NoSQL 四类典型模型
类型 代表结构 优点 典型场景
键值数据库 Key -> Value 极快、简单 缓存、会话
文档数据库 JSON/BSON 文档 结构灵活 内容系统、快速迭代业务
列族数据库 列簇组织 适合海量分布式数据 日志、宽表、分析
图数据库 点和边 关系遍历能力强 社交关系、推荐、知识图谱
4.5 关系数据库与 NoSQL 的关系

不要把它们看成"谁取代谁"。

更准确地说:

  • 关系数据库擅长结构化数据、一致性、复杂查询、事务
  • NoSQL 更强调特定场景下的扩展性、灵活性或访问模式

现代系统中,它们常常协同使用,而不是彼此消灭。

5. 易混点与常见误区
误区 1:NoSQL 就是不支持 SQL

NoSQL 更准确是"Not Only SQL",强调不只一种数据模型,不等于完全没有查询语言。

误区 2:NoSQL 一定比关系数据库先进

不是,是场景不同、取舍不同。

误区 3:分布式一定更好

分布式会带来额外复杂性,只有在单机确实无法满足时才值得。

误区 4:复制等于分片
  • 复制是同一份数据多份保存
  • 分片是不同数据分散保存
6. 考试常见问法
  1. 为什么需要分布式数据库?
  2. 什么是复制与分片?二者有何区别?
  3. CAP 反映了什么分布式系统矛盾?
  4. 什么是两阶段提交?
  5. NoSQL 产生的原因是什么?
  6. 比较关系数据库与 NoSQL 数据库的适用场景。
7. 典型例题

例题:简述关系数据库和 NoSQL 数据库的差异。

答题思路:

  • 数据模型:关系模型 vs 多样化模型
  • 模式:固定模式较强 vs 模式灵活
  • 事务一致性:关系库更强
  • 扩展方式:NoSQL 往往更强调横向扩展
  • 适用场景:复杂事务与查询 vs 高并发、大规模、特定访问模式
8. 解题思路

这一模块答题不要陷入技术细枝末节,重点是:

  1. 为什么会出现新技术
  2. 它解决什么原本难解决的问题
  3. 它为此牺牲了什么
9. 简短总结

这一模块最重要的认知是:

数据库世界没有万能方案。单机关系数据库、分布式数据库、NoSQL,都是在不同约束下做的取舍。

理解它们,不是为了追潮流,而是为了知道:当问题变了,数据库设计为什么也必须跟着变。

模块 12:综合设计与工程落地

1. 本模块解决的问题

学到这里,你已经分别学过建模、SQL、范式、索引、事务、恢复和安全。但考试和项目里真正难的地方,是把这些内容连起来。

这一模块解决的是:

  • 面对一个业务场景,如何从需求一路走到数据库实现?
  • 为什么"表设计、索引设计、事务边界、权限控制、备份恢复"要一起考虑?
  • 综合题该怎么答,项目实践该怎么想?
2. 综合方法论:从需求到数据库的完整链条

可以把数据库设计与实现看成九步:

  1. 明确业务对象与规则
  2. 做概念建模(E-R)
  3. 转换为关系模式
  4. 判断主键、外键与完整性约束
  5. 用范式优化结构,控制冗余
  6. 根据查询场景设计索引
  7. 根据业务原子性划定事务边界
  8. 根据用户角色设计视图和权限
  9. 根据可靠性需求设计备份、恢复、复制策略

这九步中,任何一步省略,系统都有可能在后面返工。

3. 综合案例:教学管理系统
3.1 需求描述

某教学系统需要支持:

  • 维护学生信息
  • 维护教师信息
  • 维护课程信息
  • 支持学生选课与退课
  • 支持教师录入成绩
  • 支持查询某学生成绩单
  • 支持统计某课程平均分
  • 不同角色权限不同:学生、教师、管理员
3.2 概念建模

实体:

  • 学生
  • 教师
  • 课程

联系:

  • 教师授课
  • 学生选课

可能的属性:

  • 学生:学号、姓名、专业、年级
  • 教师:工号、姓名、职称
  • 课程:课程号、课程名、学分、授课教师号
  • 选课联系:学号、课程号、成绩、选课时间
3.3 关系模式设计
text 复制代码
Student(学号, 姓名, 专业, 年级)
Teacher(工号, 姓名, 职称)
Course(课程号, 课程名, 学分, 工号)
SC(学号, 课程号, 成绩, 选课时间)

约束:

  • Student.学号 主键
  • Teacher.工号 主键
  • Course.课程号 主键
  • Course.工号 外键引用 Teacher
  • SC(学号, 课程号) 组合主键
  • SC.学号 外键引用 Student
  • SC.课程号 外键引用 Course
  • 成绩 取值范围 0 到 100
3.4 规范化分析

为什么这样设计较合理?

  • 学生信息只由学号决定
  • 教师信息只由工号决定
  • 课程信息只由课程号决定
  • 选课成绩由 (学号, 课程号) 决定

不同事实由不同决定因素负责,减少冗余与异常。

3.5 索引设计

如果系统高频查询包括:

  • 按学号查成绩单
  • 按课程号查选课名单
  • 按教师查授课课程

则可考虑:

  • SC(学号) 索引
  • SC(课程号) 索引
  • Course(工号) 索引

若经常按 (学号, 课程号) 联合查询,则组合主键本身已天然支持。

3.6 事务设计

选课操作看似简单,但本质上可能包含:

  1. 检查课程是否存在
  2. 检查是否已选
  3. 检查人数是否已满
  4. 写入选课记录
  5. 更新课程已选人数

这些步骤必须在同一事务中完成,否则容易出现:

  • 重复选课
  • 超卖名额
  • 记录写了但人数没更新
3.7 权限设计

不同角色权限不同:

  • 学生:可查询自己信息和成绩
  • 教师:可查询自己课程名单、录入成绩
  • 管理员:可维护基础数据

这就说明数据库不是"所有人都能直接查所有表",而要通过角色、视图和授权控制访问面。

3.8 恢复与可用性设计

系统上线后还要考虑:

  • 定期备份
  • 日志保留
  • 主从复制
  • 故障恢复演练

否则再好的逻辑设计,也可能在一次硬盘损坏后归零。

4. 考试综合题怎么答

数据库综合设计题常常把多个模块混在一起。答题时建议顺序:

  1. 先找实体和联系
  2. 画简版 E-R
  3. 转关系模式
  4. 标主键、外键、约束
  5. 看是否存在冗余与异常
  6. 提出规范化结果
  7. 说明典型查询需要的索引
  8. 若题目涉及并发,指出事务边界
  9. 若题目涉及安全,说明角色/视图设计
5. 工程实践中的常见权衡

数据库理论喜欢干净,工程实践必须面对成本。

常见权衡包括:

  • 范式越高,结构越纯,但连接可能越多
  • 索引越多,读可能越快,但写入越慢
  • 隔离级别越高,正确性越强,但并发越低
  • 强一致越强,分布式扩展代价越高

所以真正成熟的数据库设计,不是追求某一个指标极致,而是根据业务目标做平衡

6. 易混点与常见误区
误区 1:综合题只要把表列出来就行

不够。综合题真正考查的是你有没有把"结构、约束、查询、并发、安全、恢复"串起来。

误区 2:工程实践和考试理论完全割裂

恰恰相反,工程里的很多问题正是理论概念的现实体现。

误区 3:数据库设计做完表结构就结束

表结构只是开始,索引、事务、权限、备份同样重要。

7. 考试常见问法
  1. 根据业务需求设计关系模式。
  2. 指出主键、外键和完整性约束。
  3. 说明是否存在冗余、如何规范化。
  4. 为高频查询设计索引。
  5. 指出哪些操作应放入同一事务。
  6. 设计用户权限与视图。
8. 典型例题

例题:为图书借阅系统设计数据库。

通常至少包括:

  • 图书
  • 读者
  • 借阅记录
  • 管理员

答题关键不是表越多越好,而是:

  • 先把"图书信息"和"借阅行为"分开
  • 再把"借阅记录"作为联系表处理
  • 最后考虑库存、超期、权限、事务
9. 解题思路

综合题一定要"先宏观后微观":

  1. 先搭数据对象骨架
  2. 再加约束和依赖
  3. 再讲查询与索引
  4. 最后讲事务和安全
10. 简短总结

真正学会数据库,不是会背十几个名词,而是面对一个具体系统时,能顺着下面这条链往下走:

需求 -> 建模 -> 关系模式 -> 范式 -> SQL -> 索引 -> 事务 -> 恢复 -> 安全 -> 扩展

当你能顺着这条链思考,数据库这门课就不再是碎片知识,而是一套完整的问题解决能力。


高频考点与考试重点

一、最值得优先掌握的 20% 内容

如果时间很紧,下面这些内容必须优先吃透。它们大约只占全课程知识量的 20%,但通常覆盖了 70% 到 80% 的考试分值。

优先级 内容 为什么必须先掌握
1 数据库系统基础、三级模式、数据独立性 几乎所有概念题的开头
2 E-R 模型、关系模型、主键外键、完整性 设计题和概念题核心
3 函数依赖、候选码、1NF/2NF/3NF/BCNF 高频大题核心
4 SQL 查询:连接、分组、子查询、视图 最稳定的得分点
5 索引、B+ 树、聚簇索引、索引失效 原理题和应用题高频
6 事务 ACID、并发异常、隔离级别、两段锁 概念题和分析题高频
7 日志、Undo/Redo、WAL、检查点 恢复题常考
8 安全性、授权、视图、完整性约束 概念题稳定出分

如果你真的只剩三四天,优先顺序建议就是:

  1. 范式与函数依赖
  2. SQL
  3. 事务与并发
  4. 索引与恢复
  5. 其余章节补定义和大框架

二、高频考点清单

1. 概念基础类
  • 数据库、DBMS、数据库系统的区别
  • 数据模型三层次
  • 三级模式结构与两级映像
  • 数据独立性的含义
  • 文件系统与数据库系统对比
2. 建模与关系模型类
  • 实体、属性、联系、码
  • E-R 图设计
  • E-R 到关系模式转换
  • 实体完整性、参照完整性
  • 候选码、主码、外码判断
3. 关系理论类
  • 选择、投影、连接、除法
  • 关系代数表达查询
  • 函数依赖定义与判断
  • Armstrong 公理
  • 属性闭包求法
  • 范式判断与分解
  • 无损连接与保持依赖
4. SQL 类
  • 基础查询、条件过滤、排序
  • 聚合与分组
  • WHEREHAVING
  • 多表连接
  • 子查询:INEXISTS
  • 建表、约束、视图
5. 存储与索引类
  • 页、记录、文件组织
  • B+ 树索引原理
  • 聚簇/非聚簇索引
  • 哈希索引对比
  • 组合索引与最左前缀
  • 索引失效原因
6. 事务与恢复类
  • 事务四大特性 ACID
  • 脏读、不可重复读、幻读
  • 共享锁、排他锁
  • 两段锁协议
  • 可串行化直觉
  • 日志记录
  • Undo/Redo
  • WAL
  • 检查点
7. 安全与扩展类
  • 完整性约束三类型
  • 授权、回收权限
  • 视图的作用
  • 复制与分片
  • 关系数据库与 NoSQL 对比

三、必背概念、公式、定理或方法

必背定义
  1. 数据库、DBMS、数据库系统
  2. 模式与实例
  3. 数据独立性
  4. 候选码、主码、外码
  5. 实体完整性、参照完整性
  6. 函数依赖、完全函数依赖、部分函数依赖、传递函数依赖
  7. 1NF、2NF、3NF、BCNF
  8. 事务、ACID
  9. 脏读、不可重复读、幻读
  10. Undo、Redo、WAL、检查点
必背符号与公式

函数依赖:

X→Y X \rightarrow Y X→Y

属性闭包:

X+={A∣X⇒FA} X^+ = \{A \mid X \Rightarrow_F A\} X+={A∣X⇒FA}

关系代数常用符号:

  • 选择:σ
  • 投影:π
  • 连接:
  • 并:
  • 差:-
  • 除:÷

无损连接分解常见判断条件(两关系分解时):

(R1 ∩ R2) -> R1(R1 ∩ R2) -> R2,则通常为无损连接分解。

必背方法
  1. 求候选码:用闭包看是否覆盖全部属性,再判最小性
  2. 判断 2NF:看是否有对组合码的部分依赖
  3. 判断 3NF:看是否有非主属性对码的传递依赖
  4. 关系代数解题:先找表,再筛行,再连表,后取列
  5. SQL 解题:来源表 -> 条件 -> 分组 -> 输出
  6. 事务题:先认并发异常,再配隔离机制
  7. 恢复题:先分故障类型,再说 Undo/Redo

四、常见题型分类

题型 高频程度 分值特点 解题关键
名词解释 / 简答题 ★★★★★ 分散但稳定 定义 + 作用 + 对比
E-R 建模题 ★★★★ 中等到偏大 找实体、联系、基数
候选码 / 函数依赖 / 范式题 ★★★★★ 大题核心 闭包、依赖、分解
关系代数题 ★★★★ 稳定考查逻辑 筛行、连表、取列
SQL 编写题 ★★★★★ 最好拿分 审题准确、分组/连接清晰
索引原理题 ★★★★ 原理分较多 I/O、B+ 树、适用场景
事务并发分析题 ★★★★★ 概念+分析 异常类型、锁、隔离级别
恢复机制题 ★★★★ 常考原理 日志、Undo/Redo、WAL
综合设计题 ★★★★ 分值高 建模 + 约束 + 范式 + 索引 + 事务

五、考试最容易丢分的地方

  1. 把"数据库""数据库系统""DBMS"写成同义词
  2. 2NF、3NF、BCNF 定义混淆
  3. 候选码没判最小性
  4. 关系代数漏连接条件
  5. SQL 分组题把聚合条件写到 WHERE
  6. 多表查询漏掉连接键导致笛卡尔积
  7. NULL 判断写成 = NULL
  8. 只写事务四大特性英文,不解释含义
  9. 分不清脏读、不可重复读、幻读
  10. 恢复题没区分未提交事务和已提交事务

六、各章一句话提分抓手

章节 一句话抓手
基础概念 先分清层次,再记名词
建模 先找实体和联系,再谈表
关系理论 先看谁决定谁,再谈范式
SQL 先想逻辑顺序,再写语法
索引 先想如何少读页,再谈结构
事务 先想并发会乱在哪里,再选机制
恢复 先看哪些该保留,哪些该撤销
安全 先看谁能访问,再看如何约束

典型题型与解题模板

题型 1:名词解释 / 简答定义题

常见问法
  • 什么是数据独立性?
  • 什么是候选码?
  • 什么是事务?
  • 什么是幻读?
解题模板
  1. 给出标准定义
  2. 说明其作用或意义
  3. 若题目涉及比较,再补区别
  4. 最后给一个短例子
示例模板

"数据独立性是指应用程序与数据库的数据结构之间相互独立的特性。其作用是使数据库某一层次的改变尽量不影响其他层次,提高系统可维护性。数据独立性包括逻辑独立性和物理独立性。"

题型 2:E-R 建模题

解题模板
  1. 从题目中圈出核心对象
  2. 判断对象是实体还是联系
  3. 提取每个实体属性
  4. 判断联系基数:1:1、1:N、M:N
  5. 看联系本身是否带属性
  6. 画图或文字说明后转换为关系模式
自检问题
  • 有没有把"联系"错当成"实体"?
  • 有没有漏掉多对多关系对应的中间表?
  • 主键是否明确?

题型 3:求候选码 / 闭包题

解题模板
  1. 写出函数依赖集
  2. 选一个可能的属性集 X
  3. X^+
  4. 若闭包覆盖全部属性,则 X 是超码
  5. 检查 X 是否还能删减属性
  6. 能唯一且最小,则为候选码
核心口诀

先看能否决定全体,再看是否最小。

题型 4:范式判断题

解题模板
  1. 先说明是否满足 1NF
  2. 找候选码、主属性、非主属性
  3. 检查是否有部分依赖
  4. 检查是否有传递依赖
  5. 若考 BCNF,再检查每个决定因素是否为超码
  6. 最终给出最高范式并说明理由

题型 5:关系模式分解题

解题模板
  1. 明确原关系中存在哪类异常
  2. 根据函数依赖拆分
  3. 写出子关系模式
  4. 标出主键
  5. 说明消除了哪些冗余或异常
  6. 若题目要求,再判断无损连接与依赖保持

题型 6:关系代数表达题

解题模板
  1. 先确定输出列
  2. 再确定涉及的表
  3. 用选择表示条件过滤
  4. 用连接把相关表连起来
  5. 用投影输出最终属性
  6. 若题目有"全部",考虑除法
核心口诀

先找表,再筛行,再连表,后取列。

题型 7:基础 SQL 查询题

解题模板
  1. 确认查询目标列
  2. 确认来源表
  3. WHERE 原始条件
  4. 若多表,先写连接条件
  5. 若需要排序,加 ORDER BY
  6. 若需要去重,加 DISTINCT

题型 8:分组与聚合 SQL 题

解题模板
  1. 看题目是否有"每个""平均""总数""最大值"等词
  2. 若有,通常考虑 GROUP BY
  3. 聚合前过滤用 WHERE
  4. 聚合后过滤用 HAVING
  5. 输出列里非聚合属性必须与分组一致
核心口诀

行条件看 WHERE,组条件看 HAVING

题型 9:连接与子查询 SQL 题

解题模板
  1. 判断是直接连接更清晰,还是子查询更自然
  2. 涉及"存在某类记录"时可优先想 EXISTS
  3. 涉及"属于集合"时可考虑 IN
  4. 涉及"高于平均值"时常用标量子查询
  5. 涉及"没有......的对象"时优先考虑 NOT EXISTS

题型 10:索引原理题

解题模板
  1. 先说明索引的作用:减少查找范围和 I/O
  2. 再说明具体结构特点:B+ 树多路平衡、叶子链式
  3. 再说明适用场景:等值、范围、排序
  4. 最后补充代价:维护成本、空间成本

题型 11:事务并发分析题

解题模板
  1. 找出事务之间对同一数据项的交叉操作
  2. 判断出现的是脏读、不可重复读、幻读还是丢失修改
  3. 若考锁,说明使用 S 锁 / X 锁或两段锁
  4. 若考隔离级别,指出哪个级别能避免对应问题

题型 12:恢复机制题

解题模板
  1. 先分故障类型
  2. 说明日志记录作用
  3. 指出哪些事务需 Undo,哪些需 Redo
  4. 若问 WAL,就强调先写日志后写数据
  5. 若问检查点,就强调缩短恢复时间

题型 13:综合设计题

解题模板
  1. 概念建模:实体、属性、联系
  2. 关系设计:关系模式、主键、外键
  3. 规范化:说明依赖与拆分
  4. 约束:完整性条件
  5. 索引:围绕高频查询设计
  6. 事务:围绕关键业务动作划界
  7. 安全:角色、视图、授权

题型 14:对比类论述题

常见对比
  • 文件系统 vs 数据库系统
  • 候选码 vs 主码
  • 2NF vs 3NF
  • 聚簇索引 vs 非聚簇索引
  • 脏读 vs 不可重复读 vs 幻读
  • 关系数据库 vs NoSQL
解题模板
  1. 分别定义
  2. 对比关键差异
  3. 说明各自适用场景或作用

易错点与避坑指南

一、概念层易错点

1. "数据库"和"数据库系统"不区分

避坑方法:

  • 数据库是数据集合
  • DBMS 是管理软件
  • 数据库系统是完整运行环境
2. "模式"和"实例"不区分

避坑方法:

  • 模式是结构
  • 实例是某时刻数据内容
3. "关系"和"联系"混淆

避坑方法:

  • E-R 中的联系是实体之间关联
  • 关系模型中的关系是二维表所表示的数据集合

二、建模层易错点

4. M:N 联系不单独建表

这是设计题最常见丢分点。

避坑方法:看到"一个对多个、多个对一个,两边都不唯一",优先警惕 M:N,通常单独建中间表。

5. 把联系属性放错位置

例如"成绩"不是学生自身属性,也不是课程自身属性,而是"学生选课"这个联系的属性。

6. 外键没标清引用方向

避坑方法:外键要说明"引用谁的主键"。

三、关系理论易错点

7. 候选码只看唯一,不看最小

候选码必须是最小超码。

8. 不先求码就直接判断范式

避坑方法:凡涉及 2NF、3NF、BCNF,第一步几乎总是先求候选码。

9. 2NF 与 3NF 混淆

避坑方法:

  • 2NF 看是否依赖组合码的一部分
  • 3NF 看是否经由非主属性发生传递依赖
10. 无损连接和保持依赖分不清

避坑方法:

  • 无损连接看"分了还能完整拼回去吗"
  • 保持依赖看"原来的依赖还能直接检查吗"

四、关系代数与 SQL 易错点

11. 关系代数只写投影,不写选择或连接条件

避坑方法:先列逻辑步骤,再写符号。

12. SQL 多表查询漏连接条件

避坑方法:写完 SQL 后,逐表检查每多一张表,有没有和已有结果建立关联条件。

13. WHEREHAVING 混淆

避坑方法:

  • 原始行过滤看 WHERE
  • 聚合结果过滤看 HAVING
14. NULL 比较写错

避坑方法:

  • 判断空值用 IS NULL
  • 判断非空用 IS NOT NULL
15. COUNT(*)COUNT(列名) 混淆

避坑方法:

  • 行数用 COUNT(*)
  • 某列非空个数用 COUNT(列名)
16. 视图当成真实存储表

避坑方法:普通视图更多是逻辑窗口,不是实体副本。

五、索引与优化易错点

17. 认为"加索引一定快"

避坑方法:记住索引也有维护代价,且选择性差时不一定有利。

18. 不会解释 B+ 树为何适合数据库

避坑方法:答题要从"磁盘页 I/O、树高低、范围查询友好"三方面说。

19. 忽略范围查询和哈希索引的差异

避坑方法:哈希更偏等值,B+ 树更通用。

六、事务与恢复易错点

20. ACID 只会背,不会联系业务

避坑方法:拿转账、下单、选课等场景去解释。

21. 脏读、不可重复读、幻读混淆

避坑方法:

  • 脏读:读到未提交
  • 不可重复读:同一行内容变
  • 幻读:满足条件的行集合变
22. 以为隔离级别越高越好

避坑方法:答题时补一句"隔离越强通常并发越低、开销越高"。

23. Undo / Redo 记反

避坑方法:

  • 未提交撤销
  • 已提交重做
24. WAL 作用答不完整

避坑方法:不仅写"先写日志",还要写"这样才能在故障后依据日志恢复"。

七、综合题易错点

25. 只写表结构,不写约束

综合题如果只给表不给主键、外键、完整性规则,分数会明显丢。

26. 只谈理论,不落查询与事务

好的综合答案必须把结构设计、查询需求、索引设计、并发与事务串起来。

八、最后一分钟速检清单

考场写完后,至少扫一遍这十项:

  1. 定义题是否写了"概念 + 作用"
  2. 对比题是否写出"区别点"
  3. 设计题是否标主键外键
  4. 范式题是否先求码
  5. SQL 是否漏连接条件
  6. SQL 聚合条件是否用对 HAVING
  7. 是否误用 NULL
  8. 事务异常是否分清
  9. 恢复题是否分清 Undo / Redo
  10. 论述题是否有"因果解释",不只是结论

满分复习策略

一、总策略:先搭框架,再抓高频,再刷题固化

数据库这门课最怕两种学法:

  • 只背定义,不理解问题链
  • 只刷 SQL,不懂数据库整体结构

高分策略应该是:

  1. 先把知识地图搭起来
  2. 再把高频板块吃透
  3. 最后通过题型模板固化输出

二、80 分以上的核心打法

第一步:拿下最稳分板块

最稳板块通常是:

  • 基础概念
  • E-R / 关系模型
  • SQL
  • 范式
  • 事务

这几块只要掌握到位,很多学校的数据库试卷已经能支撑 70 分以上。

第二步:把容易拉开差距的原理题补上

包括:

  • B+ 树为什么适合做索引
  • 为什么会有各种并发异常
  • 为什么日志可以恢复
  • 为什么视图可以增强安全性

这些题目不是死记硬背就能拿满,必须讲清因果链。

第三步:练"综合表达能力"

高分不是"知道",而是"在纸上写得完整"。

你要练的是:

  • 概念定义怎么写成标准答案
  • SQL 怎么写得不漏条件
  • 范式题怎么按步骤展开
  • 论述题怎么讲清"为什么"

三、最应该优先投入时间的内容比例

如果把总复习时间看成 100%,建议:

模块 时间占比
SQL 与关系代数 20%
函数依赖、范式、设计 25%
事务与并发控制 20%
基础概念与完整性/安全 10%
索引、存储、优化 15%
恢复、分布式、NoSQL 10%

四、高频考点背诵策略

数据库不适合全文死背,适合"模块化背诵"。

建议背诵顺序:

  1. 先背概念骨架
  2. 再背对比关系
  3. 最后背答题模板

例如:

  • 先背 ACID 四个词与含义
  • 再背脏读 / 不可重复读 / 幻读区别
  • 再背"事务题答题模板"

五、刷题策略

1. 概念题怎么刷

做法:

  • 每章列 5 到 10 个高频名词
  • 每个名词都练"定义 + 作用 + 对比"

标准:

  • 能在 30 秒内写出标准定义
  • 能在 1 分钟内写出区别与应用
2. SQL 题怎么刷

做法:

  • 单表查询 10 题
  • 连接查询 10 题
  • 分组聚合 10 题
  • 子查询 10 题
  • 视图与建表语句 5 题

标准:

  • 能独立写出
  • 能自己检查连接条件、分组条件和空值判断
3. 范式题怎么刷

做法:

  • 每次都强迫自己完整走一遍:
    • 依赖集
    • 闭包
    • 候选码
    • 主属性/非主属性
    • 范式判断
    • 分解

标准:

  • 不能"凭感觉"说 2NF / 3NF,必须能写出推理过程
4. 事务题怎么刷

做法:

  • 把每种并发异常都用一组简单操作自己演一遍
  • 画出时间顺序图
  • 对照不同隔离级别能防什么

标准:

  • 能看到调度就识别异常类型

六、容易被忽略但很值分的点

  1. 视图作用经常以简答题出现
  2. WHEREHAVING 区别极容易出 SQL 题
  3. 外键和参照完整性概念题稳定出现
  4. B+ 树和哈希索引对比很适合出论述题
  5. WAL 与 Undo/Redo 经常是恢复题核心

七、考前 3 天复习重点

第 3 天:搭总框架
  • 通读知识地图
  • 回看每章一句话抓手
  • 把高频定义、对比项、关键表格抄一遍
  • 整理错题类型
第 2 天:做高频题型
  • 2 套范式题
  • 2 套 SQL 综合题
  • 2 套事务 / 恢复分析题
  • 1 套综合设计题

要求:不只是做对,还要写完整步骤。

第 1 天:背模板 + 查漏补缺
  • 背必背定义和对比
  • 背事务异常和隔离级别表
  • 背索引与恢复类模板答案
  • 把最容易错的 SQL 题再看一遍

八、考场答题策略

1. 先做稳分题

优先顺序建议:

  1. 名词解释 / 简答
  2. SQL
  3. 范式题
  4. 事务与恢复分析
  5. 综合设计 / 论述
2. 概念题不要只写一句

至少写三层:

  1. 定义
  2. 作用
  3. 区别或例子
3. SQL 题写完必须二次检查

检查:

  • 表别名是否一致
  • 连接条件是否完整
  • 聚合条件是不是放在 HAVING
  • 是否需要 DISTINCT
4. 范式题宁可慢一点,也别跳步

很多失分不是不会,而是:

  • 没写闭包
  • 没说明候选码
  • 没写为什么不满足某范式
5. 论述题要讲"为什么"

例如答"为什么 B+ 树适合索引",不要只写"查询快",还要补:

  • 磁盘 I/O
  • 树高低
  • 范围查询友好

九、最后的高分心法

数据库想冲高分,真正有效的不是"多看几遍书",而是做到三件事:

  1. 脑中有因果链
  2. 手里有答题模板
  3. 笔下有完整表达

只会看懂不够,只会背也不够,必须把"理解 -> 结构化输出"这一步练出来。


分阶段学习计划

阶段一:快速入门阶段

学习目标

建立数据库整门课的整体框架,知道它研究什么、有哪些模块、模块之间如何衔接。

学习内容
  • 数据库、DBMS、数据库系统
  • 数据模型与三级模式
  • E-R 模型、关系模型
  • SQL 的整体位置
  • 事务、索引、恢复分别解决什么问题
推荐时间分配
  • 总计:2 到 3 天
  • 每天:2 到 3 小时
产出结果
  • 能手写一张数据库知识地图
  • 能用自己的话说出数据库的主线问题
  • 能说出每个大模块在解决什么
自测标准
  • 能不看书解释"为什么数据库不只是 SQL"
  • 能说出三级模式和数据独立性的关系
  • 能画出学生选课系统的简版 E-R 模型

阶段二:核心掌握阶段

学习目标

真正吃透最核心、最高频、最容易提分的内容,形成可答题的理解。

学习内容
  • 关系模型、主键外键、完整性
  • 函数依赖、候选码、范式
  • 关系代数
  • SQL 查询、连接、分组、子查询
  • 事务 ACID、并发异常、隔离级别
  • 索引与 B+ 树
推荐时间分配
  • 总计:7 到 10 天
  • 范式与函数依赖:30%
  • SQL:25%
  • 事务并发:20%
  • 索引与恢复:15%
  • 基础概念回顾:10%
产出结果
  • 一份自己的"高频概念笔记"
  • 一份"SQL 题模板"
  • 一份"范式题模板"
  • 一份"事务与恢复对比表"
自测标准
  • 能独立完成常规 SQL 题
  • 能完整写出范式判断过程
  • 能清楚区分脏读、不可重复读、幻读
  • 能解释 B+ 树为什么适合做索引

阶段三:刷题巩固阶段

学习目标

把理解转化为稳定得分能力,减少因步骤不全、表达不规范造成的丢分。

学习内容
  • 分题型刷题:定义题、范式题、关系代数题、SQL 题、事务恢复题、综合设计题
推荐时间分配
  • 总计:5 到 7 天
  • 每天至少完成:
    • SQL 题 3 到 5 题
    • 范式题 1 到 2 题
    • 简答题 5 题
    • 综合题 1 题
产出结果
  • 错题本,按错误类型分类
  • 高频题型标准答案模板
  • 一套自己的考前复盘清单
自测标准
  • 做过的题能在 24 小时后复述思路
  • 同类题再做时不再重复犯同类错误
  • 写答案时能自然按"定义 -> 原因 -> 方法 -> 结果"展开

阶段四:考前冲刺阶段

学习目标

查漏补缺、固化记忆、保持答题手感。

学习内容
  • 回看高频概念表
  • 回看公式 / 规则 / 方法表
  • 回看错题本
  • 回背题型模板
  • 计时做一套综合模拟
推荐时间分配
  • 总计:2 到 3 天
  • 每天 3 小时左右
产出结果
  • 一页纸速查表
  • 高频概念默写版
  • 个人易错点清单
自测标准
  • 关键概念能在短时间内默写
  • SQL 题和范式题手不生
  • 看到题目能快速匹配题型模板

阶段五:如果你时间极少,只剩 48 小时

第一优先
  • 数据库基础概念
  • SQL
  • 范式
  • 事务异常
第二优先
  • B+ 树索引
  • Undo / Redo / WAL
  • 视图与完整性
第三优先
  • 分布式与 NoSQL 只记框架型概念
48 小时压缩计划
时间段 学习内容
第 1 天上午 基础概念 + 三级模式 + 建模
第 1 天下午 SQL 基础 + 连接 + 分组
第 1 天晚上 函数依赖 + 范式
第 2 天上午 事务 + 并发异常 + 锁
第 2 天下午 索引 + 恢复
第 2 天晚上 背模板 + 看错题 + 一页纸速记

一页纸速查表

一页纸速查总纲

如果你考前只允许看这一页,请重点记下面这些。

1. 数据库全课主线

主线问题 对应模块 核心回答
数据如何抽象 建模、E-R、关系模型 先抽象对象,再落成表
数据如何查询 关系代数、SQL 本质是筛行、取列、连接、分组
数据如何设计得不乱 函数依赖、范式 按"谁决定谁"拆表
数据如何查得快 存储、索引、优化 通过目录结构减少 I/O
并发如何不乱 事务、锁、隔离级别、MVCC 正确性与并发性的平衡
崩溃如何恢复 日志、Undo/Redo、WAL、检查点 先留证据,再恢复状态
谁能看、谁能改 完整性、安全、视图、授权 控制数据合法性与访问范围

2. 最重要的八句记忆句

  1. 数据库不是文件堆,而是被统一管理的数据系统。
  2. 三级模式的核心目的,是隔离变化。
  3. E-R 解决"现实怎么抽象",关系模型解决"抽象怎么落表"。
  4. 关系代数不是折磨人,而是在训练查询骨架。
  5. 范式不是拆表游戏,而是在按依赖结构消除异常。
  6. 索引快的本质,是减少磁盘页访问。
  7. 事务解决多步操作的一致性,并发控制解决多人同时操作时的不乱。
  8. 恢复依赖日志,因为系统必须知道"发生过什么"。

3. SQL 极速检查卡

场景 关键点
普通查询 SELECT ... FROM ... WHERE ...
去重 DISTINCT
排序 ORDER BY
分组统计 GROUP BY + 聚合函数
聚合后过滤 HAVING
多表查询 JOIN ... ON ...
存在性判断 EXISTS
空值判断 IS NULL / IS NOT NULL

4. 范式判断极速卡

范式 核心判断点
1NF 一格一值,属性原子化
2NF 非主属性不允许对候选码有部分依赖
3NF 非主属性不允许对候选码有传递依赖
BCNF 每个决定因素都必须是超码

5. 事务异常极速卡

异常 一句话区分
脏读 读到未提交数据
不可重复读 同一行重复读内容变
幻读 同一条件重复查,行集合变
丢失修改 一个事务修改覆盖另一个事务修改

6. 恢复极速卡

术语 核心记忆
Undo 撤销未提交事务
Redo 重做已提交但未完全落盘事务
WAL 先写日志,再写数据
检查点 缩短恢复扫描范围

7. 索引极速卡

问题 速记答案
为什么索引能快 缩小查找范围,减少 I/O
为什么 B+ 树常用 多路平衡、树高低、范围查询友好
哈希索引适合什么 等值查询
索引为什么不是越多越好 占空间,更新维护慢

8. 考前最后 5 分钟必看

  1. WHEREHAVING 不要混
  2. NULL 不要写成 = NULL
  3. 范式题先求候选码
  4. 多表 SQL 一定检查连接条件
  5. 事务异常一定区分"读到未提交""同一行变""行集合变"

高频概念表

概念 标准理解 常见混淆 快速区分
数据库 有组织、可共享的数据集合 和 DBMS 混淆 数据是对象
DBMS 管理数据库的软件系统 和数据库系统混淆 软件是工具
数据库系统 DB + DBMS + 应用 + 环境 和数据库混淆 系统是整体
模式 数据库逻辑结构描述 和实例混淆 像蓝图
实例 某时刻数据库实际内容 和模式混淆 像当前住户
外模式 用户看到的局部视图 和页面表现混淆 面向不同用户
内模式 物理存储组织方式 和逻辑模式混淆 面向磁盘
实体 可区分对象 和联系混淆 例如学生
联系 实体之间关联 和关系混淆 例如选课
关系 元组集合 和联系混淆 例如一张表
元组 表中的一行 和属性混淆 一条记录
属性 表中的一列 和域混淆 一个字段
属性取值范围 和属性混淆 值的合法空间
候选码 最小超码 和主码混淆 可能有多个
主码 被选中的候选码 和候选码混淆 通常主要标识
外码 引用其他关系主码的属性 任意相同字段 必须有引用语义
函数依赖 一个属性集决定另一个属性集 程序计算关系 是数据语义约束
完全函数依赖 对整体依赖,不对真子集依赖 与传递依赖混淆 主要看组合码
传递依赖 通过中间属性间接依赖 与部分依赖混淆 主要看"绕一道"
事务 不可分逻辑工作单元 与 SQL 语句混淆 可包含多条 SQL
ACID 事务四大特性 只背英文缩写 要能解释作用
并发控制手段 和事务混淆 锁是手段,事务是单位
索引 辅助查找结构 和数据本体混淆 像目录
视图 虚拟表 和基本表混淆 通常存定义不存数据
检查点 恢复起点 和备份混淆 不是完整副本

高频公式 / 规则 / 方法表

公式 / 规则 / 方法 表达 用途 常见错误
函数依赖 X -> Y 表示 X 决定 Y 当作程序计算关系
属性闭包 X^+ 求候选码、判依赖 忘记反复扩展直到稳定
自反律 Y ⊆ X,则 X -> Y 基本推理 只记名字不知含义
增广律 X -> Y,则 XZ -> YZ 推导依赖 忘记两边都加
传递律 X -> YY -> Z,则 X -> Z 推理链路 条件不全乱用
选择 σ条件(R) 筛行 和投影混淆
投影 π属性(R) 取列 和选择混淆
连接 R ⋈ S 多表组合 漏连接条件
R ∪ S 结果集合并 忘结构兼容
R - S 求差集 忘结构兼容
R ÷ S 表示"对全部满足" 不会翻译语义
关系交可由差表示 R ∩ S = R - (R - S) 推理技巧 机械套用不看前提
无损连接判定 (R1∩R2)->R1(R1∩R2)->R2 分解判断 忘记只适合两关系常见判定
1NF 属性原子化 基础范式 把"可分解释"误当"不满足"
2NF 消除部分依赖 组合码场景 未先求候选码
3NF 消除传递依赖 规范化 把主属性也当非主属性处理
BCNF 决定因素必须是超码 更严格范式 误认为总是工程最优
WHERE 分组前过滤 SQL 行条件 拿它过滤聚合结果
HAVING 分组后过滤 SQL 组条件 WHERE 混用
IS NULL 判断空值 SQL 空值判断 写成 = NULL
COUNT(*) 统计总行数 聚合 COUNT(列名) 混淆
WAL 先写日志再写数据 恢复机制 只写口号不写作用
Undo/Redo 未提交撤销,已提交重做 故障恢复 容易记反

易混知识点对比表

对比项 A B 核心区别
数据库 数据集合 DBMS 一个是对象,一个是管理软件
DBMS 软件 数据库系统 数据库系统还包含应用、人员和环境
模式 结构描述 实例 一个静态蓝图,一个动态内容
实体 客观对象 联系 一个是对象本身,一个是对象间关联
候选码 最小超码 主码 主码是被选中的候选码
主属性 属于某候选码的属性 非主属性 范式判断高频区分
1NF 属性原子化 2NF 2NF 进一步消除部分依赖
2NF 消除部分依赖 3NF 3NF 进一步消除传递依赖
3NF 非主属性不传递依赖于码 BCNF BCNF 要求所有决定因素都是超码
选择 筛行 投影 一个处理元组,一个处理属性
自然连接 同名属性自动连接并去重列 等值连接 等值连接条件更显式
WHERE 分组前过滤 HAVING 分组后过滤
IN 属于某集合 EXISTS 一个偏集合成员,一个偏存在性
主键约束 保证唯一且非空 唯一约束 唯一约束在不同数据库中对空值处理不同
聚簇索引 数据按索引键组织 非聚簇索引 一个更贴近物理顺序,一个通过索引再定位数据
B+ 树索引 支持等值和范围查询 哈希索引 哈希更偏等值,不擅长范围
脏读 读到未提交数据 不可重复读 一个读到脏版本,一个同一行后来变了
不可重复读 同一行内容变化 幻读 幻读是行集合变化
Undo 撤销未提交事务 Redo 重做已提交但未完全落盘事务
复制 同一数据多份保存 分片 一个为高可用,一个为扩容分散
关系数据库 强结构、强事务 NoSQL 模型与取舍不同,不是谁取代谁

典型题型与解法表

题型 识别信号 解法步骤 最易错点
名词解释 "什么是......" 定义 -> 作用 -> 例子 / 对比 只写一句定义
模式/结构对比 "区别、联系、比较" 分别定义 -> 列差异 -> 举例 只列一边不对比
E-R 建模 "设计数据库、画图、分析业务" 找实体 -> 找联系 -> 判基数 -> 转关系 M:N 不单独建表
候选码题 给依赖集、求码 求闭包 -> 看覆盖 -> 判最小性 不检查最小性
范式题 "属于第几范式、规范化" 求码 -> 分主非主属性 -> 判依赖 跳过候选码
分解题 "消除冗余、无损连接" 分析异常 -> 依赖拆分 -> 判无损/保依赖 只分表不讲理由
关系代数题 "用关系代数表示" 先找表 -> 筛行 -> 连接 -> 投影 漏条件
SQL 单表题 "查询某些数据" SELECT + FROM + WHERE 条件写错字段
SQL 连接题 多张表联合条件 明确连接键 -> 写 JOIN ... ON ... 漏连接产生笛卡尔积
SQL 聚合题 "每个、平均、总数" GROUP BY + 聚合 + HAVING 把聚合条件写到 WHERE
SQL 子查询题 "高于平均值、存在、属于" 判断 IN/EXISTS/标量子查询 子查询返回类型不匹配
索引原理题 "为什么快、为什么 B+ 树" 先说 I/O -> 结构特点 -> 适用场景 只写"快"
并发异常题 给事务交叉执行过程 看同一数据项交互 -> 判异常类型 幻读/不可重复读混淆
隔离级别题 "哪个级别防什么" 先定义各异常 -> 对应隔离级别 只背表不懂原因
恢复题 "故障、日志、Undo/Redo" 分故障类型 -> 看提交状态 -> Undo/Redo 记反
综合设计题 业务描述长 建模 -> 表设计 -> 约束 -> 范式 -> 索引 -> 事务 只答表结构

自测题

一、基础理解题

  1. 什么是数据库、DBMS、数据库系统?三者有什么区别?
  2. 什么是三级模式结构?两级映像的作用是什么?
  3. 为什么说数据库系统比文件系统更适合共享和管理数据?
  4. 什么是模式,什么是实例?

二、建模与关系模型题

  1. 某图书馆系统包含读者、图书、借阅三个核心要素。请指出哪些是实体,哪些是联系。
  2. 什么是候选码、主码、外码?它们之间有什么关系?
  3. 什么是实体完整性、参照完整性?请各举一个例子。
  4. 对于"学生选课"这一业务,为什么通常要建立中间表而不是把课程列表放进学生表?

三、关系理论与范式题

  1. 已知关系模式 R(A, B, C, D),函数依赖集为 A -> BA -> CC -> D。求候选码,并判断最高范式。
  2. 什么是部分函数依赖?什么是传递函数依赖?请分别用一句例子说明。
  3. 为什么 3NF 能减少更新异常?
  4. 什么情况下一个关系模式属于 BCNF?

四、关系代数与 SQL 题

  1. 设有关系 Student(Sno, Sname, Dept)SC(Sno, Cno, Grade),写出"查询成绩大于 90 分学生姓名"的关系代数表达式。
  2. 用 SQL 写出"查询每个院系学生人数"的语句。
  3. 用 SQL 写出"查询没有选修任何课程的学生姓名"的语句。
  4. 说明 WHEREHAVING 的区别。

五、索引与优化题

  1. 为什么数据库索引通常使用 B+ 树而不是普通二叉搜索树?
  2. 为什么索引不是建得越多越好?
  3. 聚簇索引和非聚簇索引有什么区别?
  4. 什么是选择下推?为什么它常能提升查询效率?

六、事务与恢复题

  1. 什么是事务?为什么转账操作必须放在同一事务中?
  2. 区分脏读、不可重复读、幻读。
  3. 什么是两段锁协议?它有什么意义?
  4. 为什么系统故障后常常既要 Undo 又要 Redo?

七、综合设计题

  1. 设计一个简化的电商订单系统数据库,至少说明核心实体、主要联系、主键、外键。
  2. 若系统中"下单"操作包括扣库存、生成订单、写支付流水三步,为什么应放在同一事务中?
  3. 如果一个系统需要支持"用户只能看自己的订单",从数据库角度可以用哪些机制辅助实现?
  4. 关系数据库与 NoSQL 数据库分别更适合什么场景?

八、提高题

  1. 为什么说规范化与索引设计有时会存在工程上的张力?
  2. 请用"需求 -> 建模 -> 关系模式 -> 范式 -> 索引 -> 事务"这条链,概括数据库课程的整体逻辑。

自测题答案与解析

1. 什么是数据库、DBMS、数据库系统?三者有什么区别?

数据库是有组织、可共享的数据集合;DBMS 是管理数据库的软件系统;数据库系统是数据库、DBMS、应用程序、管理员及硬件软件环境共同组成的整体。区别在于层次不同:数据库是被管理对象,DBMS 是管理工具,数据库系统是完整运行生态。

2. 什么是三级模式结构?两级映像的作用是什么?

三级模式包括外模式、模式和内模式,分别对应用户局部视图、数据库整体逻辑结构和物理存储结构。两级映像包括外模式/模式映像和模式/内模式映像,作用是支持逻辑独立性和物理独立性,使某一层变化尽量不影响其他层。

3. 为什么说数据库系统比文件系统更适合共享和管理数据?

因为数据库系统具有更强的数据结构化、共享性、统一管理能力,并能通过约束、并发控制、恢复、安全机制解决文件系统在冗余、一致性、维护和恢复方面的不足。文件系统能存,但不擅长多用户、高并发和复杂查询场景。

4. 什么是模式,什么是实例?

模式是数据库逻辑结构的定义,是"数据库长什么样"的描述;实例是某一时刻数据库中的具体数据内容,是"此刻数据库里有什么"的状态。模式相对稳定,实例随时间变化。

5. 某图书馆系统包含读者、图书、借阅三个核心要素。请指出哪些是实体,哪些是联系。

读者和图书通常是实体,因为它们是可独立存在并可唯一标识的对象;借阅通常是联系,因为它描述了读者与图书之间的交互关系。如果借阅本身带有借阅日期、归还日期、状态等属性,则在关系模型中往往会落成一张独立的借阅表。

6. 什么是候选码、主码、外码?它们之间有什么关系?

候选码是能够唯一标识元组的最小属性组;主码是从候选码中选出的主要标识;外码是一个关系中引用另一个关系主码或候选码的属性。主码一定是候选码,外码则用于建立关系之间的联系与参照完整性。

7. 什么是实体完整性、参照完整性?请各举一个例子。

实体完整性要求主码不能为空且唯一,例如学生表中的学号不能重复也不能为 NULL。参照完整性要求外码取值必须引用被参照表中存在的主码,或在允许时为空,例如选课表中的学号必须存在于学生表中,否则就是非法引用。

8. 对于"学生选课"这一业务,为什么通常要建立中间表而不是把课程列表放进学生表?

因为学生与课程通常是多对多关系。若把课程列表塞进学生表,会破坏关系模型的一格一值原则,也不利于查询、统计和维护。建立中间表 SC(学号, 课程号, 成绩) 可以清楚表达联系,并存放联系本身的属性,如成绩、选课时间等。

9. 已知关系模式 R(A, B, C, D),函数依赖集为 A -> BA -> CC -> D。求候选码,并判断最高范式。

先求 A^+。由 A -> BA -> C,可得 A^+ = {A, B, C};再由 C -> D,得 A^+ = {A, B, C, D},覆盖全部属性,所以 A 是候选码。由于不存在组合码,也就不存在部分依赖;但有 A -> C -> D,即非主属性 D 对候选码 A 存在传递依赖,因此不满足 3NF。故最高范式为 2NF。

10. 什么是部分函数依赖?什么是传递函数依赖?请分别用一句例子说明。

部分函数依赖是指某非主属性只依赖组合候选码的一部分,例如在 SCInfo(学号, 课程号, 学生姓名, 成绩) 中,若主键是 (学号, 课程号),则 学号 -> 学生姓名 属于部分依赖。传递函数依赖是指属性不是直接依赖候选码,而是通过中间属性间接依赖,例如 课程号 -> 教师号教师号 -> 办公室,则 课程号 通过 教师号 传递决定 办公室

11. 为什么 3NF 能减少更新异常?

因为 3NF 消除了非主属性对候选码的传递依赖,使得每类事实更集中地存放在由其真正决定因素控制的关系中。这样教师办公室这类信息不会在多条课程记录中重复出现,更新时无需改多处,也不容易出现不一致。

12. 什么情况下一个关系模式属于 BCNF?

当关系模式中每一个非平凡函数依赖 X -> Y 的决定因素 X 都是超码时,该关系模式属于 BCNF。它比 3NF 更严格,要求所有"能决定别人的属性组"都必须足够强,不能只是普通属性组。

13. 设有关系 Student(Sno, Sname, Dept)SC(Sno, Cno, Grade),写出"查询成绩大于 90 分学生姓名"的关系代数表达式。

可以写为:

πSname(Student⋈Student.Sno=SC.SnoσGrade>90(SC)) \pi_{Sname}(Student \bowtie_{Student.Sno = SC.Sno} \sigma_{Grade > 90}(SC)) πSname(Student⋈Student.Sno=SC.SnoσGrade>90(SC))

思路是:先在 SC 中筛出成绩大于 90 的记录,再与 Student 按学号连接,最后投影出学生姓名。

14. 用 SQL 写出"查询每个院系学生人数"的语句。

sql 复制代码
SELECT Dept, COUNT(*) AS StudentCount
FROM Student
GROUP BY Dept;

题目里有"每个院系"和"人数",说明要按院系分组后统计每组数量。

15. 用 SQL 写出"查询没有选修任何课程的学生姓名"的语句。

sql 复制代码
SELECT s.Sname
FROM Student s
WHERE NOT EXISTS (
    SELECT 1
    FROM SC sc
    WHERE sc.Sno = s.Sno
);

思路是:找那些不存在任何匹配选课记录的学生。这里用 NOT EXISTS 很自然,也能清晰表达"没有关联记录"的语义。

16. 说明 WHEREHAVING 的区别。

WHERE 用于对分组前的原始行进行过滤,HAVING 用于对分组后的结果进行过滤。若条件里用到了聚合结果,例如平均分大于 80,就必须放在 HAVING 中,而不是 WHERE 中。

17. 为什么数据库索引通常使用 B+ 树而不是普通二叉搜索树?

因为数据库主要受磁盘 I/O 约束。普通二叉搜索树每层节点存的信息少,树高容易很高,查询时需要多次磁盘访问。B+ 树是多路平衡树,每个节点能存大量关键字和指针,树高低,更适合按页读取;同时其叶子节点链式连接,对范围查询和顺序扫描特别友好。

18. 为什么索引不是建得越多越好?

索引会占用额外存储空间,并且在插入、删除、更新时都需要维护。索引越多,写入成本越高,优化器选择计划也更复杂。若某列选择性差、查询并不高频,或者结果集本来就很大,索引收益可能有限,甚至不如全表扫描。

19. 聚簇索引和非聚簇索引有什么区别?

聚簇索引使数据记录本身按索引键顺序组织,适合范围查询与顺序访问;非聚簇索引则是独立于数据记录的辅助结构,索引项定位到数据位置,查询时可能需要额外回表。聚簇更贴近物理顺序,非聚簇更灵活。

20. 什么是选择下推?为什么它常能提升查询效率?

选择下推是指在查询优化中尽量早地执行选择操作,把满足条件的元组先筛出来,再进入连接、排序、分组等后续操作。它之所以有效,是因为越早减少输入规模,中间结果越小,后续操作的 I/O、CPU 和内存开销通常也越低。

21. 什么是事务?为什么转账操作必须放在同一事务中?

事务是数据库中不可分割的逻辑工作单元。转账包含至少两步:一个账户扣款,另一个账户加款。若只完成其中一步,就会破坏资金总量一致性。把它们放在同一事务中,可以依靠原子性保证"要么都成功,要么都失败",依靠持久性保证提交后结果稳定保存。

22. 区分脏读、不可重复读、幻读。

脏读是指一个事务读到了另一个事务尚未提交的数据;不可重复读是指同一事务两次读取同一行,读到的内容不同;幻读是指同一事务两次按同样条件查询,返回的行集合发生变化,例如多出一行或少一行。判断时要看是"未提交版本问题""同一行内容变化"还是"结果集成员变化"。

23. 什么是两段锁协议?它有什么意义?

两段锁协议规定事务分为两个阶段:第一阶段只允许加锁,不允许解锁;第二阶段只允许解锁,不允许加锁。其意义在于约束事务获取和释放锁的顺序,从而帮助保证并发调度的冲突可串行化,提高并发执行的正确性。

24. 为什么系统故障后常常既要 Undo 又要 Redo?

系统故障时,内存内容丢失,但磁盘上的数据页和日志可能处于"半新半旧"的状态。未提交事务的修改不应保留,所以要 Undo;已提交事务的修改理论上应保留,但可能尚未全部落盘,所以要 Redo。恢复的目标是让数据库回到"只保留已提交结果"的一致状态。

25. 设计一个简化的电商订单系统数据库,至少说明核心实体、主要联系、主键、外键。

一个简化设计可以包括:

  • User(用户ID, 姓名, 手机号, 地址),主键为 用户ID
  • Product(商品ID, 商品名, 单价, 库存),主键为 商品ID
  • Order(订单ID, 用户ID, 下单时间, 总金额, 状态),主键为 订单ID,其中 用户ID 是外键引用 User
  • OrderItem(订单ID, 商品ID, 数量, 成交单价),复合主键可为 (订单ID, 商品ID),分别作为外键引用 OrderProduct

其中用户与订单是一对多,订单与商品通常是多对多,因此需要 OrderItem 作为联系表。

26. 若系统中"下单"操作包括扣库存、生成订单、写支付流水三步,为什么应放在同一事务中?

因为这三步共同构成一次完整业务。若扣了库存但订单未生成,会出现库存减少却无订单记录;若订单已生成但支付流水未写入,资金与订单状态又会不一致。把三步放入同一事务,可保证要么全部完成,要么全部回滚,维护业务一致性。

27. 如果一个系统需要支持"用户只能看自己的订单",从数据库角度可以用哪些机制辅助实现?

可从多个层面配合:

  1. 通过认证识别当前用户身份
  2. 使用角色和权限控制访问能力
  3. 通过视图只暴露必要列和必要结果集
  4. 在应用层或数据库层加上按用户 ID 的过滤条件
  5. 对敏感访问行为做审计

如果是课程考试,答到"授权 + 视图 + 行级过滤思想"通常已经较完整。

28. 关系数据库与 NoSQL 数据库分别更适合什么场景?

关系数据库更适合结构相对稳定、事务一致性要求高、需要复杂查询与连接分析的场景,如订单系统、财务系统、教学管理等。NoSQL 更适合模式灵活、超大规模、高并发或特殊访问模式场景,如缓存、内容系统、日志平台、社交关系图等。关键不是谁更先进,而是谁更适合当前问题。

29. 为什么说规范化与索引设计有时会存在工程上的张力?

规范化倾向于把不同事实拆到合适的关系中,以减少冗余和异常;而高规范化往往意味着查询时需要更多连接。为了提高性能,工程上有时会引入额外索引,甚至适度反规范化。因此,设计时既要考虑理论纯净,也要考虑查询成本与系统目标,这就是二者的张力所在。

30. 请用"需求 -> 建模 -> 关系模式 -> 范式 -> 索引 -> 事务"这条链,概括数据库课程的整体逻辑。

需求决定系统要管理哪些对象与规则;建模把现实抽象成实体、属性和联系;关系模式把建模结果落成表结构;范式用函数依赖优化表结构,减少冗余与异常;索引基于查询需求改善访问性能;事务则保证多步业务操作在并发环境下尽量保持一致。整门课就是在围绕"既能正确表达数据,又能高效可靠地使用数据"逐层展开。


附录一:SQL 专项加练

训练 1:单表筛选

题目:查询 Student 表中年龄大于 20 且专业为"计算机科学"的学生姓名和学号,并按学号升序排列。

解题切入:

  • 只涉及一张表,是单表查询
  • 条件都属于原始行过滤,放 WHERE
  • 结果要排序,使用 ORDER BY

参考写法:

sql 复制代码
SELECT Sno, Sname
FROM Student
WHERE Age > 20
  AND Major = '计算机科学'
ORDER BY Sno ASC;

易错点:

  • 把排序写成 GROUP BY
  • 漏写专业过滤

训练 2:去重查询

题目:查询所有开设课程的教师编号,不重复显示。

解题切入:

  • 目标是去重后的单列结果
  • 不需要分组,只需 DISTINCT

参考写法:

sql 复制代码
SELECT DISTINCT TeacherId
FROM Course;

易错点:

  • DISTINCTGROUP BY 乱替换

训练 3:分组统计

题目:查询每门课程的选课人数。

解题切入:

  • 关键词"每门课程""人数"
  • 说明要按课程号分组,再统计每组记录数

参考写法:

sql 复制代码
SELECT Cno, COUNT(*) AS StudentCount
FROM SC
GROUP BY Cno;

易错点:

  • 误把 COUNT(Grade) 当总人数,若成绩可为空就可能偏差

训练 4:分组后过滤

题目:查询平均成绩大于 85 的课程号及其平均成绩。

解题切入:

  • 先分组,再算平均,再过滤平均值
  • 过滤聚合结果必须用 HAVING

参考写法:

sql 复制代码
SELECT Cno, AVG(Grade) AS AvgGrade
FROM SC
GROUP BY Cno
HAVING AVG(Grade) > 85;

训练 5:两表连接

题目:查询所有学生的姓名及其所在院系名称,已知 Student(DeptId) 引用 Dept(DeptId)

解题切入:

  • 两张表通过院系编号连接
  • 输出字段来自两表

参考写法:

sql 复制代码
SELECT s.Sname, d.DeptName
FROM Student s
JOIN Dept d ON s.DeptId = d.DeptId;

易错点:

  • 忘写连接条件,导致笛卡尔积

训练 6:三表连接

题目:查询选修了"数据库原理"课程的学生姓名和成绩。

解题切入:

  • 课程名称在 Course
  • 成绩在 SC
  • 学生姓名在 Student
  • 需要三表连接

参考写法:

sql 复制代码
SELECT s.Sname, sc.Grade
FROM Student s
JOIN SC sc ON s.Sno = sc.Sno
JOIN Course c ON sc.Cno = c.Cno
WHERE c.Cname = '数据库原理';

训练 7:存在性查询

题目:查询至少选修过一门课程的学生姓名。

解题切入:

  • 关键语义是"至少存在一条关联记录"
  • 可优先考虑 EXISTS

参考写法:

sql 复制代码
SELECT s.Sname
FROM Student s
WHERE EXISTS (
    SELECT 1
    FROM SC sc
    WHERE sc.Sno = s.Sno
);

训练 8:不存在性查询

题目:查询没有选修任何课程的学生姓名。

解题切入:

  • 与上一题相反,直接用 NOT EXISTS

参考写法:

sql 复制代码
SELECT s.Sname
FROM Student s
WHERE NOT EXISTS (
    SELECT 1
    FROM SC sc
    WHERE sc.Sno = s.Sno
);

训练 9:标量子查询

题目:查询成绩高于全体平均成绩的选课记录。

解题切入:

  • 平均成绩是单个值
  • 适合标量子查询

参考写法:

sql 复制代码
SELECT *
FROM SC
WHERE Grade > (
    SELECT AVG(Grade)
    FROM SC
);

训练 10:综合分组连接题

题目:查询每个院系平均成绩大于 80 分的院系名称和平均成绩。

解题切入:

  • 成绩在选课表
  • 院系在学生表
  • 需要连接后按院系分组

参考写法:

sql 复制代码
SELECT s.Dept, AVG(sc.Grade) AS AvgGrade
FROM Student s
JOIN SC sc ON s.Sno = sc.Sno
GROUP BY s.Dept
HAVING AVG(sc.Grade) > 80;

训练总结:

  • 单表题先锁定 FROM
  • 多表题先锁定连接键
  • 分组题先找"每个""平均""总数"
  • 子查询题先问自己:结果是一值、一列,还是只判断是否存在

附录二:范式与数据库设计专项加练

训练 1:识别部分依赖

题目:关系模式 R(学号, 课程号, 学生姓名, 成绩),主键为 (学号, 课程号)。若存在依赖 学号 -> 学生姓名,请判断是否满足 2NF。

分析:

不满足 2NF。因为非主属性 学生姓名 只依赖组合键 (学号, 课程号) 的一部分 学号,构成部分函数依赖。应将学生信息拆出为独立关系。

训练 2:识别传递依赖

题目:关系模式 Course(课程号, 课程名, 教师号, 教师办公室),已知 课程号 -> 教师号教师号 -> 教师办公室。请判断是否满足 3NF。

分析:

不满足 3NF。因为非主属性 教师办公室 通过 教师号 对候选码 课程号 构成传递依赖。应将教师信息独立为 Teacher(教师号, 教师办公室)

训练 3:候选码判断

题目:R(A, B, C),依赖集为 A -> BB -> C。求候选码。

分析:

先求 A^+,由 A -> BB -> CA^+ = {A, B, C},覆盖全部属性,所以 A 是候选码。B^+ = {B, C} 不能决定 A,不是候选码。

训练 4:无损连接直觉

题目:将关系 R(A, B, C) 分解为 R1(A, B)R2(B, C),且已知 B -> C。说明该分解为什么常可认为无损。

分析:

因为交集为 B,而 B -> C,即 (R1 ∩ R2) -> R2,符合常见的两关系无损连接判定条件。因此连接回去通常不会生成伪元组。

训练 5:综合设计

题目:某宿舍管理系统需要记录学生信息、宿舍信息和入住信息。一个宿舍可以住多个学生,一个学生在某一时期只住一个宿舍。请给出合理的关系设计思路。

分析思路:

  • Student(学号, 姓名, 专业, 年级)
  • Dorm(宿舍号, 楼栋, 房间类型, 容量)
  • Stay(学号, 宿舍号, 入住日期, 退宿日期)

为什么不把宿舍号直接写在学生表里?

  • 如果要记录历史入住变更,就需要联系表携带时间属性
  • 入住本质上是学生和宿舍之间的一种关系,并且关系本身带属性

训练 6:订单系统设计

题目:设计一个订单系统,包含用户、商品、订单、订单明细,并简述为什么订单明细要单独成表。

分析:

订单和商品通常是多对多关系:一张订单可包含多个商品,一个商品可出现在多张订单里。订单明细既承担联系作用,又记录数量、成交价等联系属性,所以必须独立成表。

训练 7:课程安排设计

题目:某课程由一个教师授课,一个教师可授多门课程。请说明 1:N 联系如何落地到关系模式。

分析:

对于 1:N 联系,通常把 1 方主键作为外键放到 N 方。即在 Course 表中放入 TeacherId 外键引用 Teacher(TeacherId),而不是另建中间表。除非联系本身还有独立属性。

训练 8:综合范式判断模板

题目:给出任意关系模式及依赖集,说明规范化题的一般步骤。

模板答案:

  1. 写出属性全集和依赖集
  2. 求候选码
  3. 判主属性和非主属性
  4. 检查是否有部分依赖
  5. 检查是否有传递依赖
  6. 若考 BCNF,再检查决定因素是否为超码
  7. 若需分解,说明每步分解消除的问题

训练总结:

  • 范式题不是先背定义,而是先抓依赖结构
  • 设计题不是先列字段,而是先看事实由谁决定
  • 一旦出现联系属性,就要警惕"联系表是否应该独立"

附录三:事务、恢复与索引专题加练

训练 1:并发异常识别

场景:事务 T1 把某商品库存从 20 改为 15,但尚未提交;事务 T2 读取到库存 15,并据此判断可以下单。随后 T1 回滚,库存恢复为 20。

分析:

若 T2 读取的是 T1 未提交的数据,这就是脏读。关键识别点不是"读到的数据后来变了",而是"读的时候对方根本没提交"。

训练 2:不可重复读识别

场景:T1 先读取学生张三成绩为 80;T2 提交后把张三成绩更新为 90;T1 再次读取张三成绩得到 90。

分析:

这是不可重复读。因为同一事务 T1 两次读取同一行,读到内容不同。注意这里不是行集合变化,而是同一行值变化。

训练 3:幻读识别

场景:T1 查询"成绩大于 90 的学生人数"为 10;T2 插入一条成绩为 95 的新记录并提交;T1 再次执行同样查询,人数变为 11。

分析:

这是幻读。因为变化的不是某一行已有内容,而是满足条件的记录集合发生了变化。

训练 4:锁兼容分析

题目:为什么两个共享锁通常可以并存,但共享锁与排他锁不兼容?

分析:

共享锁的目标是允许多个事务同时读取,不破坏彼此正确性;而排他锁意味着事务要独占读写对象,若和共享锁同时存在,就可能出现"一边改一边读"的冲突。因此 S 与 S 可兼容,S 与 X 不兼容,X 与 X 也不兼容。

训练 5:两段锁协议应用

题目:说明为什么两段锁协议有助于保证调度正确性。

分析:

两段锁协议限制事务必须先集中加锁,再统一解锁,使事务对数据项的控制边界更清晰,避免"边释放边申请"带来的复杂交错,从而支持冲突可串行化。答题时不要只写定义,要写"它限制了锁的顺序,因此提高了并发调度正确性"。

训练 6:WAL 原理说明

题目:为什么必须先写日志,再写数据页?

分析:

因为系统故障时,恢复必须依赖日志判断哪些事务该撤销、哪些该重做。如果数据先写而日志没写,故障后系统就可能面对"磁盘变了,但不知道为什么变"的局面,恢复将失去依据。

训练 7:Undo / Redo 场景判断

场景:事务 T1 已提交,但部分数据页尚未写盘;事务 T2 尚未提交,其修改已进入缓冲区。此时系统崩溃。

分析:

  • T1 已提交,应被保留,若未完全落盘则需要 Redo
  • T2 未提交,不应保留,需要 Undo

这正是"系统故障后同时需要 Undo 和 Redo"的经典场景。

训练 8:检查点意义

题目:为什么有了日志还需要检查点?

分析:

如果每次恢复都从日志开头重放,成本会非常高。检查点记录了某个相对安全的恢复位置,使恢复可以从较近的位置开始分析与回放,从而显著缩短恢复时间。检查点不是替代日志,而是帮助日志恢复更高效。

训练 9:B+ 树与哈希索引比较

题目:为什么范围查询通常更倾向 B+ 树而不是哈希索引?

分析:

哈希索引按哈希值散列分布,适合等值查询,但键之间天然不保序,因此不利于区间遍历。B+ 树叶子节点按关键字顺序组织并可顺链扫描,因此特别适合范围查询、排序和前缀匹配。

训练 10:组合索引直觉

题目:为什么组合索引 (A, B, C) 往往更容易支持基于 AA+B 的查询,而不容易直接支持只基于 B 的查询?

分析:

因为组合索引内部的排序首先由 A 决定,再在 A 相同的范围内考虑 B,最后才是 C。所以只有从最左前缀开始利用,索引结构的有序性才真正能帮助快速定位。

训练 11:索引失效思维

题目:对索引列做函数运算为什么常不利于索引使用?

分析:

索引组织的是原始列值的有序结构。若查询条件写成 YEAR(create_time) = 2026 之类的形式,数据库可能无法直接利用原始索引顺序定位范围,而需要对每条记录先计算函数结果,导致索引优势下降。

训练 12:综合原理题模板

题目:为什么数据库课程里"索引、事务、恢复"会被视为三大关键原理板块?

分析模板:

  • 索引解决访问效率问题
  • 事务解决多步操作的一致性问题
  • 恢复解决故障后的可靠性问题

三者分别对应性能、正确性、可靠性,是数据库从"能存数据"升级到"能稳定服务业务"的核心支柱。

附录总结:

如果你能把这三组专题训练真正做熟,那么数据库试卷里最能拉开差距的"SQL + 范式 + 事务恢复"三大板块,你就已经建立了稳定的解题骨架。


最后总结

数据库这门课最容易给人的错觉,是它好像什么都讲一点,所以很散。真正把它学透以后你会发现,它其实一直在回答同一个问题:

数据怎样才能被长期、正确、高效、共享地使用。

围绕这个问题,整门课自然展开出一条清晰因果链:

  1. 先用建模回答"现实世界怎么抽象"
  2. 再用关系模型回答"抽象结果怎么变成表"
  3. 再用 SQL 和关系代数回答"这些表怎么查、怎么改"
  4. 再用范式回答"这些表怎样设计才不乱"
  5. 再用索引和优化回答"怎样查得快"
  6. 再用事务与并发控制回答"多人同时操作时怎样尽量不乱"
  7. 再用日志与恢复回答"出故障时怎样救回来"
  8. 最后用安全、授权、分布式与 NoSQL 回答"系统真正上线后如何控制和扩展"

如果你读完这篇文档,能真正记住下面五句话,这门课的主干就已经抓住了:

  1. 数据库不是在背表和语法,而是在研究数据如何被可靠管理。
  2. 关系设计的核心不是"拆几张表",而是"谁决定谁"。
  3. SQL 的核心不是关键字,而是查询逻辑。
  4. 索引、事务、恢复分别对应性能、正确性、可靠性三大支柱。
  5. 高分的关键不是记得多,而是能按因果顺序把答案讲完整。

最后给你一个真正实用的复习标准:

  • 如果你能看着一道题,先判断它属于哪个模块、哪个题型、该套哪个模板,再开始作答,说明你已经从"零碎记忆"进入"结构化掌握"了。
  • 如果你还能把"为什么"写出来,而不是只写"是什么",那你不仅能应付考试,也已经具备了数据库学习最重要的底层理解。

把这篇文档至少完整过两遍:第一遍建框架,第二遍抓模板和易错点,第三遍结合题目输出。做到这里,数据库这门课从"看着很多"变成"其实有主线",从"背不住"变成"能组织",从"会一点"变成"能拿分",就是很自然的结果。

相关推荐
知识分享小能手2 小时前
MongoDB入门学习教程,从入门到精通,MongoDB备份完全指南(23)
数据库·学习·mongodb
VelinX2 小时前
【个人学习||vue】
前端·vue.js·学习
一个天蝎座 白勺 程序猿2 小时前
AI入门系列:AI入门者的困惑:常见术语解释与误区澄清
人工智能·学习·ai
不想学习\??!2 小时前
USB-HID学习笔记
笔记·学习
可可西里_X_back2 小时前
linux学习(一)- 环境安装
学习
LXXgalaxy2 小时前
Vue3 列表数据流:从赋值入门到进阶(独立学习版)
javascript·vue.js·学习
码农的小菜园3 小时前
提示工程学习笔记(一)
笔记·学习
四谎真好看3 小时前
Redis学习笔记(高级篇3)
redis·笔记·学习·学习笔记
十三画者3 小时前
【文献分享】TREE通过基于 Transformer 的图表示技术,在生物网络中对癌症基因进行可解释的识别学习
网络·学习·transformer