☰
数据库原理课后习题答案实战:拆解成可运行SQL与范式判定
2026/10/12 1:01:05 网站建设 项目流程

简介:这份《数据库原理及应用课后习题答案》文档,面向正在学习数据库原理课程的高校学生、自考与考研备考者,系统梳理了数据库基本概念、数据模型、数据独立性、实体关系等核心知识点,并对课后习题给出详细解答。资源为一份doc格式文档,共1个文件,整份大小约752KB,内容按章编排,便于集中查阅与打印。当前已有244人学习下载。文档不仅包含名词解释与判断题答案,还给出简答题的要点归纳,例如数据管理技术的发展阶段、文件系统与数据库系统的区别联系、数据库系统特点等,可直接用于期末复习、考试冲刺或教案参考。

1. 一份「数据库原理及应用课后习题答案.doc」,不该只是考前背的打印稿

数据库原理及应用课后习题答案.doc——如果你只是在期末前翻一遍,它顶多算个打印稿;但如果你按题型把它拆成 SQL、范式、事务和数据库建模四类练习,它就是一条能直接上机的复习路径。我带课程设计的经验是:这类答案文档普遍缺少建表语句、运行环境和方言适配,能把“答案”变成“能跑的 SQL”的同学很少。这篇笔记就是给正在备考、补基础或者做课程设计的人,讲清楚一份答案文档该怎么拆、怎么练、在哪里翻车。

2. 把习题答案拆成可执行的 SQL:从建表到增删改查的一整套动作

2.1 先把答案文档按题型切分,补齐最容易被漏掉的建表脚本

打开一份答案文档,不要从第一道题开始背。先看一眼目录,按四种题型把题号标出来:SQL 增删改查、关系模型与范式、事务与并发控制、数据库设计(ER 图转表)。这个动作花不了十分钟,但能决定你后面练习的方向。我的习惯是把题号抄进一张表格,用四列记录“题号、考点、答案要点、需要补齐的东西”。大多数答案文档在 SQL 章节直接给 SELECT 结果,不给建表脚本,而建表恰恰是能在本地跑通所有查询的第一前提。

以最常见的学生-课程选课模型为例。

CREATE DATABASE IF NOT EXISTS school CHARACTER SET utf8mb4; USE school; CREATE TABLE student ( sno VARCHAR(10) PRIMARY KEY, sname VARCHAR(50) NOT NULL, sex CHAR(1) DEFAULT '男', age TINYINT ); CREATE TABLE course ( cno VARCHAR(10) PRIMARY KEY, cname VARCHAR(50) NOT NULL, credit DECIMAL(3,1) ); CREATE TABLE sc ( sno VARCHAR(10), cno VARCHAR(10), score DECIMAL(5,2), PRIMARY KEY (sno, cno), FOREIGN KEY (sno) REFERENCES student(sno), FOREIGN KEY (cno) REFERENCES course(cno) );

这段脚本是答案文档里最常缺的部分。VARCHAR(10) 要给足长度,避免学号和课程编号在 utf8mb4 环境下因为截断报错;DECIMAL(3,1) 表示最大三位数、一位小数,用来存学分,DECIMAL(5,2) 用来存百分制成绩,不要写成 FLOAT,否则课后题里要求的精度判断题会翻车。表名 sc 不是拼写错误,而是 student-course 关联表的惯例命名,大多数教材的习题也沿用这个命名。

有了这三张表,后续的插入和查询才有地方跑。

INSERT INTO student (sno, sname, sex, age) VALUES ('001', '张三', '男', 20), ('002', '李四', '女', 21), ('003', '王五', '男', 19); INSERT INTO course (cno, cname, credit) VALUES ('C01', '数据库原理', 4.0), ('C02', '数据结构', 3.5); INSERT INTO sc (sno, cno, score) VALUES ('001', 'C01', 88.5), ('001', 'C02', 92.0), ('002', 'C01', 75.0), ('003', 'C02', NULL);

重点看第三条选课记录,score 是 NULL,它代表“缺考或未录入”,和数值 0 是两回事。这个区别在后面的聚合函数题里会直接影响答案对不对。

2.2 用最典型的三道题把增删改查落到数据库里

课后习题里频率最高的题目,基本逃不出下面三类:查询某个学生的选课信息、统计每门课的平均分、更新或删除异常数据。对应到答案文档,通常只给出 SELECT 结果,你需要自己推出背后的 SQL。

查询“选了 C02 且成绩大于 80 的学生姓名”:

SELECT s.sname FROM student s JOIN sc ON s.sno = sc.sno WHERE sc.cno = 'C02' AND sc.score > 80;

这里的 JOIN 是 INNER JOIN,省略 INNER 是标准写法。如果直接在 student 和 sc 之间不加 JOIN 条件写逗号连接,会得到笛卡尔积,这是新手最常见的问题。WHERE 里对 score 做过滤时,NULL 值会被自动排除,因为 NULL 和任何数值比较的结果都不是 TRUE。

统计每门课的平均分,并只显示平均分不低于 85 分的课程:

SELECT c.cno, c.cname, AVG(sc.score) AS avg_score FROM course c JOIN sc ON c.cno = sc.cno GROUP BY c.cno, c.cname HAVING AVG(sc.score) >= 85 ORDER BY avg_score DESC;

这段答案里最值得记住的是 GROUP BY 和 HAVING 的关系。GROUP BY 的分组键必须出现在 SELECT 列表中,或者被聚合函数包裹;WHERE 在分组之前过滤行,HAVING 在分组之后过滤组,所以成绩过滤条件写在 WHERE,平均分过滤条件写在 HAVING。AVG(sc.score) 会忽略 NULL,因此 C02 带着一条 NULL 成绩仍然能算出平均分。如果换成 AVG(IFNULL(sc.score, 0)),结果就完全不同,出题人常在这里挖坑。

更新和删除题更简单,但要注意外键约束的影响:

UPDATE sc SET score = score + 2 WHERE sno = '001' AND cno = 'C01'; DELETE FROM sc WHERE score IS NULL;

UPDATE 在成绩上加 2,会改变原有精度,比如 88.5 变成 90.5;DELETE 用 IS NULL 来删除缺考记录,不能用 = NULL。这一步做完,如果答案文档里还有关联表的数据删除题,你需要先删 sc、再删 course,否则外键约束会拦住你。

提示:在本地练习时,先把“建表 + 插入 + 查询”三段脚本分别存成三个 .sql 文件,方便后面一条条对上答案文档的题号,也避免把建表语句反复粘贴进命令行。

2.3 方言适配与本地验证:MySQL、PostgreSQL、SQLite 的差异

一份答案文档里的 SQL 往往默认是某一种教材的“标准 SQL”,但你在自己机器上跑题时会发现,MySQL、PostgreSQL、SQLite 对同一个语义的写法略有不同。最典型的有三处:取前几条记录、字符串拼接、自增主键定义。

功能MySQLPostgreSQLSQLite
取前 N 条LIMIT NLIMIT N 或 FETCH FIRST N ROWSLIMIT N
字符串拼接CONCAT(a, b)a || ba || b
自增列AUTO_INCREMENTGENERATED ALWAYS AS IDENTITYAUTOINCREMENT
事务默认行为autocommit=ONautocommit=ONautocommit=OFF

如果把答案抄成 Oracle 教材里的 ROWNUM 写法,那在上述三种数据库里都不能直接跑。应对方法很简单:在本地准备一个 SQLite 环境,先用它把题目逻辑跑通,然后再把答案复制到 MySQL 或 PostgreSQL 里做一遍方言修改。

我一般会写一个极简的 Python 验证脚本,用 SQLite 执行答案文档里的 SQL 片段,快速确认结果是否和文档一致。

import sqlite3 conn = sqlite3.connect("school.db") cur = conn.cursor() cur.executescript( open("build_school.sql", encoding="utf-8").read() ) cur.execute( "SELECT cno, AVG(score) FROM sc GROUP BY cno" ) for row in cur.fetchall(): print(row) conn.commit() conn.close()

executescript 会按分号拆分多条 SQL 语句,适合一口气把建表和插入跑完。conn.commit 在 SQLite 里作用有限,因为 SQLite 默认对写操作直接落盘,但你保留它是好习惯,等换到 MySQL 和 PostgreSQL 时,这个习惯能帮你避免“事务不提交导致锁等待”的问题。注意打开 SQL 文件时要显式指定 encoding="utf-8",否则答案文档复制出来的中文建表语句在 Windows 默认编码下会变成乱码,这是一个非常容易忽略的环境坑。

3. 关系模型与范式答案:候选码、无损连接、范式的可计算套路

3.1 候选码与属性闭包:把答案翻译成一套固定算法

课后题里“求关系模式 R(U,F) 的候选码”是理论题重灾区。答案文档常常直接写“候选码是 AB”,但没有计算路径,换一组属性你就不会了。要解决这个问题,第一步就是实现属性闭包算法。闭包的定义是:由已知函数依赖推理得到的所有属性集合。求候选码就变成求“哪些属性组合的闭包能覆盖全部属性”。

def attribute_closure(attrs, fds): closure = set(attrs) changed = True while changed: changed = False for lhs, rhs in fds: if set(lhs).issubset(closure) and not set(rhs).issubset(closure): closure |= set(rhs) changed = True return closure fds = [("A", "B"), ("AC", "D")] print(attribute_closure("A", fds)) print(attribute_closure("AC", fds))

这里的 fds 用元组列表表示函数依赖,lhs 是左侧属性、rhs 是右侧属性。算法核心是不断扫描所有依赖,只要左侧属性已经在闭包里,就把右侧属性并进来,直到一轮扫描没有新增为止。输出会证明:A 的闭包只有 {A,B},AC 的闭包是 {A,C,B,D},所以在 R(A,B,C,D) 中 AC 才是候选码。这个脚本把闭包计算从“背答案”变成了机械步骤,考试时随手就能验算。

候选码的固定套路分三步。第一步,把函数依赖右侧出现过的属性和没出现过的属性分开;第二步,先试只出现在左侧或没出现在任何依赖里的属性,若闭包覆盖全集则它就是唯一候选码;第三步,如果第二步不成立,再往这个属性组合里逐个加入左右两侧都出现过的属性,每次计算闭包。答案文档里多数候选码题,靠这三步就能自检。

3.2 范式判定:从 1NF 推进到 BCNF,一条一条看函数依赖

范式题背结论最多,也最容易错。原因是范式判定按定义逐步推进,你只需要依次检查“是否存在部分依赖”“是否存在传递依赖”“所有函数依赖左侧是否都含候选码”。

有一条通用规则:先求候选码,再看非主属性,最后看每个函数依赖的左侧。举个典型例子,关系 R(A,B,C,D),函数依赖集合 F={AB->C, C->D},候选码是 AB,因为 AB 的闭包等于全集。非主属性是 C 和 D。这里 C 完全依赖于 AB,但 D 只依赖于 C,而 C 又依赖于 AB,所以 D 对候选码 AB 存在传递依赖,关系达到 2NF 但达不到 3NF。答案文档如果只说“不是 3NF”,你写这一步推导就能拿满分。

我在对答案时会把每个关系画成一张小表,列属性名,行写函数依赖。

关系函数依赖集合候选码范式判定
R1(A,B,C)A->B, A->CABCNF
R2(A,B,C)AB->C, C->BAB3NF,非 BCNF
R3(A,B,C,D)AB->C, C->DAB2NF,非 3NF

R2 比较容易被忽略:候选码是 AB,主属性是 A 和 B,C->B 左侧是 C 不含候选码 AB,所以不满足 BCNF,但右侧 B 是主属性,不构成传递依赖,因此它仍然是 3NF。这层区分是答案文档里的高频考点,也是阅卷老师的给分点。

3.3 无损连接判定:手工表格法与答案结论对不上时怎么办

无损连接题的答案是“有损”或“无损”,但很多答案文档只有结论,没有过程。如果你背结论,考试时题目换一张表就只能靠猜。我一般直接用表格法。

分解关系 R(A,B,C),函数依赖 F={A->B, A->C},分解为 R1(A,B) 和 R2(A,C),判断是否无损。先构造初始表,每个分解模式占一行,不属于该模式的属性填 b 加下标,属于的填 a 加下标:

分解模式ABC
R1a1a2b13
R2a1b22a3

对每个函数依赖,看左侧属性是否在每一行有相同值。A->B:两行 A 相同,把 R2 的 b22 改成 a2;A->C:两行 A 相同,把 R1 的 b13 改成 a3。修改后存在一行全为 a 标记,所以该分解具有无损连接性。

这个过程看起来繁琐,但只需要一个三行表格和两次替换。遇到答案文档给的结论和我算出来的相反时,我会重看一遍依赖方向,最常见的原因是 A->C 写成了 C->A。方向一旦反了,表格规则就不成立。

判断题做多了会发现,无损连接和函数依赖的闭包计算是同一套思想:反复扫描、反复合并。把这两类题的算法都写成脚本或表格后,理论答案的准确率比死记硬背高出很多。这个基本功也是后面做数据库课程设计时设计表结构的基础,总比返工重建表好。

4. 事务、并发锁与死锁:课后答案里最容易被低估的实战点

4.1 用隔离级别参数表把 ACID 答案变成可观测实验

事务章节在课后答案文档里占比不高,但每次考试都有一道“描述并发问题”的简答题。背会“脏读、不可重复读、幻读”三个词并不够,你需要知道它们在哪种隔离级别下消失。建议准备一张参数表,在自己机器上用两个终端验证。

隔离级别脏读不可重复读幻读实现机制
READ UNCOMMITTED可能可能可能不加锁
READ COMMITTED避免可能可能语句快照
REPEATABLE READ避免避免MySQL 下避免,标准定义可能事务快照或间隙锁
SERIALIZABLE避免避免避免全局锁或串行化执行

MySQL 默认是 REPEATABLE READ,PostgreSQL 和 Oracle 默认是 READ COMMITTED。答案文档里的“标准 SQL”默认关系是 SERIALIZABLE,但实际生产环境很少有人用这个级别,性能代价太大。把默认值写进你的答案批注里,比干背 ACID 四个英文单词更有用。

4.2 复现一次死锁:两个事务、两把锁、一条回滚

死锁题答案文档会画一张等待图,但在真实数据库里复现一次,理解会更深刻。拿 MySQL InnoDB 举例,建一张账户表,开启两个终端,交错执行两条 UPDATE 语句。

终端 A:

START TRANSACTION; UPDATE accounts SET balance = balance - 100 WHERE id = 1; -- 尚未提交,持有 id=1 的行锁

终端 B:

START TRANSACTION; UPDATE accounts SET balance = balance - 100 WHERE id = 2; -- 尚未提交,持有 id=2 的行锁

回到终端 A,继续执行:

UPDATE accounts SET balance = balance + 100 WHERE id = 2; -- 等待终端 B 释放 id=2 的锁

再到终端 B,继续执行:

UPDATE accounts SET balance = balance + 100 WHERE id = 1; -- InnoDB 检测到死锁,回滚其中一个事务

关键参数是 InnoDB 的锁等待超时间隔,默认值通常是 50 秒。如果 50 秒内没有检测到死锁,两个事务中的等待方会先报锁等待超时错误。死锁发生时,可以在 MySQL 命令行执行 SHOW ENGINE INNODB STATUS\G,查看 LATEST DETECTED DEADLOCK 片段,里面有互相等待的锁记录和回滚事务的 SQL 语句。

Oracle 和 PostgreSQL 的复现方式类似,但观测工具不同。PostgreSQL 可以在配置里设置 lock_timeout="2s",等待超时后再去 pg_stat_activity 查会话状态;Oracle 则通过 v$lock 和 v$session 视图跟踪锁等待链。答案文档里的死锁题往往只给概念图,你只需要在本地把这张图变成真实的错误输出。

4.3 踩在答案边缘的加锁问题:锁顺序、锁超时与监控

死锁和“普通的锁等待”不是一回事。死锁是多个事务互相持有对方想要的锁,数据库需要主动回滚一个事务来解除循环;锁等待只是一个事务在等另一个事务释放锁,等得够久就会正常推进。连接池里常见的“连接超时”,往往不是死锁,而是长事务把行锁握在手里不放。

课后练习中最好保持一致的加锁顺序,例如先锁 id 小的一行,再锁 id 大的一行。两个事务都按同样顺序访问资源,循环等待就不会形成。答案文档不会写这些,但它会考你死锁产生的四个必要条件,其中最容易被代码控制的,就是“请求并保持”和“循环等待”。

如果只想快速观察锁状态,MySQL 里 SHOW PROCESSLIST 能看到当前连接的执行状态;SHOW ENGINE INNODB STATUS 可以看到事务和锁的摘要。把这个输出贴到答案题旁边,比默写概念更让人信服。

5. 避坑:照着课后答案练 SQL 时最容易翻车的五件事

5.1 一条分组查询答案查出多行:聚合结果被当成子查询的“唯一值”

现象:题目要求“查询最高平均分的课程”,你按答案写了子查询,却返回多行记录而不是一行。

原因:子查询里用 AVG(score) 和某个数值比较,但聚合结果并不是标量;或者你按答案写了嵌套子查询,外层 SELECT 却少了去重与排序限定。更隐蔽的情况是答案文档没写 GROUP BY 之后的边界,你直接把聚合列放进了 WHERE,导致 SQL 语义错误。

解决:先跑一遍聚合查询本身,确认输出行数;再用 LIMIT 1 或窗口函数 ROW_NUMBER() OVER (ORDER BY avg_score DESC) 做精确取行。不要把“最高分”等价为“只取第一条”,成绩有并列时这两者结果不同。

5.2 建表答案在 MySQL 8.0 里报错:字符集与 SQL_MODE 一起作妖

现象:从答案文档里复制建表语句,在 MySQL 8.0 里执行,报错“Specified key was too long”,或中文注释全部乱码。

原因:文档里的表没指定字符集,默认可能是 latin1,中文按字节计算后索引超限;同时 MySQL 8.0 的 SQL_MODE 比旧版严格,过长的 VARCHAR 主键直接被拒绝。

解决:建表时显式写 CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci,并把所有字符串字段的 VARCHAR 长度控制在合理范围内。答案文档只给表结构不给环境参数,这一步必须自己补上。

5.3 事务答案总是不提交:测试库里的锁和 undo 越积越多

现象:按照答案写完 START TRANSACTION,后续查询一直卡住,别的会话也读不到最新数据。

原因:事务开了之后没有 COMMIT 或 ROLLBACK,行锁一直持有,undo log 也在持续累积。很多答案文档为了让代码短,省略了提交语句,默认你在考试时只是“写出来”而不是“跑起来”。

解决:每个事务练习题都显式写 COMMIT 或 ROLLBACK;在 MySQL 里可以用 SHOW PROCESSLIST 查看 Sleep 状态的连接,找到对应线程后用 KILL 断开。测试环境里我习惯把事务代码封装进一个 Python 脚本,用 finally 块保证断开连接。

5.4 把 TOP 方言当通用答案:Oracle、PostgreSQL、SQLite 各说各话

现象:按照答案文档写了 SELECT TOP 3,在 MySQL 里报语法错误,在 SQLite 里也一样报错。

原因:答案可能来自 SQL Server 教材风格的题目,TOP 是 SQL Server 和 Access 的方言;MySQL、PostgreSQL、SQLite 统一用 LIMIT,Oracle 用 ROWNUM 或 FETCH FIRST。

解决:看到“取前 N 条”先确认目标数据库,再动笔。我一般会把答案文档里的每条 SQL 标注“适用于哪类数据库”,不标默认按通用 SQL 处理。这是一种低成本高收益的防翻车手段。

5.5 死锁复现只开一个终端:你看到的只是等待,不是死锁

现象:在同一个 MySQL 会话里写了两个 UPDATE,然后手动执行,发现第二条 UPDATE 一直等待,最后超时。

原因:一个事务还没提交,自身持有的锁没有释放,第二个 UPDATE 在同一个连接里等待的是同一个事务的锁。死锁至少要两个会话交叉加锁才能形成。

解决:复用 4.2 节的两个终端脚本,或写一个 Python 多线程脚本,分别建立两条连接,按顺序交叉执行 UPDATE。如果用的是连接池,要把池的最小空闲连接数调成至少 2,否则第二个事务复用同一个连接,依然复现不出死锁。

6. 以题带练:把答案文档变成一份能自动校验的实验报告模板

最后分享一个我常用的技巧。很多答案文档的 SQL 题没有配套数据,直接抄答案很难分清是自己写对了,还是答案本身在另一种数据库方言下成立。我的做法是把每道题变成三个文件:题号对应的 SQL 脚本、插入的测试数据、一个自动校验输出的小脚本。

import sqlite3 conn = sqlite3.connect("school.db") cur = conn.cursor() cur.executescript(open("p12.sql", encoding="utf-8").read()) cur.execute("SELECT cno, AVG(score) FROM sc GROUP BY cno;") rows = cur.fetchall() assert len(rows) >= 1, "查询结果为空" assert all(isinstance(r[1], float) for r in rows), "平均分类型异常" print(rows) conn.close()

这个脚本的作用不是检查答案正确性,而是检查答案可执行性。只要 p12.sql 能跑通、输出类型符合预期,这道题就算“落地”了。把十几个脚本放进课程设计资料目录,每次改动建表结构时批量跑一遍,就能快速发现外键引用、字段长度等连锁问题。一个人面对几十道课后题时,这种回归验证比肉眼检查高效得多。

后来带同学准备复试时,大家拿着同一份答案文档问哪条 SQL 是对的,我不会直接告诉他答案,而是让他跑一遍这个校验脚本。能跑出来的,再谈对错;跑不出来的,先补建表语句和方言适配。这个过程比背十遍答案文档更能建立手感。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询