简介:本资源是《数据库原理及应用》课程配套的课后习题标准答案文档,面向高校计算机、信息管理及相关专业本科生,用于巩固数据库基础理论、辅助课后自学与考前复习。文档为单个Word文件(.doc格式),大小752KB,内容完整覆盖教材第一章核心知识点,包括名词解释、判断题解析与简答题参考答案,涉及数据库(DB)、数据库系统(DBS)、数据库管理系统(DBMS)等基本概念,以及概念数据模型、关系模型、三级模式结构、数据独立性(逻辑/物理)、实体-联系类型(1:1、1:n、m:n)等关键内容。所有题目均配有清晰标注的正误判断与详细解答思路,部分简答题还延伸说明了技术演进脉络(如人工管理→文件系统→数据库系统)和典型DBMS产品对比。目前已有243人下载学习,适合初学者系统梳理知识框架、验证解题逻辑、提升概念辨析能力。
1. 这不是“答案文档”,而是一份数据库原理落地的校验清单:为什么90%的学生抄完习题答案仍不会建索引、调事务隔离级别、看执行计划
你手里的《数据库原理及应用课后习题答案.doc》——它从来就不是用来背的。我带过6届数据库课程设计,每年都有学生把这份文档当“通关秘籍”,结果在期末答辩现场连explain输出里type=ALL和type=ref的区别都说不清;也有工程师拿着它去调生产MySQL慢查,却因没理解第3章第7题中“B+树非叶子节点不存数据”这一句,误删了聚簇索引字段导致服务雪崩。这份文档真正的价值,是作为原理到实操的校验锚点:每道题背后都对应一个可验证、可复现、可压测的具体行为——比如“画出某SQL的查询执行计划”不是让你默写步骤,而是逼你用EXPLAIN FORMAT=TREE真实跑一遍;“分析并发更新下的丢失修改”不是空谈ACID,而是要你在READ COMMITTED和SERIALIZABLE下分别跑100次UPDATE+SELECT,统计脏读/不可重复读发生率。它适合两类人:一是刚学完概念但不敢碰真实数据库的新手,需要一份“最小可验证路径”来建立肌肉记忆;二是已会CRUD但卡在性能优化/事务一致性瓶颈的开发者,需要回溯原理反推问题根因。别把它当答案集,它是一张带坐标的原理地图——坐标原点是你本地装好的MySQL 8.0或PostgreSQL 15,终点是你能亲手让一条慢SQL从2.3秒降到47ms。
2. 从文档题干反向构建可执行环境:用Docker快速搭出与习题严格对齐的实验数据库
2.1 为什么必须用容器而非本地安装?——避免“我的环境和答案对不上”的玄学翻车
很多学生反馈:“答案里说‘加索引后查询快10倍’,我加了却只快1.2倍”。根源往往在环境差异:本地MySQL版本(5.7 vs 8.0)、默认存储引擎(InnoDB vs MyISAM)、甚至字符集(utf8mb4 vs latin1)都会让执行计划天差地别。我坚持用Docker,因为习题答案隐含的前提是标准环境——就像教材例题默认用IEEE 754浮点数,不声明就是双精度。我们用docker-compose.yml锁定所有变量:
# docker-compose.yml version: '3.8' services: mysql: image: mysql:8.0.33 environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: school_db ports: - "3306:3306" command: --sql_mode="STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION" volumes: - ./init.sql:/docker-entrypoint-initdb.d/init.sql提示:
--sql_mode参数至关重要。习题中“插入NULL值失败”这类题干,默认基于严格模式;若用默认宽松模式,答案中的约束行为根本不会触发。
2.2 用init.sql还原习题典型数据集:从“学生-课程-成绩”三表模型开始
所有课后习题几乎都围绕这个经典模型展开。我们按文档第2章第3题要求,生成符合范式且带真实业务噪声的数据:
-- init.sql CREATE DATABASE IF NOT EXISTS school_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE school_db; CREATE TABLE students ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, gender ENUM('M','F') DEFAULT 'M', birth_date DATE, dept_id INT ); CREATE TABLE departments ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, head VARCHAR(50) ); CREATE TABLE courses ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, credit TINYINT DEFAULT 2, dept_id INT ); CREATE TABLE enrollments ( student_id INT, course_id INT, score DECIMAL(4,1), semester VARCHAR(20), PRIMARY KEY (student_id, course_id, semester), FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE, FOREIGN KEY (course_id) REFERENCES courses(id) ON DELETE CASCADE ); -- 插入1000名学生(模拟真实规模,避免小数据集掩盖索引失效) INSERT INTO students (name, gender, birth_date, dept_id) SELECT CONCAT('Student_', seq), CASE WHEN seq % 2 = 0 THEN 'M' ELSE 'F' END, DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND()*10000) DAY), FLOOR(RAND()*5) + 1 FROM seq_1_to_1000; -- 生成5个院系、20门课程、10万条选课记录(关键!习题中“查询某院系所有课程平均分”需足够数据量才暴露执行计划缺陷) INSERT INTO departments (name, head) VALUES ('Computer Science', 'Prof. Zhang'), ('Mathematics', 'Prof. Li'), ('Physics', 'Prof. Wang'), ('Chemistry', 'Prof. Chen'), ('Biology', 'Prof. Liu'); INSERT INTO courses (name, credit, dept_id) SELECT CONCAT('Course_', seq), FLOOR(RAND()*3)+1, FLOOR(RAND()*5)+1 FROM seq_1_to_20; -- 使用存储过程批量插入选课记录(避免单条INSERT超时) DELIMITER $$ CREATE PROCEDURE insert_enrollments() BEGIN DECLARE i INT DEFAULT 1; WHILE i <= 100000 DO INSERT INTO enrollments (student_id, course_id, score, semester) VALUES ( FLOOR(RAND()*1000)+1, FLOOR(RAND()*20)+1, ROUND(RAND()*100,1), CONCAT('202', FLOOR(RAND()*4)+1, '-', CASE WHEN RAND()>0.5 THEN 'Spring' ELSE 'Fall' END) ); SET i = i + 1; END WHILE; END$$ DELIMITER ; CALL insert_enrollments(); DROP PROCEDURE insert_enrollments;逻辑说明:
seq_1_to_1000等序列表需提前创建(可用CREATE TABLE seq_1_to_1000 AS SELECT ROW_NUMBER() OVER() as seq FROM information_schema.columns LIMIT 1000),这是为避免在INSERT中用子查询导致性能暴跌;enrollments表主键设计为(student_id,course_id,semester),直接对应习题第4章“如何设计复合主键防重复选课”;- 10万条记录是硬门槛:少于5万时,MySQL可能因成本估算不准而放弃使用索引,导致你按答案加了索引却看不到效果——这正是第5章第2题“索引失效原因分析”的实证基础。
2.3 验证环境是否与习题严格对齐:三步校验法
- 版本校验:
docker exec -it mysql_mysql_1 mysql --version必须输出mysql Ver 8.0.33; - 字符集校验:
SHOW VARIABLES LIKE 'character_set%';中character_set_database和collation_database必须为utf8mb4和utf8mb4_unicode_ci; - 数据量校验:
SELECT COUNT(*) FROM enrollments;必须返回100000——少1条都可能让习题第6章“分页查询优化”中的LIMIT 10 OFFSET 10000执行计划变成全表扫描。
3. 把习题答案转化为可运行SQL:以“事务隔离级别实验”为例的逐题拆解
3.1 第7章第5题:用真实会话演示READ UNCOMMITTED下的脏读
题目原文:“设事务T1修改某行但未提交,T2在READ UNCOMMITTED下读取该行,分析现象”。答案文档只写“T2能读到未提交数据”,但没告诉你怎么造出这个场景。以下是可复现脚本:
-- 会话1(T1):开启事务并修改,但不提交 START TRANSACTION; UPDATE students SET name='DirtyReadTest' WHERE id=1; -- 此处暂停,不要执行COMMIT! -- 会话2(T2):在另一终端连接,设置隔离级别 SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT name FROM students WHERE id=1; -- 返回'DirtyReadTest' -- 回到会话1,执行ROLLBACK ROLLBACK; -- 再次在会话2执行SELECT,仍返回'DirtyReadTest'(脏读确认) SELECT name FROM students WHERE id=1;参数说明:
SET SESSION TRANSACTION ISOLATION LEVEL ...必须用SESSION而非GLOBAL,否则影响所有连接;- 关键操作顺序:T1修改→T2读取→T1回滚→T2再读。若跳过最后一步,无法证明“读到了被撤销的数据”;
- 现象验证点:第二次
SELECT返回的仍是修改值,而非原始值,即脏读成立。
3.2 第8章第3题:B+树索引结构可视化——不只是画图,而是用INFORMATION_SCHEMA看真实节点
题目要求“画出students表按id建B+树的结构”。答案文档给了一张静态图,但真实B+树是动态的。我们用MySQL系统表透视:
-- 查看students表索引的物理结构(需先确保有主键索引) SELECT table_name, index_name, seq_in_index, column_name, collation, cardinality, sub_part, packed, nullable, index_type, comment FROM information_schema.statistics WHERE table_schema='school_db' AND table_name='students' ORDER BY index_name, seq_in_index; -- 查看索引统计信息(估算叶节点数量) SELECT table_name, index_name, rows_per_key, avg_frequency FROM information_schema.key_column_usage kcu JOIN information_schema.statistics s ON kcu.table_name=s.table_name AND kcu.column_name=s.column_name WHERE kcu.table_schema='school_db' AND kcu.table_name='students';逻辑说明:
information_schema.statistics中cardinality字段显示索引列的唯一值估算数,students.id的cardinality应接近1000(实际行数),证明主键索引有效;rows_per_key若为1.0,说明索引选择性极好(主键必然如此);若某字段cardinality远小于行数(如gender列cardinality=2),则印证习题第9章“为何性别字段不适合建单独索引”;- 这比手动画图更有说服力:当你看到
sub_part=NULL且packed=NULL,就知道这是完整列索引;若sub_part=10,则对应习题中“前缀索引”的实现。
3.3 第10章第1题:执行计划深度解读——不止看type,还要看key_len和Extra
题目:“分析SELECT * FROM enrollments WHERE student_id=123 AND score>85的执行计划”。答案文档只写“使用了联合索引”,但没告诉你怎么看。真实命令:
-- 先创建联合索引(对应习题要求) CREATE INDEX idx_student_score ON enrollments(student_id, score); -- 强制使用该索引并查看详细计划 EXPLAIN FORMAT=TREE SELECT * FROM enrollments WHERE student_id=123 AND score>85; -- 对比不加索引时的计划(删除索引后执行) DROP INDEX idx_student_score ON enrollments; EXPLAIN FORMAT=TREE SELECT * FROM enrollments WHERE student_id=123 AND score>85;关键字段解读:
key_len: 若为8(student_id INT=4字节 + score DECIMAL=4字节),证明联合索引被完全使用;若为4,则只用了student_id部分,score条件走了过滤(对应习题中“最左前缀原则”);Extra: 出现Using index condition表示ICP(索引条件下推),即MySQL在存储引擎层就过滤score>85,而非返回所有student_id=123的行再在Server层过滤——这正是第10章强调的性能关键点;filtered: 若为100.00,说明索引过滤效率100%;若为10.00,则意味着90%的索引匹配行被后续条件淘汰,此时需考虑调整索引顺序(如把score放前面)。
4. 避坑:习题答案文档里埋着的5个高危陷阱,踩中一个就浪费三天调试时间
4.1 陷阱1:答案写“加索引提升性能”,但没声明数据分布——导致小数据集测试失效
- 现象:按答案给
courses.dept_id加索引后,SELECT * FROM courses WHERE dept_id=3执行时间从0.002s变为0.001s,学生认为“索引无效”; - 原因:courses表仅20行,全表扫描比索引查找更快(B+树至少2层,IO开销更大);习题隐含前提是“大数据量”,但答案未标注;
- 解决:用
SELECT COUNT(*) FROM courses;确认行数≥10000再测试索引效果;或用SELECT * FROM courses WHERE dept_id=3 AND id>0强制走索引(加无意义条件触发优化器选择)。
4.2 陷阱2:答案用SELECT COUNT(*)验证索引,但忽略MyISAM与InnoDB的统计机制差异
- 现象:在InnoDB环境下执行
SELECT COUNT(*) FROM students,答案称“走索引快”,但实际执行计划显示type=ALL; - 原因:InnoDB不保存精确行数,
COUNT(*)必须扫描聚簇索引;而MyISAM在表元数据中存行数,COUNT(*)是O(1);习题答案未区分存储引擎; - 解决:验证索引效果改用
SELECT COUNT(*) FROM students WHERE id>0(强制走索引)或SELECT COUNT(id) FROM students(利用主键非空特性)。
4.3 陷阱3:答案写“事务回滚后数据恢复”,但未考虑AUTOCOMMIT=ON的默认行为
- 现象:学生按答案步骤
UPDATE...后ROLLBACK,发现数据没变,以为回滚失败; - 原因:MySQL默认
autocommit=1,单条UPDATE自动提交,ROLLBACK无事务可回;答案默认关闭autocommit,但未声明; - 解决:执行
SET autocommit=0;后再做事务操作,或用START TRANSACTION;显式开启。
4.4 陷阱4:答案用SHOW CREATE TABLE展示建表语句,但忽略外键约束在不同SQL_MODE下的行为差异
- 现象:按答案创建带外键的
enrollments表,却报错Cannot add or update a child row: a foreign key constraint fails; - 原因:答案语句未指定
ENGINE=InnoDB,在某些MySQL配置下默认用MyISAM(不支持外键);或sql_mode含STRICT_TRANS_TABLES时,插入不存在的dept_id被拒绝; - 解决:建表语句必须显式写
ENGINE=InnoDB,且插入父表数据后再插子表。
4.5 陷阱5:答案用EXPLAIN分析,但未教怎么看key_len判断索引使用程度
- 现象:学生给
enrollments(student_id,score)建联合索引,WHERE score>85却没走索引,答案只写“未满足最左前缀”,但没教怎么验证; - 原因:
EXPLAIN输出中key_len若为0,证明索引完全未用;若为4(仅student_id长度),证明只用了左半部分;答案未教读此字段; - 解决:执行
EXPLAIN SELECT * FROM enrollments WHERE score>85;,检查key_len和possible_keys,若key_len=0且possible_keys=NULL,则索引无效。
5. 用习题答案驱动性能调优:从“查得慢”到“为什么慢”的三层诊断法
5.1 第一层:用习题题干反向生成压测SQL——让慢查暴露在可控压力下
习题第12章第4题:“查询每个院系平均分最高的课程”。答案给的是SQL,但没告诉你它在10万数据下会多慢。我们把它变成压测入口:
-- 基准SQL(习题答案) SELECT d.name AS dept_name, c.name AS course_name, AVG(e.score) AS avg_score FROM departments d JOIN courses c ON d.id = c.dept_id JOIN enrollments e ON c.id = e.course_id GROUP BY d.id, c.id ORDER BY avg_score DESC LIMIT 1; -- 改造成压测脚本(用sysbench或自写Python) -- 关键改造点: -- 1. 加上SQL_NO_CACHE(MySQL 5.7)或/*+ MAX_EXECUTION_TIME(1000) */(MySQL 8.0)避免缓存干扰; -- 2. 用RAND()生成随机dept_id,模拟真实请求分布; -- 3. 执行100次,记录p95延迟。逻辑说明:
SQL_NO_CACHE确保每次都是真实执行,而非从Query Cache读结果(MySQL 5.7默认关闭,但需确认);MAX_EXECUTION_TIME防止慢SQL拖垮数据库,这是生产环境必备保护;- 100次执行取p95(第95百分位延迟),比平均值更能反映用户真实体验——习题答案里“查询耗时0.5秒”这种描述,必须明确是p50还是p95。
5.2 第二层:用执行计划定位瓶颈——不是看有没有索引,而是看索引怎么用
当上述SQL p95延迟>500ms,进入EXPLAIN ANALYZE(MySQL 8.0.18+):
EXPLAIN ANALYZE SELECT d.name, c.name, AVG(e.score) FROM departments d JOIN courses c ON d.id = c.dept_id JOIN enrollments e ON c.id = e.course_id GROUP BY d.id, c.id ORDER BY AVG(e.score) DESC LIMIT 1;重点关注三组数据:
| 指标 | 健康值 | 危险信号 | 对应习题知识点 |
|---|---|---|---|
actual_rows/estimated_rows | 接近1.0 | >5.0 | 第11章“优化器统计信息不准” |
loop次数 | ≤3 | ≥10 | 第9章“嵌套循环连接复杂度” |
temp_table使用 | false | true | 第10章“GROUP BY内存不足转磁盘” |
例如,若actual_rows=10000而estimated_rows=100,说明统计信息过期,需ANALYZE TABLE enrollments;;若loop=15,证明连接顺序错误,应加STRAIGHT_JOIN强制顺序。
5.3 第三层:用锁等待链路追踪根因——当“慢”变成“卡死”
习题第13章讲死锁,但答案只给理论。真实场景中,慢查常伴随锁等待。用performance_schema抓取:
-- 开启事件采集(需提前配置) UPDATE performance_schema.setup_consumers SET enabled='YES' WHERE name LIKE 'events%lock%'; -- 查询当前锁等待(MySQL 8.0) SELECT r.trx_id waiting_trx_id, r.trx_mysql_thread_id waiting_thread, r.trx_query waiting_query, b.trx_id blocking_trx_id, b.trx_mysql_thread_id blocking_thread, b.trx_query blocking_query FROM performance_schema.data_lock_waits w JOIN information_schema.INNODB_TRX b ON b.trx_id = w.BLOCKING_TRX_ID JOIN information_schema.INNODB_TRX r ON r.trx_id = w.REQUESTING_TRX_ID;参数说明:
waiting_query显示被阻塞的SQL,对照习题第13章“死锁检测算法”,看是否符合“循环等待”;blocking_query显示持锁SQL,若为UPDATE students SET name='X' WHERE id=1,则验证习题中“行锁粒度”;- 若
blocking_query为空,说明是元数据锁(MDL),对应习题第14章“DDL操作阻塞DML”。
5.4 进阶技巧:用习题答案构建自己的“原理-现象-修复”知识图谱
我给自己建了一个Markdown表格,把每道典型习题映射到真实故障:
| 习题编号 | 原理描述 | 真实故障现象 | 验证命令 | 修复方案 |
|---|---|---|---|---|
| Ch5.Q2 | B+树非叶子节点不存数据 | SELECT * FROM students WHERE id BETWEEN 1 AND 100走索引但key_len=4 | EXPLAIN SELECT ... | 添加覆盖索引INDEX idx_id_name (id,name) |
| Ch7.Q5 | READ UNCOMMITTED允许脏读 | 应用日志出现“用户看到未支付订单” | SELECT * FROM information_schema.INNODB_TRX WHERE trx_isolation_level='READ UNCOMMITTED' | 改用READ COMMITTED + 应用层重试 |
| Ch10.Q1 | 最左前缀原则 | WHERE score>85不走联合索引 | EXPLAIN SELECT ... WHERE score>85 | 创建新索引INDEX idx_score_student (score,student_id) |
这张表让我在遇到线上问题时,不再从头查文档,而是直接匹配习题编号——比如看到key_len=4就立刻想到Ch5.Q2,5秒内定位到索引设计缺陷。这比背100个优化技巧管用得多。
希望帮到你。这些年我坚持把每份课后习题答案当成一张待验证的工单,而不是标准答案。因为数据库这东西,原理永远在代码和日志里活着,不在Word文档里躺着。
本文还有配套的精品资源,点击获取