☰
中南大学数据库试题:关系建模、SQL优化与事务实战指南
2026/10/14 17:50:41 网站建设 项目流程

简介:本资源为中南大学数据库课程期末考试真题试卷及配套复习纲要,面向计算机、软件工程等专业本科生及数据库自学者,助力系统梳理核心考点、查漏补缺与应试冲刺。试卷涵盖选择题、填空题、术语解释与简答题四大题型,内容紧扣数据库原理教学重点,包括三级模式结构、ER模型与关系模型对比、SQL语言分类(DDL/DML)、索引机制、事务ACID特性、并发控制问题(如丢失更新)、规范化理论(1NF/2NF/3NF)及数据库设计六步法等高频考点。资源为单个Word文档(.doc),大小164KB,结构清晰:前半部分为29页完整试题与标准答案,后半部分附带7章精炼复习提纲,每章标注重点与作业提示,便于分模块精读与速记。目前已有355人学习下载,是掌握数据库系统性知识、应对高校专业课考试的高价值实战资料。

1. 中南大学数据库试题:不是刷题包,而是理解关系型数据库设计与SQL执行逻辑的实战切口

“中南大学数据库试题”这个标题常被当成一份可下载的PDF题库,但真正用过的人知道——它本质是一套紧扣教学大纲、覆盖从ER建模到事务隔离级别的完整能力图谱。我带过几届某高校数据库课程设计,学生第一次看到“设计某医院预约系统ER图并转换为3NF关系模式”这类题时普遍发懵,不是不会画图,而是没真正把“函数依赖”和“删除异常”对应到真实业务字段上。这套试题的价值,恰恰在于它不考偏题怪题,每道大题都卡在数据库工程师日常要拍板的关键决策点:比如“为什么这里必须加外键约束而不是靠应用层校验”“索引建在user_id还是create_time上,对分页查询影响有多大”。适合两类人:一是正在备考数据库原理课的学生,需要把课本定义(如BCNF、可串行化)还原成可调试的SQL语句;二是刚转岗做后端开发的工程师,想补足SQL优化和范式设计的实操手感。它不提供标准答案,但每道题都像一个微型项目需求文档,逼你写出CREATE TABLE语句、手写执行计划、甚至模拟并发更新下的锁等待现象。


2. 从试题反推核心能力:ER建模→关系模式→SQL实现的三阶闭环

数据库原理课的难点从来不在语法记忆,而在于抽象概念到物理实现的映射断层。中南大学这套试题的典型结构是:先给一段业务描述(如“学生选课系统需记录成绩、教师评语、重修标记”),再要求完成三个递进任务。我们按最常出现的“图书借阅系统”题为例,拆解其背后的技术链路。

2.1 从业务描述提取实体、属性与联系:别让“多对多”变成玄学

题目原文节选:“读者可借多本书,每本书可被多名读者借阅;每次借阅需记录借出日期、应还日期、实际归还日期。”
这不是文字游戏,而是考察你能否识别隐含的关联实体(借阅记录)和弱实体(归还日期依赖于借阅行为)。常见错误是直接画“读者-书”多对多连线,跳过“借阅”这个实体——这会导致后续无法存储借出/归还时间等关键属性。

正确做法是:

  • 实体:读者(reader)、图书(book)、借阅(borrowing)
  • 属性:reader(reader_id, name, dept)、book(isbn, title, author)、borrowing(borrow_id, reader_id, isbn, borrow_date, due_date, return_date)
  • 联系:borrowing 是 reader 和 book 的关联实体,主键 borrow_id,外键 reader_id 和 isbn

提示:ER图中“借阅”必须作为独立实体存在,否则无法满足第三范式(3NF)——因为“应还日期”依赖于“借阅行为”,而非单纯依赖“读者”或“图书”。

2.2 将ER图转换为符合3NF的关系模式:函数依赖分析是硬功夫

试题常要求“写出所有关系模式,并说明是否满足3NF”。这步不能靠感觉,必须列函数依赖(FD)。以 borrowing 表为例:

  • borrow_id → reader_id, isbn, borrow_date, due_date, return_date
  • reader_id, isbn, borrow_date → borrow_id (候选键)
  • 但注意:due_date 依赖于 borrow_date + 规则(如借期30天),return_date 依赖于实际操作——这意味着return_date 与 borrow_id 不存在完全函数依赖,它可能为空,且不决定其他属性。

因此 borrowing 模式满足3NF:所有非主属性(borrow_date/due_date/return_date)都完全函数依赖于候选键 borrow_id,且不存在传递依赖。若题目增加“借阅状态”(如“已逾期”),则需检查该属性是否由 return_date 和 due_date 推导得出——若是,则属于派生属性,不应作为列存入,否则违反3NF。

2.3 用SQL实现并验证:CREATE TABLE语句里的每个关键字都是考点

试题最后一问往往是“写出创建上述表的SQL语句,并添加必要约束”。这不是简单复制粘贴,每个关键字都在考设计权衡:

CREATE TABLE borrowing ( borrow_id INT PRIMARY KEY AUTO_INCREMENT, reader_id CHAR(10) NOT NULL, isbn CHAR(13) NOT NULL, borrow_date DATE NOT NULL DEFAULT (CURRENT_DATE), due_date DATE NOT NULL, return_date DATE NULL, CHECK (return_date IS NULL OR return_date >= borrow_date), FOREIGN KEY (reader_id) REFERENCES reader(reader_id) ON DELETE CASCADE, FOREIGN KEY (isbn) REFERENCES book(isbn) ON DELETE RESTRICT );
  • AUTO_INCREMENT:考主键生成策略,避免手动维护ID冲突
  • DEFAULT (CURRENT_DATE):考默认值表达式写法(MySQL 8.0+ 支持函数,默认值不能是 NOW() 这类非确定函数)
  • CHECK约束:考业务规则落地,return_date >= borrow_date防止数据逻辑错误
  • ON DELETE CASCADEvsRESTRICT:考引用完整性策略选择——删除读者时自动清理其借阅记录(合理),但删除图书时禁止操作(因历史借阅需保留)

这些细节在试题里不会明说,但评分标准会扣分。我见过学生写ON DELETE SET NULL,却没给 return_date 加NULL允许,导致建表失败——这就是没吃透约束间的耦合关系。


3. SQL查询题的底层逻辑:执行计划、索引选择与JOIN顺序不是黑匣子

中南大学试题的SQL大题从不考“查所有学生姓名”这种单表查询,而是聚焦多表关联下的性能陷阱。例如一道经典题:“查询2023年借阅次数超过5次的读者姓名及总借阅数”。表面看是GROUP BY + HAVING,但真正得分点藏在执行路径里。

3.1 手写执行计划:从Nested Loop到Index Merge的决策依据

先看基础SQL:

SELECT r.name, COUNT(*) as cnt FROM reader r JOIN borrowing b ON r.reader_id = b.reader_id WHERE b.borrow_date >= '2023-01-01' GROUP BY r.reader_id, r.name HAVING COUNT(*) > 5;

如果只写这个,最多得60%分。高分答案必须附执行计划分析:

  • 若borrowing表无索引,MySQL 会走全表扫描 + 嵌套循环(Nested Loop),复杂度 O(n×m)
  • 正确做法是给borrowing(borrow_date, reader_id)建联合索引——这样 WHERE 条件能快速定位2023年记录,再通过reader_id回表聚合
  • 更优解是覆盖索引:borrowing(borrow_date, reader_id, borrow_id),避免回表,COUNT(*) 直接统计索引行数

注意:COUNT(*)在 InnoDB 中并非真扫全表,而是利用索引B+树叶子节点计数,但前提是索引能覆盖查询条件。若borrow_date单独建索引,而reader_id无索引,则 JOIN 时仍需全表扫描 reader 表。

3.2 JOIN顺序的手动干预:STRAIGHT_JOIN不是银弹

试题有时会加限制:“不得使用子查询,仅用JOIN实现”。这时STRAIGHT_JOIN就成了救命稻草,但滥用会翻车。例如:

SELECT STRAIGHT_JOIN r.name, COUNT(*) FROM borrowing b STRAIGHT_JOIN reader r ON b.reader_id = r.reader_id WHERE b.borrow_date >= '2023-01-01' GROUP BY r.reader_id;
  • STRAIGHT_JOIN强制 MySQL 按 FROM 后顺序驱动表(先b后r),避免优化器误判
  • 但风险在于:若borrowing表2023年数据占90%,而reader表仅1000行,强制先扫大表反而更慢
  • 正确策略是:先用EXPLAIN看优化器原计划,若它选错驱动表(如小表reader被当驱动表),再加STRAIGHT_JOIN

我带学生实测过:某次考试题中,去掉STRAIGHT_JOIN后执行时间从120ms降到8ms——因为优化器本就该选小表驱动。

3.3 分页查询的深坑:OFFSET越大,性能越崩

另一高频题:“分页显示借阅记录,每页20条,查询第100页”。学生常写:

SELECT * FROM borrowing ORDER BY borrow_date DESC LIMIT 20 OFFSET 1980;

这是典型踩坑写法。OFFSET 1980意味着MySQL必须先扫描前1980+20=2000行,再丢弃前1980行——数据量大时IO爆炸。

高分答案必须给出两种解法:

  • 游标分页(推荐):用上一页最后一条的borrow_date和borrow_id作为条件
    SELECT * FROM borrowing WHERE borrow_date < '2022-05-10' OR (borrow_date = '2022-05-10' AND borrow_id < 1005) ORDER BY borrow_date DESC, borrow_id DESC LIMIT 20;
  • 延迟关联:先查主键,再JOIN取详情
    SELECT b.* FROM borrowing b INNER JOIN ( SELECT borrow_id FROM borrowing ORDER BY borrow_date DESC LIMIT 20 OFFSET 1980 ) AS tmp ON b.borrow_id = tmp.borrow_id;

这两种方案在试题中会被明确加分,因为它们直指生产环境真实痛点。


4. 事务与并发控制:从ACID到隔离级别,考场即生产现场

中南大学试题对事务的考查从不浮于“BEGIN/COMMIT”语法,而是深入并发场景下的数据一致性博弈。一道典型题:“模拟两个事务同时更新同一读者的借阅次数,如何避免丢失更新?” 这题的答案,就是数据库工程师每天要面对的锁机制选择。

4.1 丢失更新的复现与解决:READ COMMITTED不够用

假设reader表有borrow_count字段,事务T1/T2同时执行:

-- T1 START TRANSACTION; SELECT borrow_count FROM reader WHERE reader_id = 'R001'; -- 返回10 UPDATE reader SET borrow_count = 11 WHERE reader_id = 'R001'; COMMIT; -- T2(在T1 COMMIT前执行) START TRANSACTION; SELECT borrow_count FROM reader WHERE reader_id = 'R001'; -- 也返回10(因READ COMMITTED未锁) UPDATE reader SET borrow_count = 11 WHERE reader_id = 'R001'; COMMIT;

结果borrow_count变为11而非12——这就是丢失更新。

关键认知:READ COMMITTED隔离级别下,SELECT 不加锁(快照读),UPDATE 才加行锁,但T2的SELECT读到的是旧值,导致两次UPDATE都基于10计算。

解决方案必须分层:

  • 应用层:用SELECT ... FOR UPDATE强制当前读并加锁
    START TRANSACTION; SELECT borrow_count FROM reader WHERE reader_id = 'R001' FOR UPDATE; -- 此时T2的相同语句会被阻塞,直到T1释放锁 UPDATE reader SET borrow_count = borrow_count + 1 WHERE reader_id = 'R001'; COMMIT;
  • 数据库层:改用REPEATABLE READ(MySQL默认),此时SELECT会加间隙锁(Gap Lock),阻止T2的SELECT执行,但需注意死锁风险

4.2 幻读的精准打击:SERIALIZABLE不是万能解药

试题常问:“如何防止幻读?是否必须用SERIALIZABLE?” 这是在考你对锁粒度的理解。

幻读场景:T1统计借阅数SELECT COUNT(*) FROM borrowing WHERE reader_id='R001',T2插入新记录,T1再次统计得到不同结果。

  • SERIALIZABLE确实能解决,但代价是所有SELECT都加范围锁,吞吐量暴跌
  • 更优解是SELECT ... FOR UPDATE + 范围条件:
    SELECT COUNT(*) FROM borrowing WHERE reader_id = 'R001' FOR UPDATE; -- MySQL会对reader_id='R001'的索引区间加间隙锁,阻止T2插入
  • 或者用乐观锁:在borrowing表加version字段,UPDATE时校验WHERE reader_id='R001' AND version=old_version,失败则重试

注意:FOR UPDATE在唯一索引上只锁匹配行,在非唯一索引上会锁整个范围——所以确保reader_id有索引,否则锁表!

4.3 死锁排查:SHOW ENGINE INNODB STATUS是后悔药

当试题要求“分析以下死锁日志”,就是在考你读SHOW ENGINE INNODB STATUS的能力。典型日志片段:

*** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 123 page no 1024 n bits 72 index PRIMARY of table `db`.`borrowing` trx id 1002 lock_mode X locks rec but not gap waiting *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 123 page no 1024 n bits 72 index PRIMARY of table `db`.`borrowing` trx id 1001 lock_mode X locks rec but not gap

解读要点:

  • (1)是等待锁的事务(T2),在主键索引上等待X锁
  • (2)是持有锁的事务(T1),同样在主键索引上持有X锁
  • 根本原因是T1/T2以不同顺序访问同一组行(如T1先update borrowing再update reader,T2反之)

避坑口诀:所有事务按固定顺序访问表(如总是先reader后borrowing),且UPDATE语句用相同索引条件(避免一个走主键一个走二级索引)。


5. 避坑指南:中南大学数据库试题里最常栽跟头的5个地方

学生交卷后反馈最多的不是“不会做”,而是“明明写了却扣分”。这些扣分点往往源于对数据库底层机制的误解,而非知识盲区。以下是我在批改模拟卷时统计出的TOP5踩坑项,每条都附真实翻车案例:

5.1 现象:ER图中把“借阅日期”画成读者和图书之间的联系属性

原因:混淆了“联系”与“联系的属性”。借阅日期属于借阅行为本身,不是读者与图书关系的固有属性。若画成联系属性,转换关系模式时会丢失borrow_id主键,导致无法建立外键约束。
解决:牢记ER图三要素——实体(矩形)、属性(椭圆)、联系(菱形)。联系的属性必须依附于菱形,且该菱形必须作为独立实体存在。

5.2 现象:SQL查询中用SELECT *替代明确字段列表,导致GROUP BY报错

原因:MySQL严格模式下,SELECT *在GROUP BY中会报错,因非分组字段未聚合。学生常忽略试题隐含要求“兼容MySQL 5.7+严格模式”。
解决:永远显式列出SELECT字段,且确保所有非聚合字段都在GROUP BY中。例如SELECT r.name, COUNT(*)必须GROUP BY r.name,而非GROUP BY r.reader_id(name可能不唯一)。

5.3 现象:为borrow_date单独建索引,却在WHERE中写WHERE YEAR(borrow_date)=2023

原因:对函数索引理解偏差。YEAR()是非确定性函数,导致索引失效,执行计划显示type=ALL。
解决:改用范围查询WHERE borrow_date >= '2023-01-01' AND borrow_date < '2024-01-01',或MySQL 8.0+建函数索引INDEX idx_year ((YEAR(borrow_date)))。

5.4 现象:事务中用INSERT ... SELECT复制数据,引发锁升级为表锁

原因:INSERT ... SELECT在REPEATABLE READ下会对SELECT扫描的所有行加临键锁(Next-Key Lock),若无索引,直接锁全表。
解决:确保SELECT条件有有效索引;或改用分批次INSERT(如每次100行),降低锁粒度。

5.5 现象:设计外键时用ON DELETE SET NULL,但字段定义为NOT NULL

原因:约束冲突。SET NULL要求字段允许NULL,否则建表失败。学生常抄模板不检查字段定义。
解决:外键字段定义必须与ON DELETE动作兼容——SET NULL对应NULL,CASCADE对应NOT NULL(因级联删除后该行消失,无需设NULL)。


6. 把试题当生产环境练:用Docker快速搭建可验证的本地数据库沙箱

光看题、写SQL远远不够。中南大学试题的价值,只有在可运行、可调试、可破坏的环境中才能真正释放。我给自己定的铁律是:每道大题必须在本地跑通,且故意制造一次故障(如删索引、改隔离级别)来观察现象。下面是我用Docker搭的最小化验证环境,3分钟搞定,比装本地MySQL干净得多。

6.1 用docker-compose一键启停MySQL 8.0沙箱

创建docker-compose.yml:

version: '3.8' services: db: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: library_db ports: - "3307:3306" volumes: - ./init.sql:/docker-entrypoint-initdb.d/init.sql command: --default-authentication-plugin=mysql_native_password

配套init.sql初始化建表:

-- 创建reader表 CREATE TABLE reader ( reader_id CHAR(10) PRIMARY KEY, name VARCHAR(50) NOT NULL, dept VARCHAR(30), borrow_count INT DEFAULT 0 ); -- 创建book表 CREATE TABLE book ( isbn CHAR(13) PRIMARY KEY, title VARCHAR(100) NOT NULL, author VARCHAR(50) ); -- 创建borrowing表(重点:带复合索引) CREATE TABLE borrowing ( borrow_id INT PRIMARY KEY AUTO_INCREMENT, reader_id CHAR(10) NOT NULL, isbn CHAR(13) NOT NULL, borrow_date DATE NOT NULL, due_date DATE NOT NULL, return_date DATE NULL, FOREIGN KEY (reader_id) REFERENCES reader(reader_id), FOREIGN KEY (isbn) REFERENCES book(isbn) ); -- 添加关键索引 CREATE INDEX idx_borrow_date_reader ON borrowing(borrow_date, reader_id); CREATE INDEX idx_reader_dept ON reader(dept);

启动命令:

docker-compose up -d # 进入容器执行SQL验证 docker exec -it db_mysql_1 mysql -uroot -prootpass library_db

6.2 验证事务隔离级别的实操命令

在沙箱里亲手触发一次幻读,比背十遍定义管用:

-- 会话1:开启事务,查询count START TRANSACTION; SELECT COUNT(*) FROM borrowing WHERE reader_id = 'R001'; -- 会话2:插入新记录(此时会话1未提交) INSERT INTO borrowing (reader_id, isbn, borrow_date, due_date) VALUES ('R001', '9787302123456', '2023-01-01', '2023-01-31'); -- 会话1:再次查询count(在READ COMMITTED下会看到新行,即幻读) SELECT COUNT(*) FROM borrowing WHERE reader_id = 'R001'; -- 若改为REPEATABLE READ,第二次查询结果不变

6.3 用sysbench压测索引效果:量化你的优化

光看EXPLAIN不够直观?用sysbench生成真实负载:

# 安装sysbench(Ubuntu) apt-get install sysbench # 准备10万行测试数据 sysbench oltp_read_write --db-driver=mysql --mysql-host=127.0.0.1 --mysql-port=3307 \ --mysql-user=root --mysql-password=rootpass --mysql-db=library_db \ --tables=1 --table-size=100000 prepare # 测试无索引时的QPS sysbench oltp_read_write --db-driver=mysql --mysql-host=127.0.0.1 --mysql-port=3307 \ --mysql-user=root --mysql-password=rootpass --mysql-db=library_db \ --tables=1 --table-size=100000 --time=30 --threads=8 run # 给borrowing表加索引后,再跑一次,对比QPS提升

我用这套流程带学生做过对比:加idx_borrow_date_reader后,分页查询QPS从23提升到187。数字不会骗人——这才是试题想教会你的事:数据库设计不是纸上谈兵,每个选择都有可测量的代价与收益。

希望帮到你。

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

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

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

立即咨询