跳转至

数据库练习卷 - 逐题知识点解析

考试重点

本文档严格按照《练习卷2026》参考答案,逐题分析答案为什么这么写,涉及什么知识点。

最后更新:2026-05-27 | 整理人:赫尔墨斯(小弟一号)


一、简答题(6选4,每题5分)


题目1:实体完整性约束判断

题目: 有关系模式R(A,B,C,D),已知建表语句为:

Create table R(A char(2),B char(2),C char(2),D char(2),primary key(A,B))
问当表中不存在任何数据的情况下,以下语句是否能正常运行,并回答原因:
insert into R(B,C) values('B1','C1')

答案: ❌ 不能正常运行。(1分)因为主属性A的值为空值,违反实体完整性约束。(2分)

为什么这么写:

第一步:分析表结构

表R有4个列:A, B, C, D
主键是 (A, B) 联合主键

第二步:分析插入语句

insert into R(B,C) values('B1','C1')
- 只插入了 B 和 C 的值 - A 没有被赋值,所以 A = NULL

第三步:检查约束

主键是 (A, B)
A 是主属性
主属性不能为 NULL(实体完整性规则)
A = NULL → 违反实体完整性 → 插入失败

涉及的知识点:

知识点 内容
实体完整性 主键中的所有属性(主属性)都不能取 NULL 值
PRIMARY KEY 约束 定义主键,保证实体完整性
主属性 包含在候选码中的属性
插入操作 插入数据时,必须满足所有完整性约束

记忆口诀:

实体完整性 = 主键不能为空
插入数据时,主键属性必须有值


题目2:主键插入判断

题目:

insert into R(A,B) values('A2','B1')

答案: ✅ 可以正常运行(1分)。因为主属性A、B的值不为空值(2分)

为什么这么写:

第一步:分析插入语句

insert into R(A,B) values('A2','B1')
- 提供了 A='A2',B='B1'

第二步:检查约束

主键是 (A, B)
A = 'A2'(不为空)✓
B = 'B1'(不为空)✓
表中没有任何数据,不存在重复问题 ✓
满足所有约束 → 插入成功

涉及的知识点:

知识点 内容
实体完整性 主键不能为空且唯一
插入操作 插入数据时,主键属性必须有值且不重复

记忆口诀:

插入数据时,只要主键属性都有值且不重复,就能成功


题目3:外键删除问题

题目: 有关系模式如下定义:

create table R(
    A char(4),
    B char(4),
    primary key(A),
    foreign key(B) references R(A)
)
请解释当R关系中一个元组被删除时会发生什么情况。

答案: 该关系中存在外码,参照本关系的主码A,(3分)当删除一个元组时,如果该元组的A被其他元组的B所参照,会发生违反参照完整性约束的问题。(3分)

为什么这么写:

第一步:理解表结构

表R有两个列:A, B
主键是 A
外键是 B,引用同一张表的 A 列(自引用外键)

第二步:分析删除场景

假设数据:
- 元组1:A='A1', B=NULL
- 元组2:A='A2', B='A1'(B参照了元组1的A)

如果删除元组1:
- 元组2的 B='A1' 找不到对应的 A 值了
- 违反参照完整性约束

第三步:说明处理方式

可能的处理方式:
- RESTRICT/NO ACTION:拒绝删除(默认行为)
- CASCADE:级联删除(连同引用它的元组一起删除)
- SET NULL:将引用的外键设为 NULL
- SET DEFAULT:将引用的外键设为默认值

涉及的知识点:

知识点 内容
参照完整性 外键的值必须要么等于被参照表中某个元组的主键值,要么为 NULL
自引用外键 外键引用同一张表的主键(典型例子:员工-经理关系)
删除约束 删除被参照元组时,需要考虑处理方式

记忆口诀:

自引用外键很常见
删除被参照元组时,需要考虑处理方式


题目4:并发调度问题

题目: 请说明下图中的并发调度存在什么问题?请给出可串行化的调度。

答案: 存在不可重复读的问题。(2分) (4分)加锁,解锁正确,错一处扣1分

为什么这么写:

第一步:识别问题

不可重复读的定义:
- 同一事务内两次读取结果不同
- 原因:其他事务在两次读取之间修改了数据

第二步:与脏读的区别

脏读:读取了其他事务未提交的数据
不可重复读:读取了其他事务已提交的数据,但两次读取结果不同

第三步:解决方案

使用加锁机制:
- 读锁(共享锁):读取数据时加锁
- 写锁(排他锁):修改数据时加锁

第四步:可串行化调度

定义:并发执行结果等价于某种串行执行
实现:使用两段锁协议(2PL)

涉及的知识点:

知识点 内容
不可重复读 同一事务内两次读取结果不同
脏读 读取了其他事务未提交的数据
两段锁协议(2PL) 扩展阶段只能加锁,收缩阶段只能解锁
可串行化 并发执行结果等价于某种串行执行

记忆口诀:

不可重复读 = 同一事务内两次读取结果不同
可串行化 = 并发执行结果等价于某种串行执行


题目5:BCNF判断

题目: 关系模式R(U,F),U=(A,B,C,D),F={(A,B)→C,(A,C)→B,B→C},请问R是否是BCNF,并说明理由。

答案: 首先求候选码,L:A LR:BC R:- N:D ADF+ =AD ABD F+ = ABCD=U ACD F+ = ABCD=U 所以候选码为:ABD,ACD。(4分) 由于给出的FD中存在决定因素不包含码的情况,所以R不是BCNF。(2分)

为什么这么写:

第一步:求候选码

属性分类: | 类型 | 属性 | 说明 | |------|------|------| | L(只出现在左边) | A | 只在函数依赖左边出现 | | LR(左右都出现) | B, C | 在左右两边都出现 | | R(只出现在右边) | 无 | - | | N(都不出现) | D | 在任何一边都不出现 |

计算闭包:

ADF+ = AD(无法得到全部属性)
ABD F+ = ABCD = U ✓
ACD F+ = ABCD = U ✓

候选码为:ABD 和 ACD

第二步:判断 BCNF

BCNF 要求: 对于每一个非平凡函数依赖 X→Y,X 必须是超码。

分析函数依赖: - (A,B)→C:X=(A,B),(A,B) 不是超码(无法推出 D) - (A,C)→B:X=(A,C),(A,C) 不是超码(无法推出 D) - B→C:X=B,B 显然不是超码

结论: 存在决定因素不包含码的情况,所以 R 不是 BCNF。

涉及的知识点:

知识点 内容
候选码 能唯一标识元组的最小属性集
属性分类 L类(只左)、R类(只右)、LR类(左右都出现)、N类(都不出现)
闭包计算 X⁺ = 所有能从X推出的属性集合
BCNF 每个非平凡FD的左边都必须是超码

记忆口诀:

BCNF判断步骤:
1. 先求候选码
2. 检查每个FD的左边是否都是超码
3. 如果存在左边不是超码的FD,则不是BCNF


题目6:数据库恢复

题目: 考虑如下日志记录,假设 A、B、C 的初始值均为 0:

序号 日志
1 T1:开始
2 T1:写B,B=10
3 T2:开始
4 T2:写A,A=8
5 T3:开始
6 T3:写C,C=12
7 T1:写C,C=11
8 T1:提交
9 T3:写A,A=10
10 T2:写B,B=8
11 T2:提交

如果系统故障发生在 10 之后,请说明需要重做的事务,需要撤销处理的事务。并写出系统恢复后 A、B、C 的值。

答案: 重做的事务:T1;撤销的事务:T2,T3(3分) A=0,B=10,C=11(3分)

为什么这么写:

第一步:确定事务状态

故障发生在日志10之后:
- T1:在日志8已提交 → REDO(重做)
- T2:在日志10还在执行,日志11才提交(故障发生在10之后)→ UNDO(撤销)
- T3:从未提交 → UNDO(撤销)

第二步:恢复过程

A、B、C 初始值都是 0

T1 REDO(重做):

日志2:B=10
日志7:C=11

T2 UNDO(撤销):

日志4:A=8(撤销,A恢复为0)
日志10:B=8(撤销,B恢复为10,因为T1已写入10)

T3 UNDO(撤销):

日志6:C=12(撤销,C恢复为11,因为T1已写入11)
日志9:A=10(撤销,A保持0)

第三步:最终结果

A=0,B=10,C=11

涉及的知识点:

知识点 内容
RED(重做) 已提交的事务需要重做
UNDO(撤销) 未提交的事务需要撤销
恢复原则 从后往前扫描日志
事务状态 开始、执行、提交、故障

记忆口诀:

已提交重做,未提交撤销。从后往前扫描日志。


二、设计与计算题


题目1:E-R图设计

题目: 设计一个某公司的项目管理系统的数据库。系统的主要功能是: - 公司下设几个部门,每个部门分别有多个员工,每个员工在当前只隶属于某一部门,每个部门有一个部门负责人 - 公司同时会有多个项目同时开展,每个项目由某个部门负责,有一个项目负责人,项目组成员可来自公司的不同部门 - 一个员工同一时间只属于某个项目 - 每个项目推进过程中各个阶段产生费用的报销单据

答案:

关系模型

关系名 属性 主码 外码
部门 部门编号,部门名称,部长员工号 部门编号 部长员工号→员工
员工 员工编号,姓名,所在部门编号,所在项目编号 员工编号 所在部门编号→部门,所在项目编号→项目
项目 项目编号,名称,开始时间,结束时间,预算,主要内容,部门号 项目编号 部门号→部门
单据 单据号,内容,所属项目编号 单据号 所属项目编号→项目

评分标准: 关系模式名称,属性合理每个关系模式1分,主码,外码标注正确各1分

为什么这么写:

第一步:识别实体

从题目中提取实体:
- 部门(Department)
- 员工(Employee)
- 项目(Project)
- 单据(Document)

第二步:识别关系

从题目中提取关系:
- 部门-员工:1:N(一个部门有多个员工,一个员工属于一个部门)
- 部门-项目:1:N(一个部门负责多个项目,一个项目由一个部门负责)
- 员工-项目:N:1(一个员工同一时间只属于一个项目)
- 项目-单据:1:N(一个项目有多张单据)

第三步:确定主键

每个实体选择一个唯一标识符作为主键:
- 部门:部门编号
- 员工:员工编号
- 项目:项目编号
- 单据:单据号

第四步:设置外键

在多端设置外键指向一端的主键:
- 员工表:所在部门编号→部门,所在项目编号→项目
- 项目表:部门号→部门
- 单据表:所属项目编号→项目

涉及的知识点:

知识点 内容
E-R图设计 识别实体、关系、基数
基数判断 1:1、1:N、M:N
关系模型 实体转换为表,关系转换为外键
主键选择 每个实体选择一个唯一标识符作为主键
外键设置 在多端设置外键指向一端的主键

记忆口诀:

E-R图:矩形=实体,菱形=联系,椭圆=属性
M:N关系需要新建表
外键放在多端


三、综合题


题目1:社团管理数据库

题目: 设学校社团管理数据库有如下基本表: - 学生:S(sno, sname, gender, sage, sdept) - 社团:T(Tno, Tname, sno) - 社团成员:ST(sno, Tno, date)

A. 关系代数查询

1. 查询"计算机"系参加了社团名为"音乐社"的学生的学号、姓名

答案:

π_{sno,sname}(σ_{Tname='音乐社'}(T) ⋈ ST ⋈ σ_{sdept='计算机系'}(S))

为什么这么写:

1. 先从 T 中选择 Tname='音乐社' 的社团
2. 从 S 中选择 sdept='计算机系' 的学生
3. 将结果与 ST 进行自然连接
4. 最后投影出 sno 和 sname

知识点: - 选择(σ):从关系中选取满足条件的元组 - 投影(π):从关系中选取指定的属性列 - 连接(⋈):按条件连接两个关系


2. 查询"计算机"系中没有参加任何社团的学生的学号

答案:

π_{sno}(σ_{sdept='计算机系'}(S)) - π_{sno}(ST)

为什么这么写:

1. 先找出计算机系所有学生的学号
2. 再找出参加了社团的所有学生学号
3. 用差运算得到没有参加社团的学生学号

知识点: - 差运算(−):属于 R 但不属于 S 的元组


B. SQL 查询

1. 定义 T 表(含完整性约束)

答案:

Create table T(
    Tno int,
    Tname char(20),
    sno char(8),
    primary key(Tno),
    foreign key(sno) references S(sno)
)

为什么这么写:

- primary key(Tno):定义实体完整性,Tno 不能为空且唯一
- foreign key(sno) references S(sno):定义参照完整性,sno 必须在 S 表中存在

知识点: - 实体完整性:主键不能为空且唯一 - 参照完整性:外键必须在被参照表中存在


2. 查询参加人数超过50人的社团的名称

答案:

SELECT T.Tname
FROM T, ST
WHERE T.Tno = ST.Tno
GROUP BY T.Tno, T.Tname
HAVING COUNT(ST.sno) > 50

为什么这么写:

1. 连接 T 和 ST 表
2. 按社团分组
3. 使用 HAVING 过滤分组后的人数

知识点: - GROUP BY:分组查询 - HAVING:分组后过滤 - COUNT:聚合函数,计算行数


3. 查询参加音乐社或者文学社的学生学号和姓名

答案:

SELECT DISTINCT S.sno, S.sname
FROM S, T, ST
WHERE T.Tno = ST.Tno 
  AND S.sno = ST.sno 
  AND (Tname = '音乐社' OR Tname = '文学社')

为什么这么写:

1. 三表连接
2. 使用 OR 条件筛选两个社团
3. 使用 DISTINCT 去重(一个学生可能同时参加两个社团)

知识点: - DISTINCT:去重 - OR:逻辑或,满足任一条件即可


4. 查询"王星"同学参加的所有的社团号和社团名

答案:

SELECT T.Tno, T.Tname
FROM S, T, ST
WHERE T.Tno = ST.Tno 
  AND S.sno = ST.sno 
  AND S.sname = '王星'

为什么这么写:

简单的三表连接查询,筛选条件是 sname='王星'

知识点: - 多表连接查询 - WHERE 条件筛选


5. 查询社团负责学生是计算机系的社团号和社团名

答案:

SELECT Tno, Tname
FROM T
WHERE sno IN (SELECT sno FROM S WHERE sdept = '计算机系')

为什么这么写:

1. 使用子查询找出计算机系学生的学号
2. 外层查询找出这些学生负责的社团

知识点: - IN 子查询:检查值是否在子查询结果中 - 嵌套查询:一个查询嵌套在另一个查询中


6. 查询参加了学号"20010101"学生所参加的所有社团的学生学号

答案:

SELECT S.sno
FROM S
WHERE NOT EXISTS (
    SELECT * 
    FROM ST 
    WHERE ST.sno = '20010101' 
      AND NOT EXISTS (
          SELECT * 
          FROM ST AS ST2 
          WHERE S.sno = ST2.sno 
            AND ST.tno = ST2.tno
      )
)

为什么这么写:

这是经典的"查询参加了全部课程的学生"问题的变体(所有社团)。

使用双重 NOT EXISTS 实现关系代数中的除运算(÷):
1. 外层 NOT EXISTS:对于 S 中的每个学生
2. 内层 NOT EXISTS:检查 "20010101" 参加的每个社团,该学生也都参加了
3. 如果内层 NOT EXISTS 条件成立(即不存在某个社团,20010101 参加了但该学生没参加),则该学生满足条件

知识点: - NOT EXISTS:不存在 - 双重 NOT EXISTS:实现除运算("所有"问题) - 除运算:关系代数中的除法操作

记忆口诀:

双重 NOT EXISTS = 除运算 = "所有"问题


7. 新增"交响乐团"记录

答案:

INSERT INTO T(Tno, Tname) VALUES(6, '交响乐团');

为什么这么写:

- sno 为空(负责学生待定),这是允许的(外键可为 NULL)

知识点: - INSERT 语句:插入新记录 - 外键可为 NULL


8. 删除"影视社团"记录(考虑完整性约束)

答案:

-- 先删除社团成员表中的相关记录
DELETE FROM ST 
WHERE tno = (SELECT tno FROM T WHERE Tname = '影视社团');

-- 再删除社团表中的记录
DELETE FROM T 
WHERE Tname = '影视社团';

为什么这么写:

1. 由于 ST 表有外键引用 T 表,必须先删除子表(ST)记录
2. 再删除父表(T)记录
3. 顺序不能颠倒,否则会违反参照完整性约束

知识点: - DELETE 语句:删除记录 - 删除顺序:先删子表,再删父表 - 参照完整性约束

记忆口诀:

删除顺序:先删子表,再删父表


题目2:范式分析与分解

题目: 设某公司管理职工每日生产产品数量的关系模式:R(职工编号,日期,日产量,车间编号,车间主任)

说明: - 日产量为每个职工每日的产量 - 一个车间有一个车间主任 - 每个职工隶属于一个车间,一个车间有多名职工

(1)求候选码

答案: F = {(职工编号, 日期)→日产量,车间编号→车间主任,职工编号→车间编号} 码:(职工编号, 日期)

为什么这么写:

1. 属性分类:
   - L类(只出现在左边):职工编号,日期
   - LR类(左右都出现):车间编号
   - R类(只出现在右边):日产量,车间主任

2. 计算闭包:
   - (职工编号, 日期) → 日产量 ✓
   - (职工编号, 日期) → 车间编号(通过 职工编号→车间编号)✓
   - (职工编号, 日期) → 车间主任(通过 车间编号→车间主任)✓

3. 所以 (职工编号, 日期) 能推出所有属性,是候选码

知识点: - 候选码:能唯一标识元组的最小属性集 - 属性分类:L类、R类、LR类、N类 - 闭包计算:X⁺ = 所有能从X推出的属性集合


(2)判断最高范式

答案: R 最高为 1NF

为什么这么写:

1. 码:(职工编号, 日期)
2. 非主属性:日产量,车间编号,车间主任

3. 检查部分函数依赖:
   - 职工编号 → 车间编号
   - 职工编号是码的一部分,但不是整个码
   - 车间编号 部分函数依赖于码

4. 由于存在非主属性对码的部分函数依赖,所以 R 只是 1NF

知识点: - 1NF:属性不可再分 - 2NF:消除非主属性对码的部分函数依赖 - 部分函数依赖:非主属性只依赖于码的一部分

记忆口诀:

1NF:属性不可再分
2NF:消除部分依赖
3NF:消除传递依赖
BCNF:决定因素是超码


(3)分解为 3NF

答案: 分解为3NF 职工(职工编号,车间编号) 车间(车间编号,车间主任) 日产量(职工编号,日期,日产量)

为什么这么写:

1. 分解原则:将部分函数依赖和传递函数依赖分离

2. 职工表:存储职工和车间的隶属关系
   - 职工编号 → 车间编号
   - 无部分函数依赖,无传递函数依赖

3. 车间表:存储车间和车间主任的对应关系
   - 车间编号 → 车间主任
   - 无部分函数依赖,无传递函数依赖

4. 日产量表:存储每个职工每日的产量
   - (职工编号, 日期) → 日产量
   - 无部分函数依赖,无传递函数依赖

知识点: - 3NF:消除非主属性对码的传递函数依赖 - 分解原则:保持无损连接和函数依赖


题目3:两段锁与死锁

题目: 设 T1,T2 是如下 2 个事务,其中 A、B 为数据库中 2 个数据项: - T1:如果 A>0 则更新 B=B+10 - T2:如果 B>0 则更新 A=A+5

请问使 2 事务遵守两段锁协议的情况下,是否会发生死锁。请举例说明。

答案: 有可能产生死锁。如下调度:遵循两段锁协议,并发生死锁的调度。

为什么这么写:

第一步:理解两段锁协议(2PL)

扩展阶段:只能加锁
收缩阶段:只能解锁
作用:保证并发调度的可串行性
注意:2PL可能死锁!

第二步:构造死锁调度

| 时间 | T1 | T2 |
|------|----|----|
| t1 | Lock(A) | |
| t2 | Read(A) | |
| t3 | | Lock(B) |
| t4 | | Read(B) |
| t5 | Lock(B) ← 等待T2 | |
| t6 | | Lock(A) ← 等待T1 |
| | **死锁!** | **死锁!** |

第三步:分析死锁原因

T1 持有 A 的锁,请求 B 的锁
T2 持有 B 的锁,请求 A 的锁
形成循环等待

涉及的知识点:

知识点 内容
两段锁协议(2PL) 扩展阶段只能加锁,收缩阶段只能解锁
死锁 循环等待
死锁四条件 互斥、持有并等待、不可剥夺、循环等待
死锁处理 预防、检测与恢复

记忆口诀:

2PL保证可串行化,但可能死锁
死锁四条件:互斥、持有并等待、不可剥夺、循环等待


四、核心口诀汇总

完整性约束

实体完整性:主键不能为空
参照完整性:外键必须存在或为空

范式判断

1NF:属性不可再分
2NF:消除部分依赖
3NF:消除传递依赖
BCNF:决定因素是超码

SQL查询

双重NOT EXISTS = 除运算 = "所有"
删除顺序:先删子表,再删父表

事务并发

ACID:原子一致性隔离持久
2PL:扩展阶段加锁,收缩阶段解锁
死锁四条件:互斥、持有并等待、不可剥夺、循环等待

数据库恢复

已提交重做,未提交撤销
从后往前扫描日志

题目知识点解析完成!祝考试顺利!🎓