资讯详情 用《数据库系统概论》选择题反向吃透ACID、锁机制与执行计划
📅 2026/10/11 18:07:16
简介本资源是面向数据库原理初学者与备考学生的《数据库系统概论第五版》配套复习资料聚焦核心概念辨析与应试能力训练专为课程期末复习、考研基础巩固及DBMS入门理解设计。内容涵盖数据管理技术演进、数据库系统 vs 文件系统本质区别、数据独立性物理/逻辑、三级模式结构外模式/模式/内模式、E-R模型、数据模型分类、DBMS功能定位、数据冗余与一致性关系等20个高频考点全部以单选题形式呈现每题附标准答案与简明解析便于自测与即时反馈。资源为单个PDF文件大小1.55MB排版清晰、题目分课次组织共四次课作业适合作为碎片化刷题或考前速查材料。已有140人学习下载适合高校计算机专业学生、自学备考者快速梳理知识脉络、强化易混淆概念辨析能力。1. 这不是题库搬运用《数据库系统概论第五版》选择题反向吃透数据库系统原理的实操路径你手上有这份 PDF但打开后是不是很快陷入“每个选项都像对、又都不确定”的状态不是记不住“事务的 ACID 特性”这四个字母而是当题目问“在银行转账场景中若扣款成功但入账失败系统应如何保证一致性”时你卡在——到底该选“回滚整个事务”还是“启用补偿事务”这暴露了一个被长期忽视的事实《数据库系统概论第五版》的选择题不是考记忆而是考系统级决策链路的还原能力。它把索引结构、并发控制、日志机制、查询优化这些模块全部压缩进一个具体操作比如一条 UPDATE 语句执行时的完整生命周期里。本文不讲教材目录不列知识点树而是带你用这份 PDF 作为“黑匣子探测器”从一道题出发定位它调用的底层机制复现对应行为验证教材结论。适合正在备考软考高项、数据库工程师认证或刚写完 CRUD 就想搞懂“为什么加了索引反而变慢”的开发者。核心不是刷题量而是让每道题成为一次微型系统调试实验。2. 从一道典型题切入用 SQL Server 本地实例复现“封锁协议导致死锁”的完整链路题目示例源自 PDF 第 3 章选择题第 7 题“在可重复读隔离级别下事务 T1 对 A 表执行SELECT * FROM A WHERE id100事务 T2 同时对同一行执行UPDATE A SET nameX WHERE id100此时可能发生的现象是A. T2 立即更新成功B. T2 被阻塞直到 T1 提交或回滚C. T2 报错死锁D. T1 的 SELECT 结果被 T2 修改覆盖”这道题表面考隔离级别实际在考锁粒度、锁类型、等待队列、死锁检测三者如何协同。光背“可重复读会加 S 锁”没用必须看到锁怎么加、谁在等、超时怎么触发。下面用 SQL Server 2019免费版 Express 即可本地实操2.1 搭建可复现的测试环境建表、插数据、开两个独立会话先创建最小化测试表确保无索引干扰避免页锁升级影响观察-- 在 master 数据库中执行避免权限问题 USE master; GO -- 创建测试数据库 IF DB_ID(LockTestDB) IS NOT NULL DROP DATABASE LockTestDB; CREATE DATABASE LockTestDB; GO USE LockTestDB; GO -- 创建无索引的简单表关键避免 SQL Server 自动升级锁粒度 CREATE TABLE Account ( id INT PRIMARY KEY, name NVARCHAR(50) ); GO -- 插入测试数据 INSERT INTO Account VALUES (100, Alice), (200, Bob); GO参数说明PRIMARY KEY自动生成聚集索引但此处我们明确需要行级锁行为所以后续所有操作均基于id100这一行。不建额外索引防止 SQL Server 因统计信息误判而使用页锁。2.2 复现 T1 的 SELECT 并观察其持有的锁打开第一个查询窗口Session 1执行带事务的 SELECT并不提交-- Session 1模拟 T1 BEGIN TRAN; SELECT * FROM Account WHERE id 100; -- 此处暂停不要执行 COMMIT 或 ROLLBACK此时T1 持有对id100行的共享锁S 锁。验证方式在另一个窗口Session 2中查询动态管理视图-- Session 2立即执行无需等 T1 动作 SELECT request_session_id AS spid, resource_type, resource_description, request_mode AS lock_mode, request_status FROM sys.dm_tran_locks WHERE resource_database_id DB_ID(LockTestDB) AND resource_associated_entity_id OBJECT_ID(Account);你会看到类似输出spidresource_typeresource_descriptionlock_moderequest_status56KEY(8194443284a0)SGRANT逻辑说明resource_type KEY表明是行锁非 PAGE 或 TABLElock_mode S是共享锁GRANT表示已获得。resource_description中的十六进制值是行标识符RID证明锁精确到行。这是教材中“可重复读对读操作加 S 锁”的直接证据。2.3 触发 T2 的 UPDATE 并捕获阻塞与死锁全过程在 Session 2 中执行 UPDATE注意不要关闭 Session 2保持连接-- Session 2执行 T2 操作 BEGIN TRAN; UPDATE Account SET name X WHERE id 100; -- 此时会卡住因为 T1 的 S 锁与 T2 需要的 X 锁冲突此时 Session 2 被阻塞。等待约 10 秒后SQL Server 默认死锁检测周期若系统未自动检测到死锁手动在 Session 3 中运行-- Session 3主动触发死锁检测可选 DBCC TRACEON(1204, -1); -- 输出死锁图到错误日志 DBCC TRACEON(1222, -1); -- 输出 XML 格式死锁信息然后回到 Session 2你会发现它报错Msg 1205, Level 13, State 45, Line 2 Deadlock victim. Rerun your transaction.关键验证点这不是“T2 立即失败”而是先阻塞再被选为牺牲者。这印证了选项 BT2 被阻塞和 C可能报死锁都是部分正确但题干问“可能发生的现象”需结合上下文判断——在无其他并发干扰时B 是常态当存在循环等待如 T1 同时也在等 T2 的某资源C 才触发。教材第五版 P142 图 6.12 正是此场景的抽象。3. 用 MySQL 8.0 验证“幻读”与“间隙锁”的对抗关系不只是理论是能看见的锁范围PDF 第 4 章多道题围绕“幻读是否在可重复读下被解决”但 MySQL 和 SQL Server 实现完全不同。第五版教材以 SQL Server/Oracle 为蓝本而 MySQL 的间隙锁Gap Lock是特有机制。不做对比你就永远分不清“为什么同样设 RR 隔离级别MySQL 不出现幻读SQL Server 却需要序列化”。3.1 构建 MySQL 测试表并确认当前隔离级别-- MySQL 8.0 终端 CREATE DATABASE IF NOT EXISTS phantom_test; USE phantom_test; CREATE TABLE t_order ( id INT PRIMARY KEY, amount DECIMAL(10,2), status VARCHAR(20) ); INSERT INTO t_order VALUES (1, 100.00, pending), (3, 200.00, shipped), (5, 150.00, pending);确认当前会话隔离级别SELECT transaction_isolation; -- 应返回 REPEATABLE-READ3.2 复现幻读场景T1 两次 SELECT 之间T2 插入新行打开两个 MySQL 客户端窗口Session 1T1START TRANSACTION; SELECT * FROM t_order WHERE status pending; -- 返回 id1, id5 两行 -- 不提交保持事务开启Session 2T2START TRANSACTION; INSERT INTO t_order VALUES (2, 99.99, pending); -- 插入 id2statuspending COMMIT;回到 Session 1SELECT * FROM t_order WHERE status pending; -- 仍只返回 id1, id5id2 不可见 → 无幻读为什么因为 MySQL 在第一次SELECT ... WHERE statuspending时不仅给现有匹配行id1,5加记录锁还对(1,5)之间的间隙加了间隙锁Gap Lock阻止其他事务在此区间插入statuspending的新行。这是第五版教材未展开的实现细节但 PDF 中第 4 章第 12 题的干扰项 D “MySQL 可重复读下仍可能发生幻读” 正是利用考生不了解此机制设置的陷阱。3.3 关键验证关闭间隙锁让幻读真实发生修改 MySQL 配置仅测试环境-- 在 Session 1 中执行需 SUPER 权限 SET SESSION innodb_locks_unsafe_for_binlog ON; -- 或更直接降级为 READ-COMMITTED SET SESSION transaction_isolation READ-COMMITTED;然后重做上述步骤T1 第一次查得 2 行 → T2 插入 id2 → T1 第二次查得 3 行 →幻读发生。这证明教材说的“可重复读解决幻读”是建立在标准实现含间隙锁基础上的。脱离具体 DBMS 谈隔离级别就是纸上谈兵。4. 避坑指南做《数据库系统概论第五版》选择题时最常踩的 4 个认知陷阱这些不是粗心错误而是对数据库内核机制理解偏差导致的系统性误判。每一条都来自我带学生刷题时的真实翻车现场。4.1 现象选了“视图可以提高查询效率”结果错了原因混淆了“视图定义”和“视图物化”。教材第五版 P228 明确指出“视图是虚表不存储数据”但很多考生看到“创建索引视图Indexed View”就默认所有视图都可加速。实际上SQL Server 的索引视图要求极苛刻必须 SCHEMABINDING、所有列显式指定、不能有 OUTER JOIN 等而 MySQL 根本不支持索引视图。绝大多数场景下视图只是语法糖甚至因嵌套层数过多导致优化器放弃使用索引。解决遇到“视图提升性能”选项一律视为错误除非题干明确写出“已创建唯一聚集索引的视图”。4.2 现象认为“UNIQUE 约束自动创建唯一索引所以删除约束就等于删除索引”原因忽略索引的独立生命周期。在 SQL Server 中ALTER TABLE ... DROP CONSTRAINT会连带删除其依赖的索引但在 MySQL 中DROP INDEX idx_name ON tbl和DROP CONSTRAINT uc_name是两条独立命令且 UNIQUE 约束背后的索引名可能与约束名不同如tbl_name.UK_col1。更隐蔽的是如果该索引被其他约束引用如外键删除约束不会删索引但索引可能变成“孤儿”。解决执行SHOW CREATE TABLE tbl查看约束与索引的绑定关系在生产环境永远用DROP INDEX IF EXISTS单独管理索引。4.3 现象看到“日志文件损坏数据库无法启动”立刻选“从最近全备恢复”原因忘记事务日志的核心价值是保证已提交事务不丢失。第五版 P315 强调“日志优先写Write-Ahead Logging”意味着数据页修改前日志必须先落盘。若日志文件损坏但数据文件完好且数据库处于 SIMPLE 恢复模式确实只能全备恢复但若为 FULL 模式且日志备份链完整可用RESTORE LOG ... WITH STOPAT恢复到最后一个有效日志备份点比全备少丢数小时数据。解决题干出现“日志文件损坏”必须先看是否提及“有定期日志备份”——有则选“日志恢复”无则选“全备恢复”。4.4 现象计算查询代价时把“索引深度”当成“B树层数”直接套用公式log₂(N)原因B树的实际层数取决于页大小、键长度、指针大小。SQL Server 默认页 8KBInnoDB 默认 16KB一页能存的键值数量差异巨大。例如一个INT主键在 InnoDB 中每页约存 500 个键100 万行数据只需 3 层根→中间→叶但若主键是VARCHAR(200)每页仅存 20 个键100 万行需 5 层。教材 P267 的示例用理想化数字但选择题常给具体字段类型和行数逼你估算。解决记住经验阈值InnoDB 下1000 万行以内B树通常 ≤4 层超过 1 亿行才需警惕深度带来的随机 IO 增加。遇到计算题先看字段类型——字符型主键层数必高于数值型。提示以上四坑在 PDF 第 6 章查询处理、第 7 章数据库恢复、第 8 章数据库安全的选择题中高频出现。建议把错题按“坑类型”归类而非按章节。5. 进阶技巧用 Python 自动解析 PDF 选择题构建个人错题知识图谱刷题的终极目标不是记住答案而是发现自己的知识断层。手动整理错题效率低、难关联。下面这个脚本能把 PDF 中所有选择题提取为结构化 JSON并自动标注考点章节、涉及的 DBMS 类型、易混淆概念对——让你一眼看出“为什么总在日志恢复和备份策略上反复错”。5.1 提取 PDF 文字并清洗为题目块使用pymupdf比pdfplumber更稳定处理教材 PDF 的页眉页脚# extract_questions.py import fitz # pip install PyMuPDF import re import json def extract_questions(pdf_path): doc fitz.open(pdf_path) all_text for page in doc: all_text page.get_text() \n # 教材 PDF 特征题干以数字点开头选项以 A./B./C./D. 开头 # 匹配模式\n1\. .*?(?\n\d\. |\Z) —— 从换行数字点开始到下一个同模式或文件末尾 pattern r\n(\d)\.\s(.*?)(?\n\d\.\s|\Z) questions [] for match in re.finditer(pattern, all_text, re.DOTALL): q_num int(match.group(1)) q_text match.group(2).strip() # 提取选项A. ... B. ... C. ... D. ... options {} opt_pattern r([A-D])\.\s(.*?)(?(?:[A-D]\.\s)|\Z) for opt_match in re.finditer(opt_pattern, q_text, re.DOTALL): opt_letter opt_match.group(1) opt_text opt_match.group(2).strip().replace(\n, ) options[opt_letter] opt_text # 粗略推断考点章节根据题干关键词 chapter_hint 未知 if 封锁 in q_text or 锁 in q_text or 死锁 in q_text: chapter_hint 第6章 并发控制 elif 日志 in q_text or 恢复 in q_text or 备份 in q_text: chapter_hint 第7章 数据库恢复技术 elif SQL in q_text and (CREATE in q_text or ALTER in q_text): chapter_hint 第4章 数据库安全性 questions.append({ number: q_num, text: q_text.split(A.)[0].strip(), # 去掉选项的题干 options: options, chapter: chapter_hint, dbms_hint: 通用 if SQL in q_text else SQL Server # 简单启发式 }) return questions if __name__ __main__: qs extract_questions(数据库系统概论第五版_选择题.pdf) with open(questions.json, w, encodingutf-8) as f: json.dump(qs, f, ensure_asciiFalse, indent2) print(f共提取 {len(qs)} 道题)执行效果生成questions.json内容形如{ number: 15, text: 事务的原子性是指, options: {A: 事务中包括的所有操作要么都做要么都不做, ...}, chapter: 第6章 并发控制, dbms_hint: 通用 }5.2 构建错题知识图谱用 NetworkX 可视化概念关联安装networkx和matplotlib运行以下代码# build_knowledge_graph.py import json import networkx as nx import matplotlib.pyplot as plt with open(questions.json, r, encodingutf-8) as f: questions json.load(f) # 定义概念映射根据你的错题手动维护 concept_map { 原子性: [事务, ACID], 封锁协议: [并发控制, 死锁, 两段锁], WAL: [日志, 恢复, 检查点], 间隙锁: [MySQL, 幻读, 可重复读] } G nx.Graph() # 添加节点章节、概念、DBMS for q in questions: G.add_node(q[chapter], typechapter, size1000) G.add_node(q[dbms_hint], typedbms, size500) # 关联概念需你标记错题后补充 if q[number] in [7, 15, 22]: # 假设这些是你错的题号 for concept, related in concept_map.items(): if concept in q[text] or any(concept in opt for opt in q[options].values()): G.add_node(concept, typeconcept, size800) G.add_edge(q[chapter], concept, weight2) for rel in related: G.add_node(rel, typerelated, size300) G.add_edge(concept, rel, weight1) # 绘图 plt.figure(figsize(12, 8)) pos nx.spring_layout(G, seed42) nx.draw_networkx_nodes(G, pos, node_size[d[size] for n, d in G.nodes(dataTrue)], node_color[{chapter:lightblue, dbms:lightgreen, concept:orange, related:pink}[d[type]] for n, d in G.nodes(dataTrue)]) nx.draw_networkx_labels(G, pos, font_size9) nx.draw_networkx_edges(G, pos, width1.5, alpha0.6) plt.title(我的错题知识图谱聚焦并发控制与恢复技术) plt.axis(off) plt.savefig(knowledge_graph.png, dpi300, bbox_inchestight) plt.show()效果生成一张图中心是“第6章 并发控制”向外辐射出“封锁协议”“死锁”“两段锁”再连到“间隙锁”“MySQL”——这直观告诉你你的薄弱点不是孤立的知识点而是“并发控制”这个模块下的多个子概念未打通。下次复习就集中火力攻这里。6. 最后一个血泪经验别在“SQL 语句去重”这种题上浪费时间真正该盯死的是执行计划里的“实际行数 vs 预估行数”PDF 里有至少 5 道题考DISTINCT、GROUP BY、ROW_NUMBER()去重写法区别。但现实工作中90% 的“去重慢”问题根源不在语法而在统计信息过期导致优化器选错执行计划。我曾帮一个金融客户优化一条报表 SQL他们花了两周争论该用DISTINCT还是GROUP BY最后发现UPDATE STATISTICS之后执行时间从 47 秒降到 1.2 秒。验证方法极其简单在 SQL Server Management Studio 中对任意查询按CtrlM显示实际执行计划重点看Clustered Index Scan或Index Seek算子的属性属性名含义健康值Actual Number of Rows真实返回行数与预估接近Estimated Number of Rows优化器预估行数与实际偏差 20%Warnings是否有“缺少统计信息”警告必须为 None如果Actual是 10 万Estimated是 100说明统计信息严重滞后优化器以为数据很少选择了嵌套循环Nested Loops结果实际要循环 10 万次——这就是“玄学慢”的真相。我的习惯每次拿到新业务 SQL第一件事不是改写而是右键表 → “统计信息” → “更新统计信息完全扫描”。这招在 PDF 第 9 章“查询优化”相关选择题中能帮你秒杀所有关于“为什么加了索引还走全表扫描”的题目。因为答案永远是“统计信息过期优化器误判了数据分布”。希望帮到你。本文还有配套的精品资源点击获取