《计算机数据库》满分掌握指南
默认假设:本文按中国高校中最常见的《数据库系统概论 / 数据库原理 / 数据库系统》课程体系写作,面向基础一般、希望先建立整体框架,再把考试成绩稳定提升到 80 分以上的学习者。全篇以关系数据库 为主线,适度补充 MySQL 工程直觉 与 NoSQL 入门对比,重点覆盖课程考试最核心、最常考、最能提分的内容。
阅读方式建议:第一次通读,先抓"数据库到底解决什么问题、各模块因果关系是什么";第二次再盯公式、定义、题型和易错点;第三次结合刷题与背诵,把理解转成得分。
学习前说明
很多同学学数据库,会在两个地方同时卡住。
第一,看起来什么都在讲,但不知道主线是什么。教材里既有概念模型、关系代数、SQL、范式、索引、事务、恢复、安全、分布式,又有一堆定义、符号、规则,结果学到后面只记住了零碎术语,脑中没有整体图景。
第二,会写一点 SQL,但不会解释数据库为什么这样设计。一遇到考试里的"为什么要规范化""为什么会产生幻读""为什么 B+ 树适合做索引""为什么日志能恢复""为什么视图能增强安全性",就容易答不完整。
这份文档不按教材章节死背,而是按"问题从哪里来,系统为什么这样设计,设计又带来了什么后果"的因果顺序来讲。因为数据库不是一堆互相孤立的知识点,它本质上是在解决四个持续出现的问题:
- 数据怎么描述,才能让人和机器都能理解。
- 数据怎么存和怎么查,才能又正确又高效。
- 很多人同时读写时,怎么尽量不乱。
- 系统出故障时,怎么尽量恢复。
如果你把这四个问题抓住,数据库这门课的大部分内容都会自动归位。
学科一句话概括
计算机数据库,就是研究"如何用一种可组织、可查询、可共享、可恢复、可约束的方式管理大量数据"的学科。
再说得更直白一点:
- 文件能存数据,但不擅长高效查询、多人共享和一致性控制。
- 数据库不只是"存起来",而是要同时解决建模、查询、约束、并发、恢复、优化这六件事。
- 所以数据库系统不是单纯的数据仓库,而是一套关于"数据如何被可靠使用"的工程与理论体系。
学科定位
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:综合设计与工程落地 | ★★★★ | 如何把前面知识串成一套完整系统思维 | 最能体现"真正学会" | 综合题、项目题 |
模块前后依赖关系
可以把整门课理解成下面这条链:
- 先搞清 数据库系统到底是什么。
- 再学会 如何建模现实世界。
- 然后理解 查询本质是什么。
- 接着掌握 SQL 如何表达查询和更新。
- 有了表之后,会发现 冗余和异常问题,于是需要范式和设计方法。
- 表一旦大起来,就要解决 存得下、查得快,所以需要索引和存储结构。
- 查询越来越复杂时,还要考虑 系统怎样选更优执行路径。
- 多人同时改数据时,会出现 一致性问题,于是需要事务与并发控制。
- 机器一旦崩溃,又要考虑 恢复机制。
- 系统要真正上线,还离不开 完整性、安全性和权限控制。
- 当单机顶不住时,再进入 分布式与 NoSQL 的世界。
如果你把这条链看懂了,数据库不再是"十几个彼此无关的章节",而是一套连续演化的问题解决过程。
推荐学习顺序
顺序一:框架优先
如果你现在几乎是零基础,建议按下面顺序走:
- 先看"数据库系统基础"
- 再看"E-R 模型与关系模型"
- 再看"SQL 核心查询"
- 接着看"范式与设计"
- 再看"索引与存储"
- 然后学"事务与并发"
- 最后补"恢复、安全、分布式"
这个顺序的原因很简单:
- 如果你先学 SQL 而不懂模型,就会把数据库当作"写语法题"。
- 如果你先学范式而不懂函数依赖,就会变成死背定义。
- 如果你先学事务而不懂更新和共享,就很难体会为什么会冲突。
- 如果你先学恢复而不懂事务,就很难理解日志到底在保护什么。
顺序二:考试优先
如果离考试已经不远,要快速拿分,建议优先级如下:
- 关系模型 + 码与约束 + 函数依赖 + 规范化
- SQL 查询、连接、嵌套、分组、视图
- 事务 ACID、并发问题、隔离级别、封锁协议
- 索引、B+ 树、聚簇与非聚簇
- 恢复、日志、检查点
- 完整性、安全性、授权
- 分布式与 NoSQL 作为加分项
顺序三:工程理解优先
如果你除了考试,还想把数据库真正用起来,可以这样走:
- 数据库基础
- SQL
- 表设计与范式
- 索引与执行计划
- 事务与锁
- 恢复与日志
- 复制与扩展
- NoSQL 与架构比较
核心模块详解,适合小白看懂的比喻
下面的每个核心模块,都统一按以下顺序展开:
- 本模块解决什么问题
- 核心概念、术语、符号
- 用通俗比喻或苏格拉底问答解释"为什么"
- 关键规则、定理、方法与适用条件
- 易混点与常见误区
- 考试常见问法
- 典型例题
- 解题思路
- 简短总结
为了方便后续扩写,先列出模块标题:
模块 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 数据库系统的主要特点
教材常写四条,你要会背,更要会解释:
- 数据结构化
- 数据共享性高,冗余度低,易扩充
- 数据独立性高
- 数据由 DBMS 统一管理和控制
为什么结构化重要?
因为只有当数据结构被统一定义,系统才能做约束检查、查询优化、并发控制和恢复。
为什么共享性高会带来新问题?
因为一旦共享,多个用户会同时读写,数据库就必须处理并发控制和安全问题。这说明数据库的很多复杂机制,其实是"共享"带来的副作用。
4.3 DBMS 的主要功能
DBMS 不只是"能执行 SQL",它至少要承担:
- 数据定义功能:定义模式、表、约束、视图
- 数据操纵功能:查询、插入、删除、更新
- 数据库运行管理:并发控制、恢复、安全性检查
- 数据组织、存储和管理:文件、页、索引、缓冲
- 数据库建立与维护:备份、重构、性能监控
你会发现,后面整门课其实就是在一条条拆解这些功能。
5. 易混点与常见误区
误区 1:把数据库等同于某个软件
错因:把工具当对象。
纠正:
- 数据库是数据集合
- DBMS 是管理数据库的软件
- 数据库系统是更大的整体
误区 2:把模式和实例混淆
错因:以为"学生表"本身就是数据。
纠正:
- 模式描述结构,例如"学生表有学号、姓名、年龄"
- 实例描述当前内容,例如"2026 年 4 月 14 日这张表里有 3000 条学生记录"
误区 3:把外模式理解成前端页面
外模式不是网页,而是用户看到的数据逻辑视图。一个报表、一个应用程序需要的数据子集,都可以看作外模式。
误区 4:两级映像不是多余概念
很多人觉得"映像"只是背诵项。其实它解决的是变化如何隔离的问题,没有映像就没有数据独立性。
6. 考试常见问法
- 什么是数据库、DBMS、数据库系统?三者有何联系与区别?
- 数据库系统有哪些主要特点?
- 什么是三级模式结构?什么是两级映像?
- 什么是数据独立性?逻辑独立性和物理独立性有何区别?
- 文件系统与数据库系统相比有哪些优缺点?
- 模式与实例的区别是什么?
7. 典型例题
例题 1:简述数据库系统与文件系统相比的优势。
参考分析:
文件系统也能存数据,为什么还需要数据库系统?因为当数据规模变大、使用者增多、查询条件复杂时,文件系统很容易出现冗余、共享差、一致性差、难维护、难恢复的问题。数据库系统通过模式定义、统一管理、并发控制、恢复机制、约束机制,提高了数据组织性和可管理性。
标准答题点:
- 数据结构化程度高
- 共享性高,冗余低
- 数据独立性高
- 统一控制数据的安全性、完整性、并发和恢复
例题 2:说明三级模式结构如何支持数据独立性。
答案思路:
- 说明三级模式分别对应用户层、逻辑层、物理层
- 说明外模式/模式映像支持逻辑独立性
- 说明模式/内模式映像支持物理独立性
- 强调"隔离变化"的核心作用
8. 解题思路
本模块的题目,通常不难算,但容易答空。标准写法建议按下面模板:
- 先下定义
- 再说作用
- 最后说区别或联系
例如答"数据独立性"时,不要只写一句"应用与数据分离",而要写:
- 什么是数据独立性
- 为什么需要它
- 分为哪两类
- 分别靠什么机制实现
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 关系的三个基本性质
教材常考关系的性质。要会背,也要会解释。
- 每个属性值必须是原子的、不可再分
- 行顺序通常无关
- 列顺序通常无关
- 元组不能完全重复
为什么"一格一值"如此重要?
因为数据库的查询、连接、比较、排序、索引都默认一个格子里放一个可比较的值。如果一个格子里塞"张三,李四,王五",那关系运算和 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 码的判断方法
考试常让你判断候选码、主码、外码。思路是:
- 先看什么属性或属性组能唯一标识一行
- 再看是否"最小"
- 能唯一且最小的,就是候选码
- 从候选码中选一个做主码
要注意:超码不一定是候选码。因为超码可能还包含多余属性。
5. 易混点与常见误区
误区 1:实体和关系混淆
"学生"是实体,"学生选课"是联系。很多人会把"选课"误认为实体。判断标准是:它是否独立存在,还是描述对象之间的关系。
误区 2:关系模型里的"关系"不是"联系"
这是一个经典语言陷阱。
- E-R 模型中的"联系"指实体之间的关联
- 关系模型中的"关系"指一张二维表所表示的数据集合
误区 3:候选码与主码混淆
候选码可能有多个,主码通常只选一个。主码是"被选中担当主标识"的候选码。
误区 4:外键不是"另一张表任意字段"
外键是指向其他关系主码(或候选码)的属性,不是随便引用一个字段就算外键。
误区 5:多对多关系不拆表
这是数据库设计里最常见的初学者错误之一。
6. 考试常见问法
- 什么是数据模型?概念模型、逻辑模型、物理模型有什么区别?
- 简述 E-R 模型的基本组成。
- 如何把 E-R 图转换为关系模式?
- 什么是主码、候选码、外码?
- 什么是实体完整性、参照完整性?
- 为什么关系模型成为主流?
- 画出给定业务场景的 E-R 图,并转换为关系模式。
7. 典型例题
例题:某教学系统包含如下语义。
- 一个学生有学号、姓名、专业
- 一门课程有课程号、课程名、学分
- 一个学生可选多门课程,一门课程可被多个学生选修
- 选修关系包含成绩属性
要求:
- 画出 E-R 模型要点
- 转换为关系模式
- 指出主键和外键
解答:
概念层:
- 实体
Student - 实体
Course - 联系
Select
联系类型:
Student与Course是 M:NSelect带有属性成绩
关系模式:
text
Student(学号, 姓名, 专业)
Course(课程号, 课程名, 学分)
SC(学号, 课程号, 成绩)
键:
Student主键:学号Course主键:课程号SC主键:(学号, 课程号)SC.学号外键引用Student(学号)SC.课程号外键引用Course(课程号)
8. 解题思路
E-R 设计题可按固定模板做:
- 找对象:哪些是实体
- 找描述:实体有什么属性
- 找关系:实体之间如何关联
- 判断基数:1:1、1:N、M:N
- 看联系是否带属性
- 转表:实体成表,1:N 放外键,M:N 单独成表
- 标主键、外键、必要约束
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. 考试常见问法
- 用关系代数表示给定查询。
- 说明选择、投影、连接、除法的意义。
- 用关系代数写出多表连接查询。
- 比较关系代数与关系演算。
- 给定关系模式,完成"查询所有满足某条件对象"的关系代数表达。
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. 解题思路
关系代数题建议按下面模板:
- 明确最终要输出什么列
- 明确需要哪些表
- 先做条件过滤,再做连接,最后投影
- 涉及"全部都满足"时考虑除法
- 涉及集合比较时考虑并、差、交
一个很实用的口诀是:
先找表,再筛行,再连表,后取列。
9. 简短总结
关系代数和关系演算并不是为了折磨初学者,而是在帮你看透:查询不是在背 SQL 关键字,而是在做集合与逻辑运算。
只要你脑子里先有"筛行、取列、连接、覆盖全部"的骨架,SQL 再复杂也不容易乱。
模块 4:SQL 核心操作体系
1. 本模块解决的问题
如果说关系代数告诉你"查询本质是什么",那 SQL 解决的就是"如何用工业界真正使用的语言来表达这些操作"。
SQL 是数据库课程里最容易得分、也最容易失分的部分。容易得分,是因为它题型稳定;容易失分,是因为:
- 语法细节多
- 执行顺序容易混
NULL语义特殊- 连接、分组、子查询经常混在一起
这一模块的目标,不只是让你会写 SQL,而是让你明白:每一条 SQL 都在表达一个清晰的数据需求。
2. 核心概念、术语与分类
| 分类 | 全称 | 主要作用 | 常见命令 |
|---|---|---|---|
| DDL | Data Definition Language | 定义数据库对象 | CREATE、ALTER、DROP |
| DML | Data Manipulation Language | 修改数据 | INSERT、UPDATE、DELETE |
| DQL | Data Query Language | 查询数据 | SELECT |
| DCL | Data Control Language | 权限控制 | GRANT、REVOKE |
| TCL | Transaction Control Language | 事务控制 | COMMIT、ROLLBACK、SAVEPOINT |
SQL 课程考试中,最核心的是:
- 单表查询
- 多表连接
- 分组聚合
- 嵌套子查询
- 视图
- 更新与删除
- 授权与事务基础
3. 先讲用途,再讲细节
3.1 SQL 为什么比关系代数更适合人用
关系代数很严谨,但不够贴近人类表达。SQL 更像你在和系统说:
- 从哪些表里找
- 满足什么条件
- 最后展示哪些字段
- 要不要排序
- 要不要分组统计
所以 SQL 的优势在于"可表达复杂查询,同时相对可读"。
3.2 SQL 的逻辑执行顺序
很多初学者看 SQL 文本顺序是:
sql
SELECT ...
FROM ...
WHERE ...
GROUP BY ...
HAVING ...
ORDER BY ...
但数据库在逻辑上更接近这样理解:
FROMJOINWHEREGROUP BYHAVINGSELECTDISTINCTORDER BYLIMIT(如果有)
为什么要知道这个顺序?
因为它直接解释很多常见错误。
例如:
- 为什么
WHERE里不能直接用聚合结果?因为聚合还没发生。 - 为什么
HAVING可以过滤分组结果?因为它发生在分组之后。 - 为什么别名有时不能在
WHERE用?因为SELECT比WHERE更晚。
3.3 苏格拉底式提问:为什么先 FROM 再 SELECT
问: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 聚合与分组
常见聚合函数:
COUNTSUMAVGMAXMIN
例子:统计每个院系人数。
sql
SELECT Dept, COUNT(*) AS 人数
FROM Student
GROUP BY Dept;
为什么必须 GROUP BY Dept?
因为你一边想看"每个院系",一边想做"人数统计"。那就必须先按院系分组,再对每组统计。
4.5 WHERE 与 HAVING 的区别
例子:统计平均成绩大于 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
);
子查询常见三类:
- 标量子查询:返回单个值
- 列子查询:返回一列值
- 存在型子查询:用
EXISTS判断是否存在
4.8 IN、EXISTS、ANY、ALL
这几个很容易混。
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:把 WHERE 和 HAVING 当同义词
不是。
WHERE过滤原始行HAVING过滤分组结果
误区 2:忘记连接条件,造成笛卡尔积
这是 SQL 题的大坑。多表查询一旦漏了连接条件,结果会爆炸式增长。
误区 3:COUNT(*)、COUNT(列名)、COUNT(DISTINCT 列名) 混淆
COUNT(*)统计行数,包含空值行COUNT(列名)只统计该列非空值数量COUNT(DISTINCT 列名)统计该列非空且去重后的数量
误区 4:分组后 SELECT 列乱写
一般规则是:分组查询中,SELECT 里出现的非聚合列,必须出现在 GROUP BY 中。
误区 5:NOT IN 和 NULL 组合导致结果异常
如果子查询结果中含 NULL,NOT IN 常会带来意外结果。考试里通常不会挖太深,但要知道这类陷阱。
误区 6:把视图当成独立存储数据的表
普通视图通常不存实际数据,存的是定义;它是逻辑层对象,不是物理复制。
6. 考试常见问法
- 写出完成指定查询要求的 SQL 语句。
- 比较
WHERE与HAVING的区别。 - 说明内连接、外连接的差异。
- 解释视图的作用及优点。
- 使用子查询完成"高于平均值""属于某集合""存在某条件"等题目。
- 设计带主键、外键、约束的建表语句。
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 题可以按固定步骤拆:
- 明确目标列:题目最后要显示什么
- 确定来源表:需要哪些表
- 判断关系:表之间如何连接
- 加条件:是原始行条件还是聚合结果条件
- 看是否需要分组、排序、去重
- 若一句不好写,考虑子查询
一个非常实用的自检问题是:
"我现在筛的是原始记录,还是分组后的结果?"
这能帮你在 WHERE 和 HAVING 之间做出正确选择。
9. 简短总结
SQL 真正难的地方,不是背命令,而是把需求拆成:
- 数据从哪来
- 如何关联
- 先筛什么
- 后统计什么
- 最终展示什么
只要你始终按"来源表 → 条件 → 分组 → 输出"的顺序思考,SQL 就会从语法题变成逻辑题。
模块 5:关系规范化与数据库设计
1. 本模块解决的问题
前面你已经知道如何把现实世界抽象成表,但新的问题立刻出现了:
- 表是不是只要能装下数据就算设计好了?
- 为什么有的表一改就牵一大片?
- 为什么有的表插入、删除时会出现"顺带丢信息"的怪现象?
- 为什么教材要反复讲函数依赖和 1NF、2NF、3NF、BCNF?
这一模块解决的,本质上是:如何设计"结构好、冗余少、异常少、维护成本低"的关系模式。
如果说 E-R 模型回答"应该有哪些表",那么规范化回答的就是"这些表该拆到什么程度才合理"。
2. 核心概念及术语
2.1 什么是数据冗余与更新异常
| 概念 | 含义 | 典型表现 |
|---|---|---|
| 数据冗余 | 同一事实被重复存储 | 教师姓名在多行重复出现 |
| 插入异常 | 因结构不合理导致不能正常插入 | 还没有学生选课,就无法录入课程信息 |
| 删除异常 | 删除一条记录导致无关信息丢失 | 删除最后一个选课学生,课程信息也没了 |
| 更新异常 | 一处事实需多处更新,易不一致 | 教师换办公室,很多行都要改 |
2.2 函数依赖相关术语
设关系模式为 R(U),X、Y 为属性集。
若在任意合法关系实例中,只要两个元组在 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 -> Z 且 Y 不是候选码时,X 间接决定 Z |
3NF 关注重点 |
2.3 码与闭包
| 术语 | 含义 |
|---|---|
| 超码 | 能唯一标识元组的属性组 |
| 候选码 | 最小超码 |
| 主码 | 被选作主标识的候选码 |
属性闭包 X^+ |
在函数依赖集 F 下,由 X 能推出的全部属性集合 |
闭包是考试里判断候选码、分解是否合理的高频工具。
3. 先讲直觉,再讲规范化
3.1 为什么"能用"不等于"设计好"
考虑一张大表:
text
选课信息(学号, 学生姓名, 专业, 课程号, 课程名, 教师号, 教师姓名, 教师办公室, 成绩)
这张表看起来什么都能查,但它的问题很严重:
- 学生姓名、专业会随着选课记录重复出现
- 课程名、教师姓名、办公室也会重复出现
- 教师办公室一变,所有相关行都要改
- 如果一门新课刚创建、还没人选,就没地方存
- 如果某门课最后一个学生退课,那课程和教师信息可能跟着丢
所以问题不在于"查不查得到",而在于"数据事实被混杂在了一张表里"。
3.2 苏格拉底式追问:为什么拆表能减少异常
问:为什么一张大表会冗余?
答:因为不同层级的事实被放在同一张表中。
问:什么叫不同层级的事实?
答:例如:
- 学生事实:学号决定学生姓名、专业
- 课程事实:课程号决定课程名、教师号
- 教师事实:教师号决定教师姓名、办公室
- 选课事实:
(学号, 课程号)决定成绩
问:这些事实为什么不能都混放?
答:因为它们的决定因素不同。把由不同决定因素控制的信息放一张表里,就会导致重复与异常。
问:那拆表的依据是什么?
答:就看"谁决定谁",也就是函数依赖。
这就是规范化的根:按照依赖结构拆表。
4. 关键规则、定理、方法
4.1 Armstrong 公理
函数依赖推理的经典规则:
- 自反律 :若
Y \subseteq X,则X -> Y - 增广律 :若
X -> Y,则XZ -> YZ - 传递律 :若
X -> Y且Y -> Z,则X -> Z
常用推论:
- 合并律
- 分解律
- 伪传递律
这些规则不是为了炫技,而是为了帮助你:
- 求闭包
- 判断候选码
- 判断某分解是否保留依赖
4.2 如何求属性闭包
求 X^+ 的步骤:
- 初始把
X放进闭包 - 扫描依赖集中所有
A -> B - 若
A已包含在闭包中,则把B加入闭包 - 重复直到闭包不再扩大
闭包的最常见用途:
- 如果
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 -> Y,X 都必须是超码。
BCNF 比 3NF 更严格。
为什么还需要 BCNF?
因为有些关系虽然满足 3NF,但仍可能因某些"主属性之间的依赖"存在异常。BCNF 进一步要求:凡是能决定别人的,自己必须足够强,至少得是超码。
4.7 规范化不是越高越好
这是特别重要的工程直觉。
教材从理论上常追求更高范式,但现实系统中并不总是越分越细越好。因为:
- 拆得越细,连接成本可能越高
- 读多写少场景下,有时会适度反规范化
- 报表系统常为了查询效率做冗余设计
所以你要明白:
- 考试里看理论最优
- 工程里看成本平衡
4.8 无损连接分解与保持依赖
这是考试综合题常考的两个标准。
无损连接分解:
把一个关系分成多个关系后,再连接回来,不应丢信息,也不应凭空产生假元组。
保持依赖:
原关系上的函数依赖,最好能在分解后的各子关系上继续检查和维持,而不需要每次重新连接全部表。
一个常见判断规则:
将 R 分解为 R1 和 R2,若 (R1 ∩ R2) -> R1 或 (R1 ∩ R2) -> R2,则该分解通常是无损连接的。
5. 易混点与常见误区
误区 1:把"字段重复出现"直接等同于不规范
不是。重复现象只是症状,根因是依赖结构不合理。
误区 2:2NF 与 3NF 分不清
一个抓手:
- 2NF 解决"依赖主键的一部分"
- 3NF 解决"依赖一个非主属性再间接依赖主键"
误区 3:主属性、非主属性概念模糊
主属性是"属于某个候选码的属性",不是"主键中的属性才算"。如果一个属性出现在任何候选码里,它就是主属性。
误区 4:BCNF 一定优于 3NF
理论上更纯净,但工程上不一定更合适。考试可写"BCNF 更严格",不要轻易写"总是更好"。
误区 5:规范化只靠背定义
真正做题时,关键是先找函数依赖,再判断候选码,再看异常来源。
6. 考试常见问法
- 什么是函数依赖?什么是完全函数依赖、部分函数依赖、传递函数依赖?
- 如何求某属性集的闭包?
- 如何判断候选码?
- 说明 1NF、2NF、3NF、BCNF 的含义。
- 给定关系模式和依赖集,判断其属于第几范式。
- 对关系模式进行规范化分解,并说明理由。
- 判断分解是否无损连接、是否保持依赖。
7. 典型例题
例题:设有关系模式
text
R(学号, 课程号, 学生姓名, 课程名, 教师号, 教师姓名, 成绩)
已知函数依赖:
学号 -> 学生姓名课程号 -> 课程名, 教师号教师号 -> 教师姓名(学号, 课程号) -> 成绩
求:
- 候选码
- 最高范式
- 合理分解
解题:
第一步,判断候选码。
显然仅有 学号 不够,仅有 课程号 不够。因为成绩由"学生-课程"共同决定,所以 (学号, 课程号) 能决定全部属性,是候选码。
第二步,判断范式。
学生姓名只依赖学号,对组合键存在部分依赖,不满足 2NF。- 所以最高只到 1NF。
第三步,分解。
text
Student(学号, 学生姓名)
Course(课程号, 课程名, 教师号)
Teacher(教师号, 教师姓名)
SC(学号, 课程号, 成绩)
8. 解题思路
范式题建议固定流程:
- 写出函数依赖集
- 求候选码
- 区分主属性与非主属性
- 判断是否存在部分依赖
- 判断是否存在传递依赖
- 若要上 BCNF,检查每个决定因素是否为超码
- 分解时说明每一步消除了哪类异常
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 索引为什么能快
索引的本质是:
- 先对关键字做有组织的保存
- 让查找先定位到较小范围
- 再访问真正数据
你可以把它理解成书本目录:
- 没目录:从第一页翻到最后一页
- 有目录:先找到章节页码,再翻到目标页
4.3 B+ 树为什么适合数据库
B+ 树有几个关键优势:
-
多路分支,树高低
一个节点能存很多键,几层就能覆盖大量数据。
-
所有关键字都出现在叶子节点
便于范围扫描,结构稳定。
-
叶子节点通常顺序链接
适合区间查询,例如
BETWEEN、排序、前缀范围。 -
磁盘友好
节点大小可设计得接近一个页,I/O 利用率高。
一个经典直觉是:
- 二叉树像小门店,一层只能接待两个方向
- B+ 树像大型交通枢纽,一层就能分流很多方向
4.4 B+ 树查询过程
假设对 学号 建立 B+ 树索引。
查询 学号 = '2026001' 时:
- 先从根节点比较关键字范围
- 走向对应子节点
- 重复直到叶子节点
- 在叶子上找到对应记录指针,或直接找到整行数据(视聚簇情况而定)
树高通常很低,所以需要的 I/O 次数也不多。
4.5 聚簇索引与非聚簇索引
这是课程和面试都特别爱考的点。
| 类型 | 特点 | 直观理解 |
|---|---|---|
| 聚簇索引 | 数据本身按索引键组织 | 书的正文按目录顺序排版 |
| 非聚簇索引 | 索引与数据分离 | 目录单独记录"去哪里找正文" |
如果是聚簇索引:
- 范围查询通常表现更好
- 按索引顺序读取数据更自然
如果是非聚簇索引:
- 查到索引后往往还要"回表"
- 但建立灵活,一张表可有多个辅助索引
4.6 组合索引与最左前缀
设有组合索引 (A, B, C)。
常见经验是:
- 用到
A,通常可利用索引 - 用到
A, B,通常也可利用 - 只用
B或C,通常不理想
为什么?
因为组合索引内部先按 A 排,再在 A 相同的组内按 B 排,再按 C 排。前缀顺序决定了可利用方式。
4.7 索引为什么不是越多越好
因为索引不是免费午餐。
每建一个索引:
- 占用额外空间
- 插入、删除、更新时要维护
- 优化器选择成本增加
所以只有当一个列经常用于:
- 查询过滤
- 连接条件
- 排序
- 分组
且区分度较高时,索引才更值得。
4.8 哪些情况下索引可能失效
这是实战和考试都很有价值的点。
常见情形:
- 对索引列做函数运算
- 使用前导模糊查询,如
LIKE '%abc' - 隐式类型转换
- 组合索引不满足前缀规律
- 查询结果比例过大,优化器认为全表扫描更划算
4.9 MySQL/InnoDB 的一点工程直觉
如果你学的是通用数据库课程,知道这几点就已经很加分:
- InnoDB 主键索引通常是聚簇索引
- 二级索引叶子节点常保存主键值
- 所以通过二级索引查整行时,往往还需要根据主键再去聚簇索引取数据,这就是"回表"
- 若查询列都在索引里,可能形成"覆盖索引",减少回表成本
5. 易混点与常见误区
误区 1:有索引就一定快
错。若选择性太差、返回行太多、条件写法不利于索引使用,索引不一定比全表扫描快。
误区 2:索引等于排序后的整张表
索引是辅助结构,不等于对数据简单排序后的复制。
误区 3:散列索引适合范围查询
散列主要擅长等值查询,不擅长区间。
误区 4:B+ 树只适合等值查询
不对。B+ 树对范围查询尤其友好,这恰恰是它比哈希更通用的重要原因。
误区 5:主键只是逻辑概念
在某些数据库实现中,主键与物理组织关系非常密切,尤其是在聚簇索引结构下。
6. 考试常见问法
- 数据库存储为什么按页而不是按元组管理?
- 说明堆文件、顺序文件、散列文件的特点。
- 为什么 B+ 树适合作为数据库索引?
- 聚簇索引与非聚簇索引有何区别?
- 为什么索引可以提高查询效率?
- 哪些情况下索引不一定有效?
- 比较 B+ 树索引与哈希索引的适用场景。
7. 典型例题
例题:为什么数据库索引通常使用 B+ 树而不是普通二叉搜索树或哈希结构?
答案思路:
-
和二叉搜索树比:
- B+ 树多路分支,树高低
- 更适合磁盘页 I/O
- 更稳定,不易因树高过大而性能差
-
和哈希比:
- B+ 树支持范围查询、排序、前缀匹配
- 哈希更偏等值查询,通用性差
-
B+ 树叶子节点链式组织,适合顺序扫描
8. 解题思路
关于索引的题,不要只答"快"。一定要答出:
- 快在什么地方
- 为什么快
- 适用什么查询
- 代价是什么
例如答"为什么索引快"时,可以写:
- 通过辅助结构减少全表扫描
- 降低磁盘 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 查询处理的基本流程
-
词法语法分析
检查 SQL 是否写得合法。
-
语义检查
表、列、权限、类型是否正确。
-
查询重写
把 SQL 转成关系代数式,并尝试等价变换。
-
生成多个执行计划
-
成本估计并择优
-
执行与返回结果
4.2 常见等价变换
常考思想包括:
- 选择下推:尽量早过滤
- 投影下推:尽量早减少列数
- 连接重排序:选择更合适的连接顺序
- 将复杂查询改写为更高效形式
为什么投影下推也有价值?
因为即使行数不变,列数减少也会降低中间结果体积,减少 I/O 和内存占用。
4.3 常见扫描方式
| 扫描方式 | 特点 | 适用情形 |
|---|---|---|
| 全表扫描 | 直接扫描整张表 | 无合适索引,或结果比例很大 |
| 索引扫描 | 通过索引定位目标 | 条件选择性较高 |
| 索引范围扫描 | 按区间扫描索引 | 范围查询、排序 |
4.4 常见连接算法
| 算法 | 思想 | 适用场景 |
|---|---|---|
| 嵌套循环连接 | 外表每一行去匹配内表 | 小表配索引、简单直接 |
| 排序-合并连接 | 两边按连接键排序后线性合并 | 两边已排序或适合排序 |
| 哈希连接 | 对较小表构建哈希,再探测另一表 | 等值连接常见高效 |
要会解释它们的差异:
- 嵌套循环适合理解和小规模场景
- 排序-合并适合有序数据与范围合并
- 哈希连接擅长等值连接
4.5 统计信息为什么重要
优化器不是先知,它依赖统计信息估算:
- 表有多少行
- 列值分布如何
- 某条件大概过滤多少
- 哪个索引可能更划算
如果统计信息不准,优化器也可能选错路径。
4.6 执行计划怎么看
虽然不同数据库展示方式不同,但核心关注点一般包括:
- 是否全表扫描
- 是否使用索引
- 连接顺序如何
- 使用了哪种连接算法
- 估算行数大不大
- 是否出现临时表、排序、回表等额外开销
如果你学的是 MySQL,可以记住:
EXPLAIN能帮你观察 SQL 可能的执行方式- 看它有没有走索引、访问类型如何、预估扫描多少行
5. 易混点与常见误区
误区 1:SQL 写出来能跑就行
数据库课程不是只看"能不能跑",还看"为什么这样跑"。
误区 2:优化就是加索引
优化还包括:
- 改写 SQL
- 调整连接顺序
- 减少不必要列
- 提前过滤
- 调整统计信息
误区 3:优化器永远最聪明
优化器依赖统计信息和代价模型,它做的是"估计最优",不一定绝对最优。
误区 4:哈希连接一定最好
不是。不同场景下,嵌套循环和排序-合并也可能更优。
6. 考试常见问法
- 查询处理大致经过哪些步骤?
- 什么是查询优化?为什么需要查询优化?
- 说明选择下推、投影下推的思想。
- 比较嵌套循环连接、排序-合并连接、哈希连接。
- 为什么统计信息会影响优化结果?
- 为什么同样的 SQL 在不同情况下性能可能差异很大?
7. 典型例题
例题:为什么把选择操作尽量提前执行,通常可以提升查询效率?
答题思路:
- 选择操作能先过滤掉大量无关元组
- 后续连接、排序、分组的输入规模减小
- 中间结果变小,I/O、内存、CPU 成本下降
- 因此优化器通常倾向于做选择下推
例题:比较三种常见连接算法。
答题时可按"原理 + 适用场景 + 优缺点"三段来写:
- 嵌套循环:实现简单,小表适合,若内表无索引可能代价高
- 排序-合并:适合已排序数据或大规模顺序合并
- 哈希连接:适合等值连接,但需要额外哈希结构与内存
8. 解题思路
查询优化题很少要求你算得极细,更多是考原理说明。答题模板:
- 指出问题:为什么慢
- 说明根因:数据量大、路径不合理、中间结果过大
- 给出优化原则:提前过滤、减少列、合理索引、选好连接顺序
- 若题目给具体环境,再结合场景落地
9. 简短总结
这一模块最值得建立的意识是:
SQL 只是"你想要什么",执行计划才是"数据库准备怎么做"。
真正理解数据库的人,不会只停在 SQL 文本表面,而会进一步追问:它为什么这么跑,它还有没有更好的跑法。
模块 8:事务与并发控制
1. 本模块解决的问题
数据库之所以复杂,很大程度上不是因为要"存数据",而是因为要在很多人同时访问同一批数据时尽量保证不乱。
这一模块解决的是:
- 什么是事务,为什么事务是数据库最关键的抽象之一?
- 为什么并发会带来脏读、丢失修改、不可重复读、幻读?
- 数据库如何通过锁、协议、MVCC 等机制减少混乱?
- 隔离级别到底在平衡什么?
如果说索引解释"为什么会快",那事务解释的就是"为什么还能尽量正确"。
2. 核心概念与术语
2.1 事务
事务(Transaction)是数据库中一个不可再分的逻辑工作单位。
例如银行转账:
- A 账户减 100
- 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 事务状态
事务通常经历:
- 活动
- 部分提交
- 提交成功
- 失败
- 回滚终止
理解这点有助于你把原子性和恢复机制连起来看。
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 可串行化图的考试直觉
有些课程会考"冲突可串行化判断"。
基本思路:
- 看不同事务间对同一数据项是否有冲突操作
- 若一个事务的冲突操作在前,就画一条边
- 若优先图无环,则通常冲突可串行化
这类题的本质是在判断并发调度是否可视作某种串行顺序。
5. 易混点与常见误区
误区 1:ACID 只要背英文缩写就够了
不够。一定要能联系具体业务说明每个属性在保护什么。
误区 2:不可重复读和幻读是一回事
不是。
- 不可重复读偏"同一行内容变了"
- 幻读偏"满足条件的行集合变了"
误区 3:隔离级别越高越好
并不总是。隔离越强,往往并发越低、成本越高。
误区 4:有锁就一定安全
锁本身也会带来:
- 阻塞
- 死锁
- 吞吐下降
所以数据库不是简单"多加锁就完事"。
误区 5:事务只和转账有关
事务适用于一切"多步操作必须整体正确"的业务。
6. 考试常见问法
- 什么是事务?事务的四大特性是什么?
- 什么是脏读、不可重复读、幻读?
- 隔离级别有哪些?各自能避免哪些问题?
- 什么是共享锁、排他锁?
- 什么是两段锁协议?它有什么作用?
- 什么是死锁?如何处理?
- 什么是可串行化?
- 简述 MVCC 的基本思想。
7. 典型例题
例题 1:说明为什么银行转账必须作为事务执行。
解答要点:
- 转账包含扣款和加款两个步骤
- 若只完成一半,会破坏数据一致性
- 原子性保证要么都成功,要么都失败
- 持久性保证提交后不会因故障轻易丢失
例题 2:区分脏读、不可重复读、幻读。
答题模板:
- 脏读:读到未提交修改
- 不可重复读:同一事务两次读同一行,内容发生变化
- 幻读:同一事务两次按条件查询,返回行集合发生变化
例题 3:为什么两段锁协议能提高调度正确性?
答题思路:
- 先说两段锁的定义
- 再说它限制锁的获取与释放顺序
- 再说这种限制有助于保证冲突可串行化
8. 解题思路
事务题通常按"现象 → 原因 → 机制 → 代价"答最稳。
例如答"为什么要有隔离级别"时:
- 先说并发会带来异常
- 再说完全串行虽安全但效率低
- 所以数据库设置不同隔离级别做权衡
- 最后补充各级别可避免的问题
9. 简短总结
这个模块最重要的理解是:
事务让多步操作变成一个可靠单位,并发控制让多个事务可以在"尽量不乱"的前提下同时推进。
数据库真正难,也真正精彩的地方,就在这里:它既不能完全放任并发,也不能把所有人都堵死,只能在正确性和性能之间不断做精细平衡。
模块 9:故障恢复机制
1. 本模块解决的问题
前面讲事务时,你已经知道事务要求"提交后尽量不丢"。可新的问题来了:
- 如果数据库刚写到一半机器突然断电怎么办?
- 如果内存里的修改还没刷盘,系统崩了怎么办?
- 如果已经写了一部分磁盘,另一部分还没写,怎么恢复到正确状态?
- 为什么"日志"能帮助恢复?
这一模块解决的核心是:数据库发生故障后,如何把数据恢复到一致、可信的状态。
2. 核心概念及术语
| 术语 | 含义 | 通俗解释 |
|---|---|---|
| 故障恢复 | 故障发生后重建正确数据库状态 | 把系统从"半路出错"拉回可用状态 |
| 事务故障 | 单个事务出错 | 一笔业务自己失败 |
| 系统故障 | 数据库系统宕机、断电等 | 整个服务突然停掉 |
| 介质故障 | 磁盘损坏等物理介质问题 | 数据文件本体出大问题 |
| 日志(Log) | 记录事务更新信息的顺序文件 | 像黑匣子 |
| Undo | 撤销未完成事务的影响 | 把不该留下的改动擦掉 |
| Redo | 重做已提交事务的影响 | 把该落地但还没完全落地的改动补齐 |
| 检查点(Checkpoint) | 恢复时的已知安全位置 | 像游戏存档点 |
| WAL | 先写日志,再写数据 | 恢复正确性的关键纪律 |
3. 先讲直觉:为什么恢复不是"重新启动数据库"那么简单
假设事务 T1 已经执行:
- 账户 A 扣 100
- 账户 B 加 100
如果系统在第一步写盘后、第二步写盘前突然断电,此时磁盘中的数据是不一致的。
重新开机并不能自动解决这个问题。数据库必须知道:
- 哪些事务已经提交,必须保留
- 哪些事务没完成,必须撤销
- 哪些修改已经写到磁盘
- 哪些修改只存在于内存或部分页中
所以恢复系统的核心不是"重启",而是根据历史记录做出正确回放或回滚。
苏格拉底式提问:为什么日志必须先于数据写盘
问:如果我先把数据写到磁盘,再慢慢写日志,不行吗?
答:不行。
问:为什么?
答:因为如果数据先写了一半,日志还没写,系统崩溃后你就不知道该撤销还是重做什么。
问:所以必须先把"发生了什么"记下来?
答:对。这就是 WAL 的本质:先留下证据,再实施改动。
4. 关键机制、规则与方法
4.1 故障类型
| 故障类型 | 特点 | 恢复重点 |
|---|---|---|
| 事务故障 | 单个事务逻辑错误、违反约束、被回滚 | Undo |
| 系统故障 | 断电、宕机,内存丢失但磁盘日志仍在 | Undo + Redo |
| 介质故障 | 磁盘损坏、文件破坏 | 备份恢复 + 日志重演 |
4.2 日志记录内容
数据库日志通常记录:
- 事务开始
- 事务修改了哪个数据项
- 修改前值
- 修改后值
- 提交或回滚标记
为什么要同时记录前值和后值?
- 前值支持 Undo
- 后值支持 Redo
4.3 Undo 与 Redo 的因果逻辑
可以用一句话记:
- 未提交的,撤销
- 已提交的,重做
为什么"已提交的还要重做"?
因为"事务提交"不代表所有数据页都已经真正刷到磁盘。提交时系统常常只是保证日志足够安全。若系统随后崩溃,那些还没真正落盘的修改就需要 Redo。
4.4 检查点
恢复如果每次都从日志开头扫,会非常慢。于是数据库会定期做检查点:
- 将某时刻之前的关键状态落地
- 记录一个恢复起点
这样故障恢复时,不必从远古日志开始重放。
检查点的价值不是"防故障",而是缩短恢复时间。
4.5 缓冲区管理与脏页
数据库通常先在内存缓冲区修改页,再择机刷盘。
这就会产生:
- 脏页:内存中已修改但尚未写回磁盘的页
恢复之所以复杂,就是因为:
- 事务提交顺序
- 数据页刷盘顺序
- 日志写入顺序
这三者不一定一致。
4.6 恢复过程的一般思路
很多教材把恢复过程拆成类似以下思路:
- 分析:确定哪些事务已提交,哪些未完成
- Redo:重做需要保留的已提交事务
- Undo:撤销未提交事务的影响
不同数据库实现会更复杂,但考试里抓住这条主线就够了。
4.7 影子分页(Shadow Paging)
有些课程还会提影子分页思想:
- 不直接覆盖旧页
- 修改时生成新页
- 最后通过指针切换生效
它也能支持恢复,但现代主流数据库更常结合日志机制实现高效恢复。
4.8 备份与日志恢复的关系
介质故障时,仅靠在线日志未必够,因为数据文件本体可能都没了。这时要:
- 先恢复最近备份
- 再用日志把备份之后的更新重演出来
所以高可用和可恢复,不只靠事务日志,还靠备份体系。
5. 易混点与常见误区
误区 1:事务提交就代表数据一定已经写入所有数据页
不一定。提交更多是"逻辑上生效",而物理页刷盘可能稍后进行。
误区 2:恢复只和系统崩溃有关
单个事务失败也属于恢复处理范围。
误区 3:Undo 和 Redo 含义记反
记忆法:
- Undo 撤回不该留下的
- Redo 补上应该留下但还没彻底落地的
误区 4:检查点能消除日志
检查点主要是缩小恢复扫描范围,不是让日志完全失去意义。
6. 考试常见问法
- 为什么数据库需要恢复机制?
- 什么是事务故障、系统故障、介质故障?
- 什么是日志?日志中通常记录什么信息?
- 什么是 Undo、Redo?
- 为什么要采用先写日志后写数据的原则?
- 什么是检查点?其作用是什么?
- 备份与日志恢复如何配合?
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 安全控制的几个层次
数据库安全性不是单一机制,而是多层协同:
- 身份认证:你是谁
- 权限控制:你能做什么
- 视图隔离:你能看见什么
- 审计与日志:你做过什么
- 加密与备份保护:数据被拿走后还能不能看懂
4.3 授权与回收
常见 SQL:
sql
GRANT SELECT, INSERT
ON Student
TO user_a;
sql
REVOKE INSERT
ON Student
FROM user_a;
课程考试里通常考的是思想:
- 权限应最小化
- 按角色授权优于按用户零散授权
- 需要时可回收权限
4.4 视图的三大价值
-
简化查询
把复杂连接封装起来。
-
逻辑独立性
底层表结构调整时,视图可作为缓冲层。
-
安全性
用户只接触需要的数据,不直接面对全部原表。
4.5 触发器与存储过程
课程中常作为"了解 + 会解释用途"的内容:
- 触发器:响应
INSERT、UPDATE、DELETE自动执行 - 存储过程:把一段数据库逻辑封装在服务端
它们的优点:
- 集中控制规则
- 降低重复逻辑
- 可做审计和自动维护
但也要知道其代价:
- 逻辑可能隐藏得更深
- 调试和迁移成本上升
5. 易混点与常见误区
误区 1:安全性和完整性是一回事
不是。
- 完整性关注"数据本身是否合法"
- 安全性关注"谁能访问和如何访问"
误区 2:视图一定存数据
普通视图通常不物理存储数据,只保存定义。
误区 3:外键只是程序员约定
外键是数据库层约束,不是口头规范。
误区 4:权限越细越安全
过细的权限体系若难以维护,也可能带来配置错误。合理分层、角色化更重要。
6. 考试常见问法
- 什么是完整性约束?有哪些类型?
- 什么是实体完整性、参照完整性?
- 什么是视图?视图有哪些作用?
- 数据库安全性通常通过哪些机制实现?
- 什么是授权和回收权限?
- 触发器和存储过程的用途是什么?
7. 典型例题
例题:说明视图在数据库系统中的作用。
标准答题点:
- 简化用户操作
- 屏蔽底层复杂结构
- 提供逻辑独立性
- 增强安全性,只暴露部分数据
例题:说明参照完整性为什么重要。
答题思路:
- 先定义:外键值必须引用有效主码或为空
- 再说明:否则会出现"选课记录引用不存在学生"这类脏数据
- 最后说明:数据库通过外键约束维护关系正确性
8. 解题思路
本模块题目多偏概念说明。最稳答法是:
- 定义
- 作用
- 实现方式
- 例子
例如答"数据库安全性"时,不要只写"防止非法访问",最好继续补:
- 通过认证、授权、视图、审计等机制实现
- 目的是保证数据机密性、可控性、可追踪性
9. 简短总结
完整性是在防止"错误数据进入系统",安全性是在防止"错误的人碰到数据"。而视图、约束、授权等机制,正是数据库作为"受控系统"而不是"裸数据仓库"的体现。
模块 11:分布式数据库与 NoSQL 概览
1. 本模块解决的问题
当数据量变大、访问量变高、业务类型变复杂时,单机关系数据库就可能遇到瓶颈。于是新问题出现:
- 一台机器存不下怎么办?
- 并发太高扛不住怎么办?
- 数据分布在多个节点时,如何保持一致?
- 为什么会出现键值数据库、文档数据库、列族数据库、图数据库?
这一模块是数据库课程的现代扩展部分,帮助你把"传统数据库原理"连接到真实系统世界。
2. 核心概念及术语
| 概念 | 含义 | 直白解释 |
|---|---|---|
| 分布式数据库 | 数据分布在多个节点上统一管理 | 数据不再只在一台机器上 |
| 复制 | 同一份数据保存多份副本 | 为了高可用和读扩展 |
| 分片 / 分区 | 把数据拆开存到不同节点 | 为了容量和写扩展 |
| 一致性 | 多副本或多节点之间数据是否一致 | 看到的是不是同一版本 |
| 可用性 | 系统是否能持续响应请求 | 挂一台还能不能用 |
| 分区容错性 | 网络分裂时系统能否继续工作 | 分布式系统必须面对的问题 |
| NoSQL | 非传统关系模型数据库统称 | 面向特定场景的多样化方案 |
3. 通俗理解与因果推理
3.1 为什么会从单机走向分布式
单机数据库强在:
- 事务语义清晰
- 一致性强
- 管理集中
但它的限制也明显:
- 磁盘、CPU、内存都有上限
- 单点故障风险高
- 海量流量时扩展成本高
于是系统就会做两件事:
- 复制:同一份数据多放几份,提高可用性和读取能力
- 分片:把不同数据拆开放到不同机器,提升容量和并行度
3.2 CAP 直觉
CAP 不是考试中最适合被神化的概念,但它很有助于你建立分布式直觉。
它强调,在网络分区存在的情况下,系统往往要在:
- 一致性(C)
- 可用性(A)
- 分区容错性(P)
之间做取舍。
因为分布式系统一旦网络不可靠,就不可能像单机那样"默认大家立刻看到同一状态"。
3.3 为什么会有 NoSQL
问:关系数据库不好吗?
答:很好,但不是所有场景都最适合它。
问:哪些场景会逼出新类型数据库?
答:
- 数据模式经常变化
- 超大规模写入
- 极高并发缓存访问
- 图关系复杂
- 追求横向扩展优先
于是出现了不同偏好的数据库:
- 键值型:追求极快读写
- 文档型:结构灵活
- 列族型:适合大规模分布式存储
- 图数据库:擅长关系路径查询
4. 关键机制与方法
4.1 复制
复制常见作用:
- 提高可用性
- 支持读扩展
- 灾难恢复
常见模式:
- 主从复制
- 多主复制
课程考试里一般要求知道:
- 写入通常先到主节点
- 再传播到从节点
- 如果是异步复制,主从间可能短暂不一致
4.2 分片
分片是把数据按某种规则拆到不同节点。
常见分片键:
- 用户 ID
- 地区
- 时间
分片收益:
- 单机压力下降
- 容量提升
- 并行处理能力增强
但新问题也随之而来:
- 跨分片查询更复杂
- 跨分片事务更难
- 热点分片可能失衡
4.3 分布式事务直觉
单机事务已经很复杂,跨节点事务更难。因为:
- 节点可能失败
- 网络可能延迟或分裂
- 多方需要达成一致
课程中常提两阶段提交(2PC):
- 准备阶段:询问各参与者是否能提交
- 提交阶段:协调者统一决定提交或回滚
它提高了一致性,但也会带来阻塞和协调开销。
4.4 NoSQL 四类典型模型
| 类型 | 代表结构 | 优点 | 典型场景 |
|---|---|---|---|
| 键值数据库 | Key -> Value | 极快、简单 | 缓存、会话 |
| 文档数据库 | JSON/BSON 文档 | 结构灵活 | 内容系统、快速迭代业务 |
| 列族数据库 | 列簇组织 | 适合海量分布式数据 | 日志、宽表、分析 |
| 图数据库 | 点和边 | 关系遍历能力强 | 社交关系、推荐、知识图谱 |
4.5 关系数据库与 NoSQL 的关系
不要把它们看成"谁取代谁"。
更准确地说:
- 关系数据库擅长结构化数据、一致性、复杂查询、事务
- NoSQL 更强调特定场景下的扩展性、灵活性或访问模式
现代系统中,它们常常协同使用,而不是彼此消灭。
5. 易混点与常见误区
误区 1:NoSQL 就是不支持 SQL
NoSQL 更准确是"Not Only SQL",强调不只一种数据模型,不等于完全没有查询语言。
误区 2:NoSQL 一定比关系数据库先进
不是,是场景不同、取舍不同。
误区 3:分布式一定更好
分布式会带来额外复杂性,只有在单机确实无法满足时才值得。
误区 4:复制等于分片
- 复制是同一份数据多份保存
- 分片是不同数据分散保存
6. 考试常见问法
- 为什么需要分布式数据库?
- 什么是复制与分片?二者有何区别?
- CAP 反映了什么分布式系统矛盾?
- 什么是两阶段提交?
- NoSQL 产生的原因是什么?
- 比较关系数据库与 NoSQL 数据库的适用场景。
7. 典型例题
例题:简述关系数据库和 NoSQL 数据库的差异。
答题思路:
- 数据模型:关系模型 vs 多样化模型
- 模式:固定模式较强 vs 模式灵活
- 事务一致性:关系库更强
- 扩展方式:NoSQL 往往更强调横向扩展
- 适用场景:复杂事务与查询 vs 高并发、大规模、特定访问模式
8. 解题思路
这一模块答题不要陷入技术细枝末节,重点是:
- 为什么会出现新技术
- 它解决什么原本难解决的问题
- 它为此牺牲了什么
9. 简短总结
这一模块最重要的认知是:
数据库世界没有万能方案。单机关系数据库、分布式数据库、NoSQL,都是在不同约束下做的取舍。
理解它们,不是为了追潮流,而是为了知道:当问题变了,数据库设计为什么也必须跟着变。
模块 12:综合设计与工程落地
1. 本模块解决的问题
学到这里,你已经分别学过建模、SQL、范式、索引、事务、恢复和安全。但考试和项目里真正难的地方,是把这些内容连起来。
这一模块解决的是:
- 面对一个业务场景,如何从需求一路走到数据库实现?
- 为什么"表设计、索引设计、事务边界、权限控制、备份恢复"要一起考虑?
- 综合题该怎么答,项目实践该怎么想?
2. 综合方法论:从需求到数据库的完整链条
可以把数据库设计与实现看成九步:
- 明确业务对象与规则
- 做概念建模(E-R)
- 转换为关系模式
- 判断主键、外键与完整性约束
- 用范式优化结构,控制冗余
- 根据查询场景设计索引
- 根据业务原子性划定事务边界
- 根据用户角色设计视图和权限
- 根据可靠性需求设计备份、恢复、复制策略
这九步中,任何一步省略,系统都有可能在后面返工。
3. 综合案例:教学管理系统
3.1 需求描述
某教学系统需要支持:
- 维护学生信息
- 维护教师信息
- 维护课程信息
- 支持学生选课与退课
- 支持教师录入成绩
- 支持查询某学生成绩单
- 支持统计某课程平均分
- 不同角色权限不同:学生、教师、管理员
3.2 概念建模
实体:
- 学生
- 教师
- 课程
联系:
- 教师授课
- 学生选课
可能的属性:
- 学生:学号、姓名、专业、年级
- 教师:工号、姓名、职称
- 课程:课程号、课程名、学分、授课教师号
- 选课联系:学号、课程号、成绩、选课时间
3.3 关系模式设计
text
Student(学号, 姓名, 专业, 年级)
Teacher(工号, 姓名, 职称)
Course(课程号, 课程名, 学分, 工号)
SC(学号, 课程号, 成绩, 选课时间)
约束:
Student.学号主键Teacher.工号主键Course.课程号主键Course.工号外键引用TeacherSC(学号, 课程号)组合主键SC.学号外键引用StudentSC.课程号外键引用Course成绩取值范围 0 到 100
3.4 规范化分析
为什么这样设计较合理?
- 学生信息只由学号决定
- 教师信息只由工号决定
- 课程信息只由课程号决定
- 选课成绩由
(学号, 课程号)决定
不同事实由不同决定因素负责,减少冗余与异常。
3.5 索引设计
如果系统高频查询包括:
- 按学号查成绩单
- 按课程号查选课名单
- 按教师查授课课程
则可考虑:
SC(学号)索引SC(课程号)索引Course(工号)索引
若经常按 (学号, 课程号) 联合查询,则组合主键本身已天然支持。
3.6 事务设计
选课操作看似简单,但本质上可能包含:
- 检查课程是否存在
- 检查是否已选
- 检查人数是否已满
- 写入选课记录
- 更新课程已选人数
这些步骤必须在同一事务中完成,否则容易出现:
- 重复选课
- 超卖名额
- 记录写了但人数没更新
3.7 权限设计
不同角色权限不同:
- 学生:可查询自己信息和成绩
- 教师:可查询自己课程名单、录入成绩
- 管理员:可维护基础数据
这就说明数据库不是"所有人都能直接查所有表",而要通过角色、视图和授权控制访问面。
3.8 恢复与可用性设计
系统上线后还要考虑:
- 定期备份
- 日志保留
- 主从复制
- 故障恢复演练
否则再好的逻辑设计,也可能在一次硬盘损坏后归零。
4. 考试综合题怎么答
数据库综合设计题常常把多个模块混在一起。答题时建议顺序:
- 先找实体和联系
- 画简版 E-R
- 转关系模式
- 标主键、外键、约束
- 看是否存在冗余与异常
- 提出规范化结果
- 说明典型查询需要的索引
- 若题目涉及并发,指出事务边界
- 若题目涉及安全,说明角色/视图设计
5. 工程实践中的常见权衡
数据库理论喜欢干净,工程实践必须面对成本。
常见权衡包括:
- 范式越高,结构越纯,但连接可能越多
- 索引越多,读可能越快,但写入越慢
- 隔离级别越高,正确性越强,但并发越低
- 强一致越强,分布式扩展代价越高
所以真正成熟的数据库设计,不是追求某一个指标极致,而是根据业务目标做平衡。
6. 易混点与常见误区
误区 1:综合题只要把表列出来就行
不够。综合题真正考查的是你有没有把"结构、约束、查询、并发、安全、恢复"串起来。
误区 2:工程实践和考试理论完全割裂
恰恰相反,工程里的很多问题正是理论概念的现实体现。
误区 3:数据库设计做完表结构就结束
表结构只是开始,索引、事务、权限、备份同样重要。
7. 考试常见问法
- 根据业务需求设计关系模式。
- 指出主键、外键和完整性约束。
- 说明是否存在冗余、如何规范化。
- 为高频查询设计索引。
- 指出哪些操作应放入同一事务。
- 设计用户权限与视图。
8. 典型例题
例题:为图书借阅系统设计数据库。
通常至少包括:
- 图书
- 读者
- 借阅记录
- 管理员
答题关键不是表越多越好,而是:
- 先把"图书信息"和"借阅行为"分开
- 再把"借阅记录"作为联系表处理
- 最后考虑库存、超期、权限、事务
9. 解题思路
综合题一定要"先宏观后微观":
- 先搭数据对象骨架
- 再加约束和依赖
- 再讲查询与索引
- 最后讲事务和安全
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 | 安全性、授权、视图、完整性约束 | 概念题稳定出分 |
如果你真的只剩三四天,优先顺序建议就是:
- 范式与函数依赖
- SQL
- 事务与并发
- 索引与恢复
- 其余章节补定义和大框架
二、高频考点清单
1. 概念基础类
- 数据库、DBMS、数据库系统的区别
- 数据模型三层次
- 三级模式结构与两级映像
- 数据独立性的含义
- 文件系统与数据库系统对比
2. 建模与关系模型类
- 实体、属性、联系、码
- E-R 图设计
- E-R 到关系模式转换
- 实体完整性、参照完整性
- 候选码、主码、外码判断
3. 关系理论类
- 选择、投影、连接、除法
- 关系代数表达查询
- 函数依赖定义与判断
- Armstrong 公理
- 属性闭包求法
- 范式判断与分解
- 无损连接与保持依赖
4. SQL 类
- 基础查询、条件过滤、排序
- 聚合与分组
WHERE与HAVING- 多表连接
- 子查询:
IN、EXISTS - 建表、约束、视图
5. 存储与索引类
- 页、记录、文件组织
- B+ 树索引原理
- 聚簇/非聚簇索引
- 哈希索引对比
- 组合索引与最左前缀
- 索引失效原因
6. 事务与恢复类
- 事务四大特性 ACID
- 脏读、不可重复读、幻读
- 共享锁、排他锁
- 两段锁协议
- 可串行化直觉
- 日志记录
- Undo/Redo
- WAL
- 检查点
7. 安全与扩展类
- 完整性约束三类型
- 授权、回收权限
- 视图的作用
- 复制与分片
- 关系数据库与 NoSQL 对比
三、必背概念、公式、定理或方法
必背定义
- 数据库、DBMS、数据库系统
- 模式与实例
- 数据独立性
- 候选码、主码、外码
- 实体完整性、参照完整性
- 函数依赖、完全函数依赖、部分函数依赖、传递函数依赖
- 1NF、2NF、3NF、BCNF
- 事务、ACID
- 脏读、不可重复读、幻读
- 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,则通常为无损连接分解。
必背方法
- 求候选码:用闭包看是否覆盖全部属性,再判最小性
- 判断 2NF:看是否有对组合码的部分依赖
- 判断 3NF:看是否有非主属性对码的传递依赖
- 关系代数解题:先找表,再筛行,再连表,后取列
- SQL 解题:来源表 -> 条件 -> 分组 -> 输出
- 事务题:先认并发异常,再配隔离机制
- 恢复题:先分故障类型,再说 Undo/Redo
四、常见题型分类
| 题型 | 高频程度 | 分值特点 | 解题关键 |
|---|---|---|---|
| 名词解释 / 简答题 | ★★★★★ | 分散但稳定 | 定义 + 作用 + 对比 |
| E-R 建模题 | ★★★★ | 中等到偏大 | 找实体、联系、基数 |
| 候选码 / 函数依赖 / 范式题 | ★★★★★ | 大题核心 | 闭包、依赖、分解 |
| 关系代数题 | ★★★★ | 稳定考查逻辑 | 筛行、连表、取列 |
| SQL 编写题 | ★★★★★ | 最好拿分 | 审题准确、分组/连接清晰 |
| 索引原理题 | ★★★★ | 原理分较多 | I/O、B+ 树、适用场景 |
| 事务并发分析题 | ★★★★★ | 概念+分析 | 异常类型、锁、隔离级别 |
| 恢复机制题 | ★★★★ | 常考原理 | 日志、Undo/Redo、WAL |
| 综合设计题 | ★★★★ | 分值高 | 建模 + 约束 + 范式 + 索引 + 事务 |
五、考试最容易丢分的地方
- 把"数据库""数据库系统""DBMS"写成同义词
- 2NF、3NF、BCNF 定义混淆
- 候选码没判最小性
- 关系代数漏连接条件
- SQL 分组题把聚合条件写到
WHERE - 多表查询漏掉连接键导致笛卡尔积
NULL判断写成= NULL- 只写事务四大特性英文,不解释含义
- 分不清脏读、不可重复读、幻读
- 恢复题没区分未提交事务和已提交事务
六、各章一句话提分抓手
| 章节 | 一句话抓手 |
|---|---|
| 基础概念 | 先分清层次,再记名词 |
| 建模 | 先找实体和联系,再谈表 |
| 关系理论 | 先看谁决定谁,再谈范式 |
| SQL | 先想逻辑顺序,再写语法 |
| 索引 | 先想如何少读页,再谈结构 |
| 事务 | 先想并发会乱在哪里,再选机制 |
| 恢复 | 先看哪些该保留,哪些该撤销 |
| 安全 | 先看谁能访问,再看如何约束 |
典型题型与解题模板
题型 1:名词解释 / 简答定义题
常见问法
- 什么是数据独立性?
- 什么是候选码?
- 什么是事务?
- 什么是幻读?
解题模板
- 给出标准定义
- 说明其作用或意义
- 若题目涉及比较,再补区别
- 最后给一个短例子
示例模板
"数据独立性是指应用程序与数据库的数据结构之间相互独立的特性。其作用是使数据库某一层次的改变尽量不影响其他层次,提高系统可维护性。数据独立性包括逻辑独立性和物理独立性。"
题型 2:E-R 建模题
解题模板
- 从题目中圈出核心对象
- 判断对象是实体还是联系
- 提取每个实体属性
- 判断联系基数:1:1、1:N、M:N
- 看联系本身是否带属性
- 画图或文字说明后转换为关系模式
自检问题
- 有没有把"联系"错当成"实体"?
- 有没有漏掉多对多关系对应的中间表?
- 主键是否明确?
题型 3:求候选码 / 闭包题
解题模板
- 写出函数依赖集
- 选一个可能的属性集
X - 求
X^+ - 若闭包覆盖全部属性,则
X是超码 - 检查
X是否还能删减属性 - 能唯一且最小,则为候选码
核心口诀
先看能否决定全体,再看是否最小。
题型 4:范式判断题
解题模板
- 先说明是否满足 1NF
- 找候选码、主属性、非主属性
- 检查是否有部分依赖
- 检查是否有传递依赖
- 若考 BCNF,再检查每个决定因素是否为超码
- 最终给出最高范式并说明理由
题型 5:关系模式分解题
解题模板
- 明确原关系中存在哪类异常
- 根据函数依赖拆分
- 写出子关系模式
- 标出主键
- 说明消除了哪些冗余或异常
- 若题目要求,再判断无损连接与依赖保持
题型 6:关系代数表达题
解题模板
- 先确定输出列
- 再确定涉及的表
- 用选择表示条件过滤
- 用连接把相关表连起来
- 用投影输出最终属性
- 若题目有"全部",考虑除法
核心口诀
先找表,再筛行,再连表,后取列。
题型 7:基础 SQL 查询题
解题模板
- 确认查询目标列
- 确认来源表
- 写
WHERE原始条件 - 若多表,先写连接条件
- 若需要排序,加
ORDER BY - 若需要去重,加
DISTINCT
题型 8:分组与聚合 SQL 题
解题模板
- 看题目是否有"每个""平均""总数""最大值"等词
- 若有,通常考虑
GROUP BY - 聚合前过滤用
WHERE - 聚合后过滤用
HAVING - 输出列里非聚合属性必须与分组一致
核心口诀
行条件看 WHERE,组条件看 HAVING。
题型 9:连接与子查询 SQL 题
解题模板
- 判断是直接连接更清晰,还是子查询更自然
- 涉及"存在某类记录"时可优先想
EXISTS - 涉及"属于集合"时可考虑
IN - 涉及"高于平均值"时常用标量子查询
- 涉及"没有......的对象"时优先考虑
NOT EXISTS
题型 10:索引原理题
解题模板
- 先说明索引的作用:减少查找范围和 I/O
- 再说明具体结构特点:B+ 树多路平衡、叶子链式
- 再说明适用场景:等值、范围、排序
- 最后补充代价:维护成本、空间成本
题型 11:事务并发分析题
解题模板
- 找出事务之间对同一数据项的交叉操作
- 判断出现的是脏读、不可重复读、幻读还是丢失修改
- 若考锁,说明使用 S 锁 / X 锁或两段锁
- 若考隔离级别,指出哪个级别能避免对应问题
题型 12:恢复机制题
解题模板
- 先分故障类型
- 说明日志记录作用
- 指出哪些事务需 Undo,哪些需 Redo
- 若问 WAL,就强调先写日志后写数据
- 若问检查点,就强调缩短恢复时间
题型 13:综合设计题
解题模板
- 概念建模:实体、属性、联系
- 关系设计:关系模式、主键、外键
- 规范化:说明依赖与拆分
- 约束:完整性条件
- 索引:围绕高频查询设计
- 事务:围绕关键业务动作划界
- 安全:角色、视图、授权
题型 14:对比类论述题
常见对比
- 文件系统 vs 数据库系统
- 候选码 vs 主码
- 2NF vs 3NF
- 聚簇索引 vs 非聚簇索引
- 脏读 vs 不可重复读 vs 幻读
- 关系数据库 vs NoSQL
解题模板
- 分别定义
- 对比关键差异
- 说明各自适用场景或作用
易错点与避坑指南
一、概念层易错点
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. WHERE 与 HAVING 混淆
避坑方法:
- 原始行过滤看
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. 只谈理论,不落查询与事务
好的综合答案必须把结构设计、查询需求、索引设计、并发与事务串起来。
八、最后一分钟速检清单
考场写完后,至少扫一遍这十项:
- 定义题是否写了"概念 + 作用"
- 对比题是否写出"区别点"
- 设计题是否标主键外键
- 范式题是否先求码
- SQL 是否漏连接条件
- SQL 聚合条件是否用对
HAVING - 是否误用
NULL - 事务异常是否分清
- 恢复题是否分清 Undo / Redo
- 论述题是否有"因果解释",不只是结论
满分复习策略
一、总策略:先搭框架,再抓高频,再刷题固化
数据库这门课最怕两种学法:
- 只背定义,不理解问题链
- 只刷 SQL,不懂数据库整体结构
高分策略应该是:
- 先把知识地图搭起来
- 再把高频板块吃透
- 最后通过题型模板固化输出
二、80 分以上的核心打法
第一步:拿下最稳分板块
最稳板块通常是:
- 基础概念
- E-R / 关系模型
- SQL
- 范式
- 事务
这几块只要掌握到位,很多学校的数据库试卷已经能支撑 70 分以上。
第二步:把容易拉开差距的原理题补上
包括:
- B+ 树为什么适合做索引
- 为什么会有各种并发异常
- 为什么日志可以恢复
- 为什么视图可以增强安全性
这些题目不是死记硬背就能拿满,必须讲清因果链。
第三步:练"综合表达能力"
高分不是"知道",而是"在纸上写得完整"。
你要练的是:
- 概念定义怎么写成标准答案
- SQL 怎么写得不漏条件
- 范式题怎么按步骤展开
- 论述题怎么讲清"为什么"
三、最应该优先投入时间的内容比例
如果把总复习时间看成 100%,建议:
| 模块 | 时间占比 |
|---|---|
| SQL 与关系代数 | 20% |
| 函数依赖、范式、设计 | 25% |
| 事务与并发控制 | 20% |
| 基础概念与完整性/安全 | 10% |
| 索引、存储、优化 | 15% |
| 恢复、分布式、NoSQL | 10% |
四、高频考点背诵策略
数据库不适合全文死背,适合"模块化背诵"。
建议背诵顺序:
- 先背概念骨架
- 再背对比关系
- 最后背答题模板
例如:
- 先背 ACID 四个词与含义
- 再背脏读 / 不可重复读 / 幻读区别
- 再背"事务题答题模板"
五、刷题策略
1. 概念题怎么刷
做法:
- 每章列 5 到 10 个高频名词
- 每个名词都练"定义 + 作用 + 对比"
标准:
- 能在 30 秒内写出标准定义
- 能在 1 分钟内写出区别与应用
2. SQL 题怎么刷
做法:
- 单表查询 10 题
- 连接查询 10 题
- 分组聚合 10 题
- 子查询 10 题
- 视图与建表语句 5 题
标准:
- 能独立写出
- 能自己检查连接条件、分组条件和空值判断
3. 范式题怎么刷
做法:
- 每次都强迫自己完整走一遍:
- 依赖集
- 闭包
- 候选码
- 主属性/非主属性
- 范式判断
- 分解
标准:
- 不能"凭感觉"说 2NF / 3NF,必须能写出推理过程
4. 事务题怎么刷
做法:
- 把每种并发异常都用一组简单操作自己演一遍
- 画出时间顺序图
- 对照不同隔离级别能防什么
标准:
- 能看到调度就识别异常类型
六、容易被忽略但很值分的点
- 视图作用经常以简答题出现
WHERE与HAVING区别极容易出 SQL 题- 外键和参照完整性概念题稳定出现
- B+ 树和哈希索引对比很适合出论述题
- WAL 与 Undo/Redo 经常是恢复题核心
七、考前 3 天复习重点
第 3 天:搭总框架
- 通读知识地图
- 回看每章一句话抓手
- 把高频定义、对比项、关键表格抄一遍
- 整理错题类型
第 2 天:做高频题型
- 2 套范式题
- 2 套 SQL 综合题
- 2 套事务 / 恢复分析题
- 1 套综合设计题
要求:不只是做对,还要写完整步骤。
第 1 天:背模板 + 查漏补缺
- 背必背定义和对比
- 背事务异常和隔离级别表
- 背索引与恢复类模板答案
- 把最容易错的 SQL 题再看一遍
八、考场答题策略
1. 先做稳分题
优先顺序建议:
- 名词解释 / 简答
- SQL
- 范式题
- 事务与恢复分析
- 综合设计 / 论述
2. 概念题不要只写一句
至少写三层:
- 定义
- 作用
- 区别或例子
3. SQL 题写完必须二次检查
检查:
- 表别名是否一致
- 连接条件是否完整
- 聚合条件是不是放在
HAVING - 是否需要
DISTINCT
4. 范式题宁可慢一点,也别跳步
很多失分不是不会,而是:
- 没写闭包
- 没说明候选码
- 没写为什么不满足某范式
5. 论述题要讲"为什么"
例如答"为什么 B+ 树适合索引",不要只写"查询快",还要补:
- 磁盘 I/O
- 树高低
- 范围查询友好
九、最后的高分心法
数据库想冲高分,真正有效的不是"多看几遍书",而是做到三件事:
- 脑中有因果链
- 手里有答题模板
- 笔下有完整表达
只会看懂不够,只会背也不够,必须把"理解 -> 结构化输出"这一步练出来。
分阶段学习计划
阶段一:快速入门阶段
学习目标
建立数据库整门课的整体框架,知道它研究什么、有哪些模块、模块之间如何衔接。
学习内容
- 数据库、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. 最重要的八句记忆句
- 数据库不是文件堆,而是被统一管理的数据系统。
- 三级模式的核心目的,是隔离变化。
- E-R 解决"现实怎么抽象",关系模型解决"抽象怎么落表"。
- 关系代数不是折磨人,而是在训练查询骨架。
- 范式不是拆表游戏,而是在按依赖结构消除异常。
- 索引快的本质,是减少磁盘页访问。
- 事务解决多步操作的一致性,并发控制解决多人同时操作时的不乱。
- 恢复依赖日志,因为系统必须知道"发生过什么"。
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 分钟必看
WHERE和HAVING不要混NULL不要写成= NULL- 范式题先求候选码
- 多表 SQL 一定检查连接条件
- 事务异常一定区分"读到未提交""同一行变""行集合变"
高频概念表
| 概念 | 标准理解 | 常见混淆 | 快速区分 |
|---|---|---|---|
| 数据库 | 有组织、可共享的数据集合 | 和 DBMS 混淆 | 数据是对象 |
| DBMS | 管理数据库的软件系统 | 和数据库系统混淆 | 软件是工具 |
| 数据库系统 | DB + DBMS + 应用 + 环境 | 和数据库混淆 | 系统是整体 |
| 模式 | 数据库逻辑结构描述 | 和实例混淆 | 像蓝图 |
| 实例 | 某时刻数据库实际内容 | 和模式混淆 | 像当前住户 |
| 外模式 | 用户看到的局部视图 | 和页面表现混淆 | 面向不同用户 |
| 内模式 | 物理存储组织方式 | 和逻辑模式混淆 | 面向磁盘 |
| 实体 | 可区分对象 | 和联系混淆 | 例如学生 |
| 联系 | 实体之间关联 | 和关系混淆 | 例如选课 |
| 关系 | 元组集合 | 和联系混淆 | 例如一张表 |
| 元组 | 表中的一行 | 和属性混淆 | 一条记录 |
| 属性 | 表中的一列 | 和域混淆 | 一个字段 |
| 域 | 属性取值范围 | 和属性混淆 | 值的合法空间 |
| 候选码 | 最小超码 | 和主码混淆 | 可能有多个 |
| 主码 | 被选中的候选码 | 和候选码混淆 | 通常主要标识 |
| 外码 | 引用其他关系主码的属性 | 任意相同字段 | 必须有引用语义 |
| 函数依赖 | 一个属性集决定另一个属性集 | 程序计算关系 | 是数据语义约束 |
| 完全函数依赖 | 对整体依赖,不对真子集依赖 | 与传递依赖混淆 | 主要看组合码 |
| 传递依赖 | 通过中间属性间接依赖 | 与部分依赖混淆 | 主要看"绕一道" |
| 事务 | 不可分逻辑工作单元 | 与 SQL 语句混淆 | 可包含多条 SQL |
| ACID | 事务四大特性 | 只背英文缩写 | 要能解释作用 |
| 锁 | 并发控制手段 | 和事务混淆 | 锁是手段,事务是单位 |
| 索引 | 辅助查找结构 | 和数据本体混淆 | 像目录 |
| 视图 | 虚拟表 | 和基本表混淆 | 通常存定义不存数据 |
| 检查点 | 恢复起点 | 和备份混淆 | 不是完整副本 |
高频公式 / 规则 / 方法表
| 公式 / 规则 / 方法 | 表达 | 用途 | 常见错误 |
|---|---|---|---|
| 函数依赖 | X -> Y |
表示 X 决定 Y | 当作程序计算关系 |
| 属性闭包 | X^+ |
求候选码、判依赖 | 忘记反复扩展直到稳定 |
| 自反律 | 若 Y ⊆ X,则 X -> Y |
基本推理 | 只记名字不知含义 |
| 增广律 | 若 X -> Y,则 XZ -> YZ |
推导依赖 | 忘记两边都加 |
| 传递律 | 若 X -> Y 且 Y -> 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 | 记反 |
| 综合设计题 | 业务描述长 | 建模 -> 表设计 -> 约束 -> 范式 -> 索引 -> 事务 | 只答表结构 |
自测题
一、基础理解题
- 什么是数据库、DBMS、数据库系统?三者有什么区别?
- 什么是三级模式结构?两级映像的作用是什么?
- 为什么说数据库系统比文件系统更适合共享和管理数据?
- 什么是模式,什么是实例?
二、建模与关系模型题
- 某图书馆系统包含读者、图书、借阅三个核心要素。请指出哪些是实体,哪些是联系。
- 什么是候选码、主码、外码?它们之间有什么关系?
- 什么是实体完整性、参照完整性?请各举一个例子。
- 对于"学生选课"这一业务,为什么通常要建立中间表而不是把课程列表放进学生表?
三、关系理论与范式题
- 已知关系模式
R(A, B, C, D),函数依赖集为A -> B,A -> C,C -> D。求候选码,并判断最高范式。 - 什么是部分函数依赖?什么是传递函数依赖?请分别用一句例子说明。
- 为什么 3NF 能减少更新异常?
- 什么情况下一个关系模式属于 BCNF?
四、关系代数与 SQL 题
- 设有关系
Student(Sno, Sname, Dept)、SC(Sno, Cno, Grade),写出"查询成绩大于 90 分学生姓名"的关系代数表达式。 - 用 SQL 写出"查询每个院系学生人数"的语句。
- 用 SQL 写出"查询没有选修任何课程的学生姓名"的语句。
- 说明
WHERE与HAVING的区别。
五、索引与优化题
- 为什么数据库索引通常使用 B+ 树而不是普通二叉搜索树?
- 为什么索引不是建得越多越好?
- 聚簇索引和非聚簇索引有什么区别?
- 什么是选择下推?为什么它常能提升查询效率?
六、事务与恢复题
- 什么是事务?为什么转账操作必须放在同一事务中?
- 区分脏读、不可重复读、幻读。
- 什么是两段锁协议?它有什么意义?
- 为什么系统故障后常常既要 Undo 又要 Redo?
七、综合设计题
- 设计一个简化的电商订单系统数据库,至少说明核心实体、主要联系、主键、外键。
- 若系统中"下单"操作包括扣库存、生成订单、写支付流水三步,为什么应放在同一事务中?
- 如果一个系统需要支持"用户只能看自己的订单",从数据库角度可以用哪些机制辅助实现?
- 关系数据库与 NoSQL 数据库分别更适合什么场景?
八、提高题
- 为什么说规范化与索引设计有时会存在工程上的张力?
- 请用"需求 -> 建模 -> 关系模式 -> 范式 -> 索引 -> 事务"这条链,概括数据库课程的整体逻辑。
自测题答案与解析
1. 什么是数据库、DBMS、数据库系统?三者有什么区别?
数据库是有组织、可共享的数据集合;DBMS 是管理数据库的软件系统;数据库系统是数据库、DBMS、应用程序、管理员及硬件软件环境共同组成的整体。区别在于层次不同:数据库是被管理对象,DBMS 是管理工具,数据库系统是完整运行生态。
2. 什么是三级模式结构?两级映像的作用是什么?
三级模式包括外模式、模式和内模式,分别对应用户局部视图、数据库整体逻辑结构和物理存储结构。两级映像包括外模式/模式映像和模式/内模式映像,作用是支持逻辑独立性和物理独立性,使某一层变化尽量不影响其他层。
3. 为什么说数据库系统比文件系统更适合共享和管理数据?
因为数据库系统具有更强的数据结构化、共享性、统一管理能力,并能通过约束、并发控制、恢复、安全机制解决文件系统在冗余、一致性、维护和恢复方面的不足。文件系统能存,但不擅长多用户、高并发和复杂查询场景。
4. 什么是模式,什么是实例?
模式是数据库逻辑结构的定义,是"数据库长什么样"的描述;实例是某一时刻数据库中的具体数据内容,是"此刻数据库里有什么"的状态。模式相对稳定,实例随时间变化。
5. 某图书馆系统包含读者、图书、借阅三个核心要素。请指出哪些是实体,哪些是联系。
读者和图书通常是实体,因为它们是可独立存在并可唯一标识的对象;借阅通常是联系,因为它描述了读者与图书之间的交互关系。如果借阅本身带有借阅日期、归还日期、状态等属性,则在关系模型中往往会落成一张独立的借阅表。
6. 什么是候选码、主码、外码?它们之间有什么关系?
候选码是能够唯一标识元组的最小属性组;主码是从候选码中选出的主要标识;外码是一个关系中引用另一个关系主码或候选码的属性。主码一定是候选码,外码则用于建立关系之间的联系与参照完整性。
7. 什么是实体完整性、参照完整性?请各举一个例子。
实体完整性要求主码不能为空且唯一,例如学生表中的学号不能重复也不能为 NULL。参照完整性要求外码取值必须引用被参照表中存在的主码,或在允许时为空,例如选课表中的学号必须存在于学生表中,否则就是非法引用。
8. 对于"学生选课"这一业务,为什么通常要建立中间表而不是把课程列表放进学生表?
因为学生与课程通常是多对多关系。若把课程列表塞进学生表,会破坏关系模型的一格一值原则,也不利于查询、统计和维护。建立中间表 SC(学号, 课程号, 成绩) 可以清楚表达联系,并存放联系本身的属性,如成绩、选课时间等。
9. 已知关系模式 R(A, B, C, D),函数依赖集为 A -> B,A -> C,C -> D。求候选码,并判断最高范式。
先求 A^+。由 A -> B 和 A -> 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. 说明 WHERE 与 HAVING 的区别。
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, 姓名, 手机号, 地址),主键为用户IDProduct(商品ID, 商品名, 单价, 库存),主键为商品IDOrder(订单ID, 用户ID, 下单时间, 总金额, 状态),主键为订单ID,其中用户ID是外键引用UserOrderItem(订单ID, 商品ID, 数量, 成交单价),复合主键可为(订单ID, 商品ID),分别作为外键引用Order和Product
其中用户与订单是一对多,订单与商品通常是多对多,因此需要 OrderItem 作为联系表。
26. 若系统中"下单"操作包括扣库存、生成订单、写支付流水三步,为什么应放在同一事务中?
因为这三步共同构成一次完整业务。若扣了库存但订单未生成,会出现库存减少却无订单记录;若订单已生成但支付流水未写入,资金与订单状态又会不一致。把三步放入同一事务,可保证要么全部完成,要么全部回滚,维护业务一致性。
27. 如果一个系统需要支持"用户只能看自己的订单",从数据库角度可以用哪些机制辅助实现?
可从多个层面配合:
- 通过认证识别当前用户身份
- 使用角色和权限控制访问能力
- 通过视图只暴露必要列和必要结果集
- 在应用层或数据库层加上按用户 ID 的过滤条件
- 对敏感访问行为做审计
如果是课程考试,答到"授权 + 视图 + 行级过滤思想"通常已经较完整。
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;
易错点:
- 把
DISTINCT和GROUP 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 -> B,B -> C。求候选码。
分析:
先求 A^+,由 A -> B 和 B -> C 得 A^+ = {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:综合范式判断模板
题目:给出任意关系模式及依赖集,说明规范化题的一般步骤。
模板答案:
- 写出属性全集和依赖集
- 求候选码
- 判主属性和非主属性
- 检查是否有部分依赖
- 检查是否有传递依赖
- 若考 BCNF,再检查决定因素是否为超码
- 若需分解,说明每步分解消除的问题
训练总结:
- 范式题不是先背定义,而是先抓依赖结构
- 设计题不是先列字段,而是先看事实由谁决定
- 一旦出现联系属性,就要警惕"联系表是否应该独立"
附录三:事务、恢复与索引专题加练
训练 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) 往往更容易支持基于 A、A+B 的查询,而不容易直接支持只基于 B 的查询?
分析:
因为组合索引内部的排序首先由 A 决定,再在 A 相同的范围内考虑 B,最后才是 C。所以只有从最左前缀开始利用,索引结构的有序性才真正能帮助快速定位。
训练 11:索引失效思维
题目:对索引列做函数运算为什么常不利于索引使用?
分析:
索引组织的是原始列值的有序结构。若查询条件写成 YEAR(create_time) = 2026 之类的形式,数据库可能无法直接利用原始索引顺序定位范围,而需要对每条记录先计算函数结果,导致索引优势下降。
训练 12:综合原理题模板
题目:为什么数据库课程里"索引、事务、恢复"会被视为三大关键原理板块?
分析模板:
- 索引解决访问效率问题
- 事务解决多步操作的一致性问题
- 恢复解决故障后的可靠性问题
三者分别对应性能、正确性、可靠性,是数据库从"能存数据"升级到"能稳定服务业务"的核心支柱。
附录总结:
如果你能把这三组专题训练真正做熟,那么数据库试卷里最能拉开差距的"SQL + 范式 + 事务恢复"三大板块,你就已经建立了稳定的解题骨架。
最后总结
数据库这门课最容易给人的错觉,是它好像什么都讲一点,所以很散。真正把它学透以后你会发现,它其实一直在回答同一个问题:
数据怎样才能被长期、正确、高效、共享地使用。
围绕这个问题,整门课自然展开出一条清晰因果链:
- 先用建模回答"现实世界怎么抽象"
- 再用关系模型回答"抽象结果怎么变成表"
- 再用 SQL 和关系代数回答"这些表怎么查、怎么改"
- 再用范式回答"这些表怎样设计才不乱"
- 再用索引和优化回答"怎样查得快"
- 再用事务与并发控制回答"多人同时操作时怎样尽量不乱"
- 再用日志与恢复回答"出故障时怎样救回来"
- 最后用安全、授权、分布式与 NoSQL 回答"系统真正上线后如何控制和扩展"
如果你读完这篇文档,能真正记住下面五句话,这门课的主干就已经抓住了:
- 数据库不是在背表和语法,而是在研究数据如何被可靠管理。
- 关系设计的核心不是"拆几张表",而是"谁决定谁"。
- SQL 的核心不是关键字,而是查询逻辑。
- 索引、事务、恢复分别对应性能、正确性、可靠性三大支柱。
- 高分的关键不是记得多,而是能按因果顺序把答案讲完整。
最后给你一个真正实用的复习标准:
- 如果你能看着一道题,先判断它属于哪个模块、哪个题型、该套哪个模板,再开始作答,说明你已经从"零碎记忆"进入"结构化掌握"了。
- 如果你还能把"为什么"写出来,而不是只写"是什么",那你不仅能应付考试,也已经具备了数据库学习最重要的底层理解。
把这篇文档至少完整过两遍:第一遍建框架,第二遍抓模板和易错点,第三遍结合题目输出。做到这里,数据库这门课从"看着很多"变成"其实有主线",从"背不住"变成"能组织",从"会一点"变成"能拿分",就是很自然的结果。