
简介《数据库原理及技术钱学忠答案》是一份面向数据库课程学习者与备考者的习题解答资料配套教材内容组织覆盖数据库设计、SQL语言、关系数据库理论、数据库管理系统与数据库管理五大核心主题。答案解析不仅对应课后练习还对需求分析、E-R模型、逻辑设计与物理设计等流程进行梳理并围绕SQL查询、视图、索引、事务控制以及关系代数、关系演算、Codd规范化原则展开说明有助于读者理解规范化减少冗余、优化查询性能、设计安全可靠的数据库系统。压缩包整体约16.69MB下载后可直接对照使用目前已有693人学习下载适合学生课后自习、考前复习也可供开发者在项目实践中查漏补缺。通过习题演练读者还能进一步掌握数据备份恢复、性能调优、权限管理等数据库运维要点为解决实际工程问题打下扎实基础。1. 数据库原理及技术答案期末前一周才找的东西怎么用才不亏数据库原理及技术答案是很多学生期末前一周才想起来找的东西。这份答案对应一套覆盖面很典型的习题集关系代数、SQL 查询、函数依赖与范式分解、事务与并发控制几乎每章都有可对照的解法。它解决的实际问题很具体——你做完一道题拿不准对不对复习完一章心里没底掌握到什么程度。对准备笔试的学生和自学入门的人这可能是性价比最高的自检工具。但它也容易被用错直接抄一遍除了虚假的熟悉感什么都得不到。这篇笔记按「答案覆盖什么 → 逐题怎么吃透 → 哪些答案不能全信 → 如何自己验证」的顺序展开目标是帮你把这份答案用成一台自检仪器而不是一份背诵材料。适合正在复习这门课的人也适合带实验课的人参考。2. 答案覆盖的知识框架先弄清每道题在考什么用答案之前先得知道这套习题集在考什么。我见过不少学生拿着一份答案从头做到尾做到第三章才发现前面的题号对不上、章节顺序也和教材不一样最后浪费一晚上。与其这样不如先花半小时把答案的目录和你的教材目录做一次映射。教材章节常见题型答案的呈现形式高频失分点关系模型与关系代数写表达式、化简表达式算子序列连接顺序、选择条件漏写SQL 与视图查询、更新、授权SELECT/UPDATE 语句分组条件放错位置范式与函数依赖判范式、求候选键、分解模式推导步骤候选键求错、分解后不验证事务与并发控制简答、判定调度可串行化关键词要点术语不完整、口径不一致这张表是这类教材的通用结构。你先对着自己手上的目录把习题章节标出来再在答案里找对应位置。如果答案的章节顺序和教材不完全一致以题干的专业术语为准去匹配别死抠题号。2.1 关系模型与关系代数答案里最容易被跳过的一章关系代数是整门课里最像数学的部分没有表、没有界面只有 σ选择、π投影、⋈连接、÷除法这些算子符号。很多学生跳过它直接做 SQL这是我在复习指导里反复纠正的习惯。笔试里关系代数通常占 1520 分更重要的是关系代数是 SQL 的语义基础——你写出的每一条 SELECT都能在关系代数里找到对应的表达式。答案在这一章给的是一个算子序列。你要做的不是背序列而是把它当作中间表示来校验。对着答案检查三个关键点选择条件是否完整、连接是否涉及正确的表、投影列是否多余或缺失。比如「查询选修了数据库课程的学生姓名」标准写法是 π_Sname(Student ⋈ SC ⋈ σ_Cname数据库(Course))如果你写的是先做笛卡尔积再选条件结果一样但表达式更长考试时容易被认为思路不清晰。2.2 SQL 应用题结果能对不代表思路对SQL 习题是整本答案里篇幅最大的部分。答案通常给一段能直接运行的 SELECT 语句但你必须意识到同一道查询题合法写法至少有三四套——JOIN 连接、IN 子查询、EXISTS 相关子查询甚至 GROUP BY 加 HAVING 也能写出等价结果。答案只会给其中一种。我见过学生在复习时把自己的 SQL 和答案对照发现写法不同就以为自己错了这是典型的误判。正确用法是先盖住答案自己写写完后把两种 SQL 都在数据库里跑一遍对比结果集。如果结果集一致再检查两个版本的语义是否在处理 NULL、重复行时也一样。如果都一致你的写法就是对的答案只是提供了一个更「教材化」的参考版本。2.3 范式与函数依赖争议答案的高发地带范式题是答案里争议最多的地方。原因在于不同教材对 2NF、3NF、BCNF 的定义细节并不完全一致尤其是「主属性」和「非主属性」的判定口径、以及分解到 3NF 还是 BCNF 才算完。你在对照这章答案之前先把自己教材里的定义抄出来1NF属性不可再分2NF1NF 且非主属性完全依赖候选键3NF2NF 且无非主属性对候选键的传递依赖BCNF每个函数依赖的左部都包含候选键答案给出分解步骤时重点核对两个验证分解是否无损连接、分解是否保持依赖。这两条在大多数答案里是直接给出结论而不写推导过程的需要你自己补上验证。候选键如果求错后面全错所以我会在第三章用完整例子演示一遍。2.4 事务与并发控制简答题的采分点分布这章的答案形态和前几章完全不同不是表达式也不是 SQL而是成段的文字要点。ACID 四个性质、共享锁和排他锁、两段锁协议、冲突可串行化判定、UNDO/REDO 恢复策略这些知识点的答案看起来都「差不多」判分靠的不是句子通顺而是关键词是否到齐。举例问「事务的 ACID 特性是什么」答案如果只写「原子性、一致性、隔离性、持久性」四个词能拿基本分但答案里通常还会补一句每个特性的场景化描述——比如原子性对应「要么全做要么全不做」隔离性对应「并发事务互不干扰」。你在对照时应该把自己写的答案里的关键词勾出来看少了哪个而不是看有没有整句背诵。这章我能给的实用建议是把答案里的关键词做成一个 check list考前对着清单自述一遍能说出来就算过。3. 逐题吃透的完整路径关系代数、SQL 与范式分解典型题演示这一章用三组最常考到的题型演示怎么从题干一路做到答案再做对答案的校验。全程用一个经典的选课库你可以在自己机器上照着建后续验证也用得上。3.1 关系代数表达式题把中文查询句翻译成算子序列选课库的三张表结构如下这是这类教材的标配Student(Sno, Sname, Ssex, Sage, Sdept)Course(Cno, Cname, Cpno, Ccredit)SC(Sno, Cno, Grade)题目「查询选修了『数据库』课程的学生姓名」。我自己做题的习惯是分三步走。第一步判断涉及哪些表学生姓名在 Student课程名在 Course选课关系在 SC三张表都要用。第二步先写选择再写连接把行数大的表缩小——σ_Cname数据库(Course) 先把课程表缩成一行。第三步补连接和投影得到π_Sname(Student ⋈ SC ⋈ σ_Cname数据库(Course))对照答案时注意另一种等价写法是先三表连接再做选择π_Sname(σ_Cname数据库(Student ⋈ SC ⋈ Course))。两种都正确答案给哪版取决于出题人的习惯。我建议你记住第一种因为它是「选择下推」的典型示例在查询优化那一章还会再遇到。再给一道除法题「查询选修了全部课程的学生姓名」。关系代数的标准写法是π_Sname(Student ⋈ (π_Sno,Cno(SC) ÷ π_Cno(Course)))这里的 ÷ 是除法运算语义是「被除数中的 Sno 要覆盖除数中所有的 Cno」这是最直观的「全部」表达。很多学生第一次看到 ÷ 就懵答案里一般只给表达式不解释你需要自己在草稿上演算一遍先算 SC 对 Cno 的投影再按 Sno 分组检查是否包含 Course 的全部课程号。提示关系代数题对完答案后把表达式翻译成 SQL 跑一遍。结果集能对上表达式基本就没问题。3.2 SQL 查询题先写关系代数再翻译成 SELECT 语句同一道「查询选修了数据库课程的学生姓名」SQL 的常见答案写法是SELECT DISTINCT Student.Sname FROM Student JOIN SC ON Student.Sno SC.Sno JOIN Course ON SC.Cno Course.Cno WHERE Course.Cname 数据库;写这句有三个要点。第一JOIN 的顺序是从 Student 到 SC 再到 Course连接条件分别用 Sno 和 Cno 作桥漏掉任何一个连接条件都会产生笛卡尔积式的错误结果。第二WHERE 里的条件是 Course.Cname不是 SC 里的任何列因为课程名只存在于 Course 表。第三SELECT 里加 DISTINCT 是为了防重复——虽然 SC 表的主键是 (Sno, Cno)同一学生同一课程不会出现两次但多表连接后结果集的行数不保证不重复养成加 DISTINCT 的习惯能省很多排查时间。那「选修了全部课程的学生姓名」怎么写答案十有八九给的是双重 NOT EXISTSSELECT 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 ) );这段 SQL 是关系代数除法的直接翻译读法很绕外层是「不存在这样一门课程」内层是「该学生没选过这门课」。两层否定叠一起语义就是「不存在任何一门该学生没选的课」。我建议你把它背下来的同时也要能讲清楚每一层的含义因为笔试中常考的变形题是把 Course 换成「至少两门课」之类理解了嵌套结构才能应付变形。另一种答案也可能给 GROUP BY 写法SELECT Student.Sname FROM Student JOIN SC ON Student.Sno SC.Sno GROUP BY Student.Sno, Student.Sname HAVING COUNT(DISTINCT SC.Cno) (SELECT COUNT(*) FROM Course);这个版本的逻辑是「选课数等于总课程数」。它有个边界坑如果 Course 表为空COUNT(*) 是 0但 JOIN 后的 SC 里任何学生都没有匹配行GROUP BY 加 HAVING 的结果是空集不会误判但如果用 LEFT JOIN 就会出问题。答案一般不会解释这些边界所以你需要自己做一次边界测试。3.3 关系模式分解题从函数依赖集到 3NF 无损分解范式分解题是最值得对着答案一步步重算的题型。它的步骤是固定的但每一步都有坑。我拿一道典型题演示。给定关系模式 R(U, F)U {Sno, Sname, Sdept, Dean, Cno, Grade}F {Sno→Sname, Sno→Sdept, Sdept→Dean, (Sno,Cno)→Grade}。第一步求候选键。左部出现但不在任何依赖右部的属性是 Sno 和 Cno先算 {Sno, Cno} 的闭包由 Sno→Sname 得到 Sname由 Sno→Sdept 得到 Sdept由 Sdept→Dean 得到 Dean由 (Sno,Cno)→Grade 得到 Grade最终闭包等于全集 U。所以候选键是 (Sno, Cno)且是唯一候选键。第二步检查 2NF。注意 Sno→Sname 和 Sno→SdeptSno 是候选键 (Sno, Cno) 的真子集非主属性 Sname、Sdept 对候选键存在部分依赖所以 R 不满足 2NF。这一步的分解是把部分依赖的属性拆出去R1(Sno, Sname, Sdept, Dean)F1 {Sno→Sname, Sno→Sdept, Sdept→Dean}R2(Sno, Cno, Grade)F2 {(Sno,Cno)→Grade}第三步检查 R1 是否满足 3NF。R1 的候选键是 Sno依赖 Sno→Sdept→Dean 构成传递依赖Dean 通过 Sdept 间接依赖 Sno所以 R1 不是 3NF继续拆R11(Sno, Sname, Sdept)F11 {Sno→Sname, Sno→Sdept}R12(Sdept, Dean)F12 {Sdept→Dean}R2 的候选键是 (Sno, Cno)没有部分依赖也没有传递依赖已经在 3NF。最终分解结果是 R11、R12、R2。第四步验证无损连接。R11 和 R12 的交集是 Sdept而 Sdept→Dean 在 R12 中成立满足无损连接的条件R11 和 R2 的交集是 SnoSno 是 R11 的超键同样满足。由于每一步分解都保持无损最终分解整体无损答案里如果直接写「无损」两个字你要能在草稿上补出这个验证过程。第五步验证依赖保持。F11 里有 Sno→Sname、Sno→SdeptF12 里有 Sdept→DeanF2 里有 (Sno,Cno)→Grade原 F 中的 4 条依赖没有一条丢失依赖保持成立。这一步在答案里经常被省略但它和考题里「判断分解是否正确」直接相关值得每次手动补上。提示这类题如果教材把 BCNF 列为必考答案会把结果补一句「以上三个模式同时满足 BCNF」。R11 的唯一非平凡依赖左部 Sno 是超键R12 的 Sdept 是超键R2 的 (Sno,Cno) 是超键确实都是 BCNF。但 3NF 和 BCNF 不是总重合例如 R(A,B,C) 上有 F{AB→C, C→B}候选键是 AB 和 AC所有属性都是主属性满足 3NF但 C 不是超键不满足 BCNF。遇到这种差异题以你教材的考试范围为准。4. 对照答案排查时的 5 个坑现象、原因与解决办法答案不是权威它只是一份整理过的参考资料。我给学生做课程答疑这几年见到的对照答案翻车现场可以归成下面五类每类都按「现象 → 原因 → 解决」说清楚。4.1 题号漂移教材版次不同导致的前后错位现象答案前 5 题和教材完全对得上到第 6 题开始驴唇不对马嘴题号和题干内容配不上。原因这门课的教材出过多个印次习题做过增删和重排网上流传的答案不一定对应你手上这个印次。解决永远不要按题号对照只按题干关键词匹配。先把答案里每道题的题干提出来扫一遍你教材的习题能匹配上多少算多少。如果整份答案只有一半能对上说明版次差太远直接换一份整理版别在错位的题上浪费时间。判断一份答案是否靠谱就看它是否写了「对应第几版第几次印刷」没写的默认按最新版走。4.2 关系代数符号体系不一致σ/π 和 Select/Project 混用现象答案里写的是 σ 和 π你的教材用的是 SELECT 和 PROJECT连接符号有的写 ⋈有的写 ∞有的干脆写 × 加条件。原因国内教材受不同原版教材影响关系代数的符号习惯分成好几派。答案的整理者大概率直接沿用了某一派的写法。解决在开始对照前先做一张自己的符号对照表把教材首页的符号定义抄下来再补上答案里出现的对应符号。常见对应是σ → SELECT选行、π → PROJECT选列、⋈ → JOIN连接、∪ → UNION并、− → DIFFERENCE差、÷ → DIVIDE除。做完对照再逐行读答案否则你会把 π 看成圆括号的一部分白白读错一整道题。4.3 范式判定口径不统一3NF 还是 BCNF 之争现象同一道模式分解题你自己算出来停在 3NF答案却说应该继续分解到 BCNF或者反过来答案停在 3NF你按 BCNF 判它不完整。原因不同教材对「分解到什么程度算完成」的约定不同。有的教材把 BCNF 放选学习题答案统一到 3NF 为止有的教材要求能分到 BCNF 就分到 BCNF。这两种都是「对的」取决于评分标准。解决先看你教材大纲和历年考题确认考试要求。如果要求到 3NF那么答案停在 3NF 是正常的你不应该为了追求 BCNF 多拆一步。判定时用教材的定义逐条核对特别留意「主属性」的判定——一个属性只要出现在任意一个候选键里就算主属性这一点最容易搞错。4.4 SQL 方言差异教材写法在你的数据库里翻车现象把答案里的 SELECT 粘进 SQLite 或者 MySQL 里运行直接报语法错误或者跑出来的结果集和参考答案不一致。原因答案的 SQL 很可能是按 SQL Server 或 Oracle 的习惯写的。SQL Server 的分页用 TOPOracle 用 ROWNUMMySQL 用 LIMIT日期函数、字符串拼接、是否强制 GROUP BY 全列表这些在不同数据库里都不一致。解决先把 SQL 里的方言特征识别出来。常见的三个标志SELECT TOP 代表 SQL Server、ROWNUM 代表 Oracle、反引号包裹表名代表 MySQL。SQLite 是最接近标准 SQL 的轻量库你先手工把答案翻译成标准 SQL再粘进 SQLite 验证语义是否正确。这步里最容易忽略的是 GROUP BY 的 ONLY_FULL_GROUP_BY 模式MySQL 在高版本默认开启SELECT 列必须出现在 GROUP BY 里或用聚合函数包裹答案里那些只取 Sname 不写进 GROUP BY 的写法在这种模式下会直接报错但你改一下 SELECT 列表就能跑通不是你的查询逻辑错。4.5 答案本身有错识别并纠正印刷错误现象某一步推导和上下文明显矛盾比如函数依赖集抄漏了一条或分解后的某个关系里出现了原模式没有的属性SQL 题的查询结果和你手动推演的结果对不上。原因网上流传的答案版本多数是学生整理的没有经过校验错误率远高于正式出版物。长表达式和长推导步骤是高发区。解决用双重校验。第一遍自己完整求解得到自己的答案第二遍看参考答案记录分歧点第三遍针对分歧点找第二个来源——可以是另一份整理版也可以是教材的配套习题指导或者问带课的人。SQL 题不用争直接跑出结果集来判对错。你如果发现答案确实错了值得把正确的推导过程附在旁边这类修订版往往会在班级里流传很多年。5. 自己搭一个答案验证环境用 SQLite 把每道 SQL 题跑出结果SQL 题最大的好处是结果是可判的。与其瞪着眼睛比较两个 SELECT 写法的差异不如建一个最小数据库把答案里的语句全部跑一遍。这节教你用 SQLite 搭一个零配置的验证环境全程不用安装服务器。5.1 建库与导入数据最小可用的三张表先建一个 schema 文件。如果你手上的教材表名不是 Student、Course、SC按教材里的名字替换即可-- schema.sql选课库三张表 CREATE TABLE Student ( Sno TEXT PRIMARY KEY, -- 学号 Sname TEXT NOT NULL, -- 姓名 Ssex TEXT, -- 性别 Sage INTEGER, -- 年龄 Sdept TEXT -- 系别 ); CREATE TABLE Course ( Cno TEXT PRIMARY KEY, -- 课程号 Cname TEXT NOT NULL, -- 课程名 Cpno TEXT, -- 先修课课程号 Ccredit INTEGER -- 学分 ); CREATE TABLE SC ( Sno TEXT, Cno TEXT, Grade REAL, -- 成绩 PRIMARY KEY (Sno, Cno), FOREIGN KEY (Sno) REFERENCES Student(Sno), FOREIGN KEY (Cno) REFERENCES Course(Cno) );这个 schema 里有几个参数值得注意。Sno、Cno 用 TEXT 而不是 INTEGER是因为学号课程号经常有前导零或以字母开头TEXT 最省心。SC 表的主键是 (Sno, Cno)这个复合主键会约束同一学生同一课程最多一条成绩记录避免你验证 DISTINCT 相关题目时被脏数据干扰。外键约束在 SQLite 里默认不生效需要每次连接时手动开启 PRAGMA foreign_keysON这一点建完库就知道。接着插入一份最小数据集覆盖后面验证要用到的场景INSERT INTO Student VALUES (001, 张三, 男, 20, 计算机系); INSERT INTO Student VALUES (002, 李四, 女, 19, 计算机系); INSERT INTO Student VALUES (003, 王五, 男, 21, 数学系); INSERT INTO Course VALUES (C1, 数据库, NULL, 4); INSERT INTO Course VALUES (C2, 数据结构, C1, 4); INSERT INTO Course VALUES (C3, 离散数学, NULL, 3); INSERT INTO SC VALUES (001, C1, 88); INSERT INTO SC VALUES (001, C2, 92); INSERT INTO SC VALUES (002, C1, 76); INSERT INTO SC VALUES (003, C1, 81); INSERT INTO SC VALUES (003, C3, 67);这组数据里我特意让李四只选了一门课让王五选了 C1 和 C3 但没选 C2这样「选修了全部课程」这类题才有区分度。你用这个库去跑答案里的任何查询结果集一眼就能看出对不对。5.2 逐题验证把答案里的 SELECT 语句粘进 sqlite3建库并导入数据用命令行完成sqlite3 textbook.db schema.sql sqlite3 textbook.db进入交互界面后先把上节的数据粘贴进去或者把 INSERT 语句也写进一个 data.sql 文件后一并导入。然后逐题跑答案。比如验证第三章那道题sqlite3 textbook.db SELECT DISTINCT Student.Sname FROM Student JOIN SC ON Student.Sno SC.Sno JOIN Course ON SC.Cno Course.Cno WHERE Course.Cname 数据库;输出应该是张三、李四、王五三个人名。如果你得到的行数比预期多先看 SC 表里有没有重复选课记录如果少看是不是 JOIN 条件把某张表的 NULL 值过滤掉了。SQLite 的默认输出是管道分隔的文本可以用.mode column和.headers on让输出排版更清楚。注意我一般不会只跑一遍就下结论而是把题干里的条件改一改比如把「数据库」换成「数据结构」看结果是否符合常识。这能帮你确认自己理解的是查询语义而不是某一条固定语句。5.3 用 Python 脚本批量比对结果集题目多了以后一条条在命令行跑效率太低。我习惯把每道题的答案 SQL 和预期结果写进一个 Python 脚本一次性跑完所有题import sqlite3 conn sqlite3.connect(textbook.db) cur conn.cursor() # 每道题的答案 SQLkey 是题号 queries { 3.2-1: SELECT DISTINCT Student.Sname FROM Student JOIN SC ON Student.Sno SC.Sno JOIN Course ON SC.Cno Course.Cno WHERE Course.Cname 数据库; , 3.2-2: SELECT 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 ) ); , } # 预期结果集用集合存储以便顺序无关比较 expected { 3.2-1: {(张三,), (李四,), (王五,)}, 3.2-2: {(张三,), (王五,)}, } for qid, sql in queries.items(): cur.execute(sql) actual set(cur.fetchall()) status PASS if actual expected.get(qid) else FAIL print(f{qid}: {status} 实际{actual} 预期{expected.get(qid)})这个脚本的核心是 set 比较。数据库返回的行有先后顺序SELECT 没写 ORDER BY 时顺序根本不保证用元组的集合来比就能屏蔽顺序差异只关心内容。需要调整的参数有三个connect 里的数据库文件名要和你建库路径一致expected 里的预期结果要靠你对数据集的推演得出来别拿答案里的结果直接填如果题目增多把 queries 和 expected 挪到 JSON 文件里脚本只负责执行与比对方便其他人复现。对关系代数和范式分解题这个环境帮不上忙它们靠草稿纸验证。但一份答案里 SQL 题通常占四成以上把它们全部变成可运行的验证据你的复习效率会明显不一样。6. 一个进阶习惯把答案倒推成一张考前自检表答案用到最后应该被「拆掉」。我的做法是每做完一章习题就把答案里出现过的考点整理成一张自检表然后扔掉答案只留表。这张表就是考试前一天的复习材料。考点对应题号能否独立做出卡在哪一步需要重看的章节关系代数选择与投影2.3是无无关系代数除法运算2.11否不懂 ÷ 的语义2.4 节SQL双重 NOT EXISTS3.5否内外层角色混淆3.2 节范式候选键闭包求解4.7是无无范式无损连接验证4.9部分交集判断条件记混4.4 节并发两段锁协议6.2是无无这张表的填写有个硬规则只有不看答案完整做出并验证过的题才能算「是」。凡是看过一眼答案的一律记「否」。别骗自己考前发现真实的薄弱点比考后才发现便宜得多。填表时顺便把卡住的步骤写具体「不懂 ÷ 的语义」比「关系代数不会」有用十倍因为它直接指向重看的章节。填完整张表之后你会得到一个很有意思的分布卡住的地方往往集中在少数几个考点上比如除法运算、嵌套子查询、无损连接的判定。这些就是你真正的复习重点而不是从第一章再重看一遍。我在带人复习时发现大部分学生以为自己哪里都会填完表才承认有一半的题是「看着答案会了」。这份表的另一个用途是隔两天重测把「否」的题重新做一遍能独立做出来再改成「是」这个过程本质上在做间隔重复比考前突击背答案扎实得多。这个习惯是我自己复习数据库时踩出来的后来每次带学生也让他们这么做效果比刷三遍题都明显。答案可以帮你确认对错但只有这张自检表能告诉你哪里没懂。希望帮到你。本文还有配套的精品资源点击获取