简介:《数据库原理及技术》(钱学忠)配套习题答案是面向高校数据库课程学习者与备考人员的答案汇编,对应教材中的概念理解、SQL 编写和关系模型分析等练习,用于课后自查、期末复习与查漏补缺。内容围绕数据库设计中的需求分析、E-R 模型与关系模式转换,SQL 查询与更新操作,关系代数、关系演算及范式规范化,以及 DBMS 的并发控制、恢复与权限管理等章节展开,可帮助读者对照题目梳理解题思路,理解规范化设计与查询优化的判断依据。压缩包约 16.69MB,文件总数与类型明细暂未提供,下载后可解压查看具体内容。目前已有 693 人学习,适合需要同步练习、核对答案或准备课程考试的学习者使用。
1. 对答案对到怀疑自己,问题多半不在你
带课程设计那阵子,被问得最多的一句话是:数据库原理及技术的课后习题,我写完不知道对不对,网上搜到的答案还互相打架。这份配套答案资源解决的就是这件事——它按章节给出关系代数、SQL 语句、函数依赖与范式分解、事务调度与恢复的参考解,覆盖从关系模型到并发控制的整条主线。适合正在跟这门课、写完题却没人批改的人,也适合隔了几年回头补基础、想快速验证自己判断的在职开发者。真正费时间的从来不是把题做出来,而是做完之后没法确认对错,一份能逐题对照的答案,省下的是反复自我怀疑的时间。下面按「题型怎么分布 → SQL 怎么复现 → 范式事务怎么判 → 哪些地方必翻车」的顺序拆开讲。
2. 先摸清题型地图:六类题各自的权重和先后顺序
拿到一份答案直接从头翻,是最低效的用法。教材习题的题型分布差异很大:概念题占分不多但耗时最长,SQL 题看着简单但答案写法不唯一,范式题和事务题一旦理解偏了,后面整章都会连环错。先把题型分清,再决定哪类题值得逐字对照答案、哪类题只看思路即可。
2.1 六类题型的依赖关系决定刷题顺序
按常见教材的组织方式,习题大体落在六类上:概念与简答、关系代数、SQL、ER 模型与数据库设计、函数依赖与范式、事务并发与恢复。它们之间存在明确的先后依赖,前一类没弄透,后一类只能靠背。
| 题型 | 依赖的前置 | 答案该怎么用 | 建议投入 |
|---|---|---|---|
| 概念简答 | 无 | 只对关键词,不对表述 | 低 |
| 关系代数 | 关系模型基础 | 逐个符号核对,尤其是除运算 | 中 |
| SQL | 关系代数 | 必须本地跑通,答案只作对照 | 高 |
| ER 设计与转换 | 关系模型 | 对实体、联系、基数三处 | 中 |
| 函数依赖与范式 | 关系代数基础 | 对推导过程,不对最终分解 | 高 |
| 事务与恢复 | 并发基础概念 | 对判定结论,调度图自己画一遍 | 高 |
依赖关系里最关键的一条是:关系代数到 SQL 是单向的。SQL 写不出来,八成是关系代数那一步没想清楚「先选后连还是先连后选」。反过来,SQL 写对了但关系代数写错,说明你只是记住了模板。所以碰到 SQL 题反复出错,别急着刷更多 SQL,回头把对应章节的关系代数题重做一遍,效率高得多。
另一条是函数依赖到范式的单向依赖。候选码求错,后面 2NF、3NF、BCNF 的判定全错,而且错得很隐蔽——分解出来的关系看起来「挺合理」,实际上候选码就漏了属性。这类题必须按固定流程走,凭感觉判断迟早翻车。
事务那一块相对独立,但它和「数据库设计」共用一套思维方式:都是先列出所有约束,再验证是否满足。调度可串行化判定就是画优先图看有没有环,和范式判定里验证无损连接是同一类动作。
2.2 用题号索引表把答案和教材对齐
答案资源最大的问题是页码和教材对不上,尤其是不同印次。稳妥的做法是自己建一张索引表,四列就够:题号、教材小节、知识点标签、我的错因。前两列照着教材抄,后两列边做边填。
具体操作按这四步走:
- 先翻教材目录,把每一章的习题页码范围记下来,形成「章 → 题号区间」的粗映射。
- 做题时给每道题打一个知识点标签,标签控制在十个以内,比如「SQL-连接」「范式-候选码」「事务-隔离级别」,标签太细反而没法统计。
- 错题只记错因,不抄整道题。错因写一句人话,比如「NOT IN 遇到 NULL 返回空」,比抄一遍题干有用。
- 每做完一章,把这一章的标签统计一次,错得最多的标签就是下一章开始前要回看的小节。
这套动作的价值在于,它把「刷题」变成了「定位」。答案本身只是参照物,真正让你进步的是那张错因表——它告诉你知识缺口在哪个标签上,而不是笼统的「我这章学得不好」。
2.3 一份可以直接照抄的三轮节奏
时间紧的时候不要按章节平均用力,按轮次推进更快。第一轮只做关系代数和 SQL,目标是能独立写出正确语句,答案只用来对最终结果;第二轮做范式与 ER 设计,目标是推导过程能自圆其说,答案用来对中间步骤;第三轮做事务与恢复,目标是判定结论正确,答案用来对优先图和日志顺序。
每一轮的时长建议按 4:3:3 分配,第一轮最长,因为关系代数和 SQL 是后面所有内容的地基。第一轮里如果某道 SQL 题你想了十五分钟还没思路,直接看答案的思路部分,然后合上答案自己重写一遍——注意是重写,不是抄写。抄写一遍带来的熟悉感是假的,合上答案重写才是真的。
三轮结束之后,再回头把错因表里出现两次以上的标签集中处理一遍。这类反复出错的点通常只有三五个,处理掉它们,整门课的题感会明显不一样。
3. SQL 题别只看答案:本地建库复现的完整链路
SQL 题的参考答案有个天然缺陷:同一道题可能有五六种写法,答案给的往往是最「教科书」的那一种,未必是最容易理解的。更麻烦的是,答案里的语句对错你根本看不出来——少一个 GROUP BY、多一个 JOIN 条件,肉眼很难发现。唯一可靠的办法是把教材里的示例表在本地建起来,把答案敲进去跑一遍,看结果集和预期是否一致。
3.1 建库建表:把答案里的约束逐条落地
教材里的表结构描述通常是文字加表格,落到 SQL 上要做三件事:确定主键(也就是候选码里选出来的那个)、确定外键和参照完整性、把值域约束写成 CHECK。以最常见的「学生-课程-选课」三表为例:
-- 本地复现用的库,字符集按中文环境选 CREATE DATABASE IF NOT EXISTS db_course DEFAULT CHARACTER SET utf8mb4; USE db_course; CREATE TABLE student ( sno CHAR(8) PRIMARY KEY, -- 学号,教材里写的候选码 sname VARCHAR(20) NOT NULL, ssex CHAR(2) CHECK (ssex IN ('男','女')), sage TINYINT CHECK (sage BETWEEN 15 AND 45), sdept VARCHAR(20) ); CREATE TABLE course ( cno CHAR(4) PRIMARY KEY, cname VARCHAR(40) NOT NULL UNIQUE, credit TINYINT CHECK (credit BETWEEN 1 AND 10), cpno CHAR(4), -- 先修课,自引用外键 CONSTRAINT fk_course_cpno FOREIGN KEY (cpno) REFERENCES course(cno) ); CREATE TABLE sc ( sno CHAR(8), cno CHAR(4), grade DECIMAL(5,1) CHECK (grade BETWEEN 0 AND 100), PRIMARY KEY (sno, cno), -- 复合主键对应教材里的候选码组合 CONSTRAINT fk_sc_sno FOREIGN KEY (sno) REFERENCES student(sno) ON DELETE CASCADE, CONSTRAINT fk_sc_cno FOREIGN KEY (cno) REFERENCES course(cno) );几个参数要说明白。CHAR(8)和CHAR(4)是定长,学号课程号这类编码用定长比变长查询更快,也避免前后空格带来的比较问题。DECIMAL(5,1)而不是FLOAT,是因为成绩要精确比较,浮点数在grade = 90这种条件上会出现匹配不到的情况。ON DELETE CASCADE只加在选课表对学生的引用上,删学生连带删选课记录符合业务直觉;课程那边不加,是因为删课程不该顺手抹掉历史成绩。
执行顺序也有讲究。course表有自引用外键,插入数据时先修课必须先存在,否则直接报 1452 外键约束错误。教材答案里通常不写数据插入顺序,这是复现时第一个卡点。
3.2 多表连接与子查询:用两三种写法互相验证
SQL 答案不唯一,验证思路就是用不同写法跑同一道题,结果集一致才说明你真的懂了。最典型的是「查询选修了全部课程的学生」——关系代数里的除运算,SQL 里对应双重 NOT EXISTS:
-- 写法一:双重 NOT EXISTS,对应关系代数的除运算 SELECT s.sname FROM student s WHERE NOT EXISTS ( SELECT 1 FROM course c WHERE NOT EXISTS ( SELECT 1 FROM sc WHERE sc.sno = s.sno AND sc.cno = c.cno ) );这四层嵌套读起来绕,逻辑其实很直白:对每个学生,检查「是否存在一门课他没选」,如果不存在,就入选。内层SELECT 1不关心查出什么,只关心有没有行,写成SELECT *也对,但1更省事。
同一道题用聚合写法验证:
-- 写法二:计数比对,用总课程数做基准 SELECT s.sname FROM student s JOIN sc ON sc.sno = s.sno GROUP BY s.sno, s.sname HAVING COUNT(DISTINCT sc.cno) = (SELECT COUNT(*) FROM course);两种写法结果必须一致。不一致的时候别急着改 SQL,先看数据:sc表里有没有重复的选课记录,course表里有没有课程一条选课记录都没有。这两种数据状况会让写法二和写法一出现分歧,教材上的示例数据往往很干净,自己造数据时就容易踩到。
还有一种常见错误是拿NOT IN去替代NOT EXISTS。这两者在子查询结果含 NULL 时行为完全不同,NOT IN会整体返回空集,而NOT EXISTS正常返回。这个坑后面单独讲。
3.3 视图、索引、权限:三类容易被跳过的题
教材后半部分常出现视图、索引、权限的习题,很多人直接对着答案抄一遍就过。这三类恰恰是工作中最常用的,值得花十分钟跑通。
-- 视图:把三表连接固化,注意 LEFT JOIN 保证没选课的学生也在结果里 CREATE VIEW v_student_sc AS SELECT s.sno, s.sname, c.cname, sc.grade FROM student s LEFT JOIN sc ON sc.sno = s.sno LEFT JOIN course c ON sc.cno = c.cno; -- 索引:外键列和经常出现在 WHERE 里的列优先建 CREATE INDEX idx_sc_cno ON sc(cno); CREATE INDEX idx_student_dept ON student(sdept); -- 权限:只授视图的查询权,不碰基表 CREATE USER 'u_read'@'localhost' IDENTIFIED BY 'Read_2024'; GRANT SELECT ON db_course.v_student_sc TO 'u_read'@'localhost';视图这里的关键是LEFT JOIN的顺序:学生左连接选课,没选课的学生会保留一行,grade为 NULL。如果写成内连接,视图就丢人,教材答案里经常用内连接,是因为它的示例数据里每个学生都选了课——这个隐含前提一旦被打破,答案就不成立了。
索引部分要注意,索引不是越多越好。sc表上已经有(sno, cno)的复合主键索引,最左前缀能覆盖按sno查的场景,所以只需要再补一个cno单列索引给「按课程查学生」用。教材答案里有时会给sno单独建一个索引,那是冗余的,跑了 EXPLAIN 就能看出来。
4. 范式与事务:两类最容易「答案对、理解错」的题
这两块的共同点是:答案给你一个结论,你不验证也能抄对,但换个题面立刻不会。范式题的结论是一组分解后的关系模式,事务题的结论是一个「是/否可串行化」的判断,两者的正确性都依赖中间推导过程,而答案通常只写结论。
4.1 候选码怎么求:四类属性分类法
求候选码的流程是固定的,别凭直觉。先把所有属性按在函数依赖里的出现位置分成四类:
| 属性类别 | 定义 | 处理方式 |
|---|---|---|
| L 类 | 只出现在依赖左边 | 一定属于某个候选码 |
| R 类 | 只出现在依赖右边 | 一定不属于任何候选码 |
| LR 类 | 两边都出现 | 可能属于,需要试 |
| N 类 | 两边都不出现 | 一定属于每个候选码 |
L 类和 N 类合起来先求闭包,如果闭包等于全部属性,那它就是一个候选码,结束;如果不等于,就往里逐个加 LR 类属性再求闭包,直到覆盖全属性。R 类属性直接排除。
举个具体例子。设 R(A, B, C, D),函数依赖集 F = {A→B, B→C, C→D}。A 只在左边,属 L 类;D 只在右边,属 R 类;B、C 两边都有,属 LR 类;没有 N 类。先算 A 的闭包:A→B→C→D,闭包是 ABCD,等于全集,所以 A 是唯一候选码。
换个依赖集:F = {AB→C, C→D, D→A}。这里 A、B 出现在左边但在右边没出现(A 在 D→A 右边出现过,所以 A 是 LR;B 只在左边,是 L 类);C、D 都是 LR。先算 B 的闭包只有 B 自己,不够,逐个加属性:加 A,AB 的闭包是 ABCD,覆盖全集,AB 是候选码;加 C,BC→A(由 D→A 和 C→D 推得),BCA 再推 D,覆盖全集,BC 也是候选码;加 D,D→A,DA→B,覆盖全集,BD 也是候选码。所以候选码有三个:AB、BC、BD。
这一步最容易翻车的地方是漏掉 N 类属性。N 类属性不出现在任何依赖里,意味着它无法被其他属性推出,只能由自己提供,所以必须出现在每个候选码中。教材答案里偶尔会漏,自己推的时候一定要把 N 类单独标出来。
4.2 无损连接与保持依赖:两个判据别混用
分解是否「好」,教材给了两个独立判据:无损连接和保持函数依赖。这两个判据是并列关系,不是替代关系。很多人把无损连接当成唯一标准,结果分解方案看着能还原原表,但函数依赖全丢了,答完还觉得自己做对了。
无损连接判定,两个子关系的情况有个直接定理:R 分解成 R1 和 R2,如果 R1∩R2 → R1−R2 或 R1∩R2 → R2−R1 成立,则分解是无损的。也就是说,公共属性能决定其中一个子关系的全部属性即可。
保持依赖的判定更麻烦一些,标准做法是把 F 投影到每个子关系上,得到 Fi,然后看这些 Fi 的并集能不能推出原来的每一条依赖。能推出就是保持的,推不出就不保持。
来看一个经典的两难:R(A, B, C),F = {AB→C, C→A}。分解成 R1(A, C) 和 R2(B, C)。公共属性是 C,C→A 成立,A 在 R1 里,所以是无损的。但 AB→C 这条依赖在 R1 里推不出(R1 只有 C→A),在 R2 里也推不出(R2 没有任何非平凡依赖),所以不保持依赖。无损但不保持——这个结论必须两个判据都跑一遍才能得到。
三个及以上子关系的无损判定要用表格法(Chase 算法):画一张 k 行 n 列的表,第 i 行对应第 i 个子关系,列对应全部属性,子关系里有的属性填 aj,没有的填 bij;然后按函数依赖反复修改,直到出现一行全是 a,或者表不再变化。这个过程手算容易乱,建议用铅笔,按依赖一条一条改,改完一条检查一次。
4.3 事务与隔离级别:把书面答案跑成可观察现象
事务题的参考答案一般是文字描述:某调度是否冲突可串行化、某隔离级别下会不会出现脏读。这些结论光看答案记不住,开两个会话跑一遍就忘不掉了。
-- 会话 A:设为读已提交,开启事务 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; START TRANSACTION; SELECT balance FROM account WHERE id = 1; -- 第一次读,假设得到 1000-- 会话 B:另一个连接里改数据并提交 START TRANSACTION; UPDATE account SET balance = 800 WHERE id = 1; COMMIT;-- 回到会话 A:再读一次 SELECT balance FROM account WHERE id = 1; -- 读已提交下得到 800 COMMIT;同样的流程把隔离级别换成REPEATABLE READ,第二次读到的还是 1000,这就是可重复读和读已提交的差别。想观察幻读,把操作换成范围查询加插入:会话 A 先SELECT COUNT(*) FROM sc WHERE grade > 90,会话 B 插入一条 95 分的记录并提交,会话 A 再查一次,读已提交下数量变了,可重复读下不变。
参数上要注意SET SESSION和SET GLOBAL的区别,前者只影响当前连接,实验时用 SESSION,别改全局,不然会影响同实例上的其他库。另外 MySQL 默认隔离级别是 REPEATABLE READ,如果你不显式设置,第一次实验看到的就不是教材上说的读已提交行为,很容易误判成「答案错了」。
调度可串行化的判定则是画优先图:调度里每对冲突操作(不同事务、同一数据、至少一个是写)画一条有向边,图无环就是冲突可串行化。教材答案给的是「是/否」,但考试和面试要的是过程,边画漏一条结论就反了。常见的漏边是读-写冲突,很多人只记得写-写冲突。
5. 避坑排查:五条最容易翻车的地方
这些坑的共同特征是:本地一跑就报错或者结果不对,但对着答案看又找不出问题在哪。按「现象 → 原因 → 解决」记下来,下次遇到直接查表。
5.1 SQL 与建库环节的三条
现象一:NOT IN子查询返回空结果,明明数据是存在的。原因是子查询结果集里含 NULL,NOT IN的语义展开成x <> v1 AND x <> v2 AND ...,其中任一项和 NULL 比较结果是 UNKNOWN,整个条件不为真,所以一行都返回不了。sc表里成绩为 NULL 的记录(选了课没出分)就是最常见的触发源。解决办法有两个:改成NOT EXISTS相关子查询,或者在NOT IN的子查询里加WHERE grade IS NOT NULL把 NULL 排掉。前者更稳,后者要记得每处都加。
现象二:建表时外键约束创建失败,报 1215 或 1452。1215 是父表不存在或父表对应列没有索引,通常发生在建表顺序不对——先建了sc再建student。1452 是插入数据时引用的父记录不存在,最典型的是course表的自引用外键:先修课那一行还没插进去,引用它的行就先插了。解决办法是调整建表和插入顺序,父表先于子表,自引用表按先修关系从底层往上插。实在要临时绕过,可以SET FOREIGN_KEY_CHECKS = 0,但导完数据必须记得改回 1,不然约束等于没建。
现象三:GROUP BY查询报 1055,提示某列不在 GROUP BY 中。这是ONLY_FULL_GROUP_BY模式在起作用,要求 SELECT 列表里的非聚合列必须出现在 GROUP BY 里。教材答案里常见的写法是SELECT sno, sname, AVG(grade) ... GROUP BY sno,在旧版本能跑,在新版本直接报错。原因是sname没有函数依赖于sno(从 SQL 解析器的角度看不出来)。解决办法是把sname也加进 GROUP BY,或者用ANY_VALUE(sname)包一层,再或者确认sno是主键后改写为子查询先聚合再连接。推荐最后一种,语义最清楚。
5.2 概念判定环节的两条
现象四:范式分解题答案看着对,但一验证发现函数依赖丢了。原因是只验证了无损连接,没验证保持依赖。这两个判据相互独立,一个分解可以无损但不保持依赖,也可以保持依赖但有损。做题时把两个判据都跑一遍,形成固定动作。具体的验证方法在 4.2 节写过,投影到子关系再取并集推原依赖,推不出来就是不保持。
现象五:候选码求出来只有一个,但答案给了三个。大概率是漏了 LR 类属性的组合。L 类和 N 类合起来闭包覆盖全集时,只得到一个候选码,但 LR 类属性两两组合、三三组合也可能是候选码,必须逐个试闭包。另一种可能是把某个 R 类属性误当成了 LR 类——比如属性只在依赖右边出现,却被算进了候选码,导致多算出错误的组合。解决办法是分类那一步慢一点,把每个属性在依赖集里出现的位置一个个标出来,标完再动手算。
6. 拿答案反向出题:一套三十分钟的自测流程
把答案从头到尾对一遍,只能验证「这道题我做过」。真正能验证掌握程度的,是拿答案反向出题:把答案里的结论遮住,只看题干,限时写出结果,再对照。这个过程我一般控制在三十分钟,分三段。
第一段十分钟,专挑错因表里出现两次以上的标签,每个标签从答案里找两道同类题,遮住答案重做。做对了划掉,做错了在错因后面加一个正字笔画——同一道题错三次以上,说明不是粗心,是这个知识点真的没通,得回教材重读那一节。
第二段十分钟,做交叉验证。SQL 题用两种写法各跑一遍看结果是否一致,范式题把分解后的关系模式代回原数据看能不能还原,事务题把调度画成优先图看有没有环。这一步的目的是把「结论正确」升级成「过程自洽」。
第三段十分钟,把当次的错题按知识点聚类,输出下一轮的复习顺序。这一步可以交给脚本,省得手算:
# wrong_book.py —— 错题按知识点聚类,错得多的排前面 from collections import Counter # 每条记录:(题号, 知识点标签, 一句话错因) records = [ ("2-3", "关系代数", "除运算的选取顺序写反"), ("3-7", "SQL", "NOT IN 子查询含 NULL"), ("3-9", "SQL", "LEFT JOIN 条件写进 WHERE 变成内连接"), ("4-2", "范式", "候选码漏掉 N 类属性"), ("4-5", "范式", "只验无损没验保持依赖"), ("5-1", "事务", "隔离级别默认值记错"), ("5-4", "事务", "优先图漏画读-写冲突边"), ] counter = Counter(tag for _, tag, _ in records) # 次数相同时按基础依赖顺序兜底,SQL 和范式优先 fallback = ["SQL", "范式", "事务", "关系代数"] for tag in sorted(counter, key=lambda t: (-counter[t], fallback.index(t))): nums = [q for q, g, _ in records if g == tag] print(f"{tag}({counter[tag]} 题):{', '.join(nums)}")这段脚本的逻辑很简单:Counter统计每个标签出现的次数,sorted的 key 用负数次数实现降序,次数相同时用fallback列表里的位置决定先后。fallback的顺序不是随便排的,SQL 和范式是后面所有内容的地基,错在这两块要优先处理。运行之后输出的就是下一轮的复习清单,直接照着做。
有一点要提醒:这个脚本只统计「记录下来的错题」,不记录的题等于没做。我早期的习惯是只在脑子里记「这题我错了」,结果一周后完全想不起来错在哪。后来改成做完一章立刻录进列表,哪怕只写三个字,也比事后回忆强。错题本的价值不在数量,在于它能不能让你在考前半小时内定位到最该看的那五道题。
从那以后我每次带人过这门课,都强制走一遍「遮答案重做 + 交叉验证 + 聚类排期」这三段,哪怕时间只够做第一段。答案资源本身只是参照物,把它用成自测工具,才算真的把这份材料吃透。希望帮到你。
本文还有配套的精品资源,点击获取