简介南京大学中国大学MOOC《数据库开发技术》2023年课后章节答案与期末题库合集主要面向选修该课程的高校学生也适合备考数据库相关考试、希望系统梳理核心概念的技术人员。内容覆盖索引管理、SQL查询技巧、多表查询优化、并发控制、性能调优与数据库架构等高频考点既有选择题解析也包含易错知识点辨析可用于考前自测与查漏补缺。资源包为单个docx文档大小约15KB结构紧凑可直接查阅复制。目前已有125人学习下载。文档提炼了MyISAM不支持hash索引、性别等低基数列适合位图索引、CAST转换数据类型、concat遇NULL返回NULL、FLOAT与DOUBLE精度误区、多表查询需在关联表分别建索引、MVCC各存储引擎实现不同、读写分离并非设计SQL时必选项等要点并涉及左连接查找缺失数据、日期差值计算、软解析与硬解析等细节同时纠正了关系模型与分布式系统性能等常见误解能帮助读者快速把握考试重点提升对数据库开发技术的理解深度。1. 数据库开发技术这份题库为什么值得一份份过做数据库开发这些年我见过太多人拿着索引、事务、隔离级别的概念背得滚瓜烂熟一上手调优就翻车。这份南京大学中国大学MOOC的《数据库开发技术》课后章节答案与期末考试题库本质上是一份把教材里容易混淆的判断题和场景题全拎出来的避坑合集。和网上那种只给答案的文档不同它每道题都踩在一个真实的生产痛点上比如MyISAM到底支不支持hash索引、多表查询是不是只需要建一个索引、Oracle的rownum分页为什么经常查错人。适合正在刷MOOC期末考的学生也适合刚接手业务库、想系统补一遍数据库常识的工程师。我花了一下午把题目按主题重排了一遍发现它差不多就是一份「索引、查询优化、并发控制、范式设计」四件套的浓缩笔记每道题都能在线上环境里找到对应案例。2. 索引与查询优化先看懂BTree再谈要不要加索引2.1 为什么索引不总是加速器题库里反复出现索引相关题目第一道就是使用索引是为了检索大量数据这种说法是错误的。这个结论对新手有点反直觉但仔细想就通了索引真正的价值是在有序结构上进行快速定位而不是在数据量大的时候无脑加速。如果一张表只有几百行全表扫描可能比走索引还快因为回表的随机I/O开销完全能抵消掉索引带来的查找收益。索引更适合的是高选择性的查询条件比如按用户ID定位一条记录而不是按性别这种区分度极低的列。性别、婚姻状况这类低基数列题库给的答案是位图索引。它在Oracle里会比较合适因为位图索引用位运算来压缩存储同一个键值对应一个位图对低基数列的等值查询非常高效。但MySQL的InnoDB和MyISAM都不支持位图索引遇到这类列一般不建议建普通BTree索引性价比太低倒不如考虑在应用层做枚举映射或者分区裁剪。2.2 BTree索引的存储机制与count(*)的坑题库里有一道题问为什么大多数情况下select count() from T不会走B树索引答案是B树不会为空值构建索引。这个点特别值得展开。InnoDB的普通二级索引不存NULL值如果你对某列建了索引但列里有大量NULL那count()走索引统计出来的行数就和实际行数对不上。更准确地说InnoDB的count(*)会优先选择最小的二级索引来扫但如果统计精度要求高MySQL 8.0之前还是得回表或者扫主键索引。顺便说一句BTree和B-Tree的区别题库里也考了BTree的空间复杂度并不低于B-Tree。BTree的所有数据都落在叶子节点非叶子节点只存索引键和指针所以层数更矮、扇出更大适合磁盘存储。而B指的是balance平衡树不是binary。很多人面试的时候在这里翻车以为B树是二叉搜索树。2.3 索引创建的实践建议实际建索引的时候我一般会先看查询的WHERE条件和JOIN的关联字段遵循左前缀原则。比如联合索引(a, b, c)查询条件里如果只带b不带a那这个索引基本废了。还有一个点是外键题库里问外键不加索引可以吗答案是可以只要从表的数据几乎不被修改。这个结论的出发点在于如果从表是只读的主表删除或更新时不需要级联检查省掉外键索引能减少写放大。但如果你有ON DELETE CASCADE或者经常更新主表外键索引该建还是要建否则锁范围会扩大死锁概率直线上升。3. SQL编写习惯与多表查询少踩子查询的坑多用JOIN和绑定变量3.1 子查询与JOIN的性能边界题库里有道题问查询A表中某个字段不存在于B表中的数据怎么优化给了好几个选项包括用JOIN替代子查询、非关联子查询可以变成内嵌视图、打破范式增加冗余字段。这些说法都是对的。实际写SQL时NOT IN子查询如果子查询结果集很大很容易触发临时表和全表扫描。用LEFT JOIN加IS NULL的判断往往更稳因为优化器能更好地利用索引做anti-join。但JOIN也不是万能灵药题库里提到在 multi-table query 时只需要在其中一个表中建立索引即可是错误的。JOIN的驱动表和被驱动表都有各自的索引策略。小表驱动大表时被驱动表的关联字段必须建索引否则嵌套循环连接会把被驱动表扫一遍。这个点我在实际优化一个订单表和用户表的关联查询时验证过给orders表的user_id补上索引后查询耗时从1.8秒降到了120毫秒。3.2 排序、去重与类型转换的隐性开销DISTINCT这个关键字题库里说为了排除重复数据应尽可能使用distinct是错误的。原因是DISTINCT会触发排序或哈希去重在数据量大时开销很高。如果数据本身不会重复就不要加DISTINCT如果只是想知道有没有重复用EXISTS或者GROUP BY加COUNT更可控。还有一道题讲排序受数据量的线性影响是错误的排序的复杂度是O(n log n)数据量翻倍排序时间不是翻倍而是更多。这就是为什么大结果集排序要借助索引的有序性用索引避免filesort。另外CAST函数做类型转换时如果对索引列做强转比如WHERE cast(create_time as date) 2023-01-01索引就失效了。正确的写法是改参数create_time 2023-01-01 and create_time 2023-01-02。3.3 绑定变量与软解析Oracle的软解析和硬解析题库里考了好几道。本质是共享池里能不能复用已有的执行计划。能复用就是软解析不能复用就必须硬解析硬解析要重新生成执行计划消耗CPU和锁。用绑定变量是推动软解析的关键手段。但MySQL这边对绑定变量的处理不太一样MySQL 8.0的预处理语句也有缓存机制不过如果查询的字段分布严重倾斜绑定变量可能导致优化器选错执行计划。所以这个技巧要看数据库类型来用不能无脑套。4. 数据库设计原则与并发控制范式不是越高越好锁也不是越少越好4.1 范式与反范式的取舍逻辑关于数据库设计题库里出现频率很高的一个结论是数据库设计满足的范式级别越高数据库性能越好是错误的。第三范式可以消除大部分数据冗余但查询经常要join五六张表性能反而下降。解决思路是先按第三范式建模再针对高频查询路径做反范式化比如增加冗余字段或者冗余表这就是打破范式的意义。打破范式的前提是系统低修改性、高查询率而且要控制数据的一致性和完整性。我做过一个标签系统的表用户表和标签表之间频繁join后来直接把标签冗余到用户表的一个JSON字段里查询性能提升了三倍代价是更新标签时要手动维护JSON好在业务上标签变更频率极低。树状结构存储也是一个高频考点。邻接模型用一个parent_id字段物化路径模型用path字段存路径。邻接模型查询子树要递归物化路径模型用LIKE 1/2/%就能一次查出整个子树聚合操作的时候物化路径能用到中间结果集效率更高。但物化路径的问题是路径字符串会随着层级增长插入和移动节点需要重写路径。数据量大的时候关系型数据库存树本身就吃力该换NoSQL就换。4.2 并发控制与MVCC的边界高并发下会出现脏读、不可重复读、幻读、丢失更新这个没争议。MVCC是InnoDB解决读写冲突的核心机制但不同存储引擎对MVCC的实现并不一样所以题库里那句不同存储引擎对于MVCC的实现都是一样的是错的。InnoDB的MVCC靠undo log和read view实现MyISAM压根没有MVCC。MySQL的默认隔离级别是Repeatable ReadInnoDB用next-key locking解决幻读但如果你手动把隔离级别降到Read Committed幻读就会回来。关于锁有一个重要认知数据库加锁一定会考虑事务的隔离级别。隔离级别决定了锁的范围读未提交可能不加读锁可重复读要加间隙锁。另一个重要点Oracle不支持Read Uncommitted隔离级别而MySQL支持这是两者在实际行为上非常显著的差异。Oracle默认的隔离级别是Read Committed它的实现机制和多版本读一致性也和MySQL不一样。如果跨数据库迁移这条容易被忽略。4.3 外键与锁等待的工程经验题库里有一道题问在高并发的线上事务中几乎无法避免锁等待的产生这是对的。哪怕你每条SQL都很简单只要两个事务以不同顺序更新同一组记录就可能死锁。死锁产生的必要条件题库里明确指出不包括先来先服务条件。死锁需要互斥、持有并等待、不可剥夺、循环等待四个条件。工程上减少死锁的办法是固定更新顺序比如先更新用户表再更新订单表所有事务都按这个顺序执行循环等待就断了。还有一个实践是缩小事务范围把不必要的查询移出事务减少锁持有的时间。事务粒度太大导致锁等待的线上案例我遇到过很多。改造的通用思路是把一个大事务拆成多个小事务每个事务只做一件事事务之间允许中间状态存在再配合消息队列做最终一致性。这里就涉及题库里提到的合理使用消息队列异步处理任务和应用层使用合适的数据库连接池这些都是降低数据库压力的有效手段。5. 避坑与常见问题排查分页、存储引擎、日期函数那些事5.1 Oracle分页陷阱题库里最经典的一道场景题就是Oracle查不是经理的员工中收入最高的五个人第一条SQL写成了先在WHERE里加rownum 5再ORDER BY salary desc结果返回的是最先查到的五条记录再排序完全不是预期结果。正确写法是先排序再取rownum也就是select * from (select ... order by salary desc) where rownum 5。这个坑的本质是rownum在WHERE阶段就生成先于ORDER BY执行所以不能直接在带排序的查询上套rownum。MySQL有LIMITSQL Server有TOPOracle只能用子查询嵌套。数据库方言差异在这里体现得很直接。5.2 聚合函数与GROUP BY的隐含条件有一道题问select goods_name, goods_number from sw_goods having goods_price 100为什么不能执行。原因是SELECT列表里没有goods_price而HAVING子句作用于分组后的结果集要求所有引用的列要么在GROUP BY里要么在聚合函数里否则数据库不知道拿哪个值来过滤。这条SQL如果改成WHERE goods_price 100就能跑了因为WHERE在分组前过滤。当没有聚合函数、没有多种条件选择时用JOIN比使用子查询更好这条经验在HAVING和WHERE的选择上也有类似逻辑能提前过滤就提前过滤。5.3 datetime与timestamp的细微差异MySQL里datetime和timestamp题库问哪个区别是错的错误说法是datetime的默认值为not null。实际区别是timestamp存储的是UTC时间戳会随会话时区变化显示datetime存储的是字面时间不随会话时区变化。timestamp的范围是1970到2038年datetime的范围大得多。还有一个点timestamp在插入时如果没赋值会自动用当前时间填充而datetime默认是NULL。所以如果你需要记录创建时间timestamp会更省心但要注意2038年问题。DATE格式的分隔符也容易被误解MySQL的DATE接受很多宽松格式不是必须用横线分隔。5.4 concat与NULL的传播concat(aaa, null, bbb)的结果是NULL这个很多人知道但未必知道背后的原因SQL的NULL传播特性任何与NULL做运算的结果都是NULL。与此相关的是count(*)返回所有行的数目包括NULL值而count(col)只统计非NULL值。SUM函数在没有匹配行时返回NULL而不是0所以做报表时sum可能返回NULL这时候要配合IFNULL或者COALESCE处理。abs()函数返回绝对值这个倒是很直接。5.5 排序字段与索引失效的边界有一道题说如果表T使用B树构建了索引但select count(*) from T不会走这个索引原因前面已经讲过是NULL值不入索引。实际上还有一个相关场景如果查询条件是WHERE name IS NULL二级索引也不会被使用因为索引里没有NULL条目。这种情况下优化器只能走全表扫描。这就是为什么设计表结构时建议把所有字段都设置成NOT NULL并给默认值从源头避免索引失效。排查这类问题我一般会在执行计划里看一眼possible_keys和key如果possible_keys有值但key是NULL说明优化器评估后放弃了索引。这时候检查三个东西一是列上有没有隐式类型转换二是列上有没有函数运算三是统计信息是不是过期了。分析完再决定是改SQL还是重新ANALYZE TABLE不要一上来就加索引。6. 从题库到实战把错题变成你的SQL肌肉记忆把这份题库过完一遍之后最有价值的动作不是背答案而是把每道做错的题改写成一个可以在本机MySQL里执行的验证脚本。比如Oracle的rownum分页你在MySQL里用limit模拟不出来但可以在MySQL里验证HAVING的问题建一张表插入几行数据试试糟糕的HAVING写法看报错信息是什么样的。我自己喜欢的方式是准备一个干净的docker容器装一个MySQL 8.0和一个PostgreSQL把题库里涉及方言差异的题目都跑一遍比如concat对NULL的处理、日期函数的格式、字符串转数字的cast行为。另有一个适合自己的做法把文档里容易忘的结论做成一个速查表放在常用代码段里每次写SQL前扫一遍。比如# 数据库问题排查清单 # 1. 查询慢先看EXPLAINpossible_keys和key是否一致 # 2. WHERE条件里有函数或隐式转换索引大概率失效 # 3. ORACLE分页永远先排序再取rownum # 4. 低基数索引选位图MySQL不支持就用普通BTree且谨慎 # 5. JOIN时被驱动表关联字段必须有索引 # 6. concat与NULL操作结果为NULL聚合时用IFNULL兜底 # 7. 高并发下按固定顺序更新记录降低死锁概率 # 8. 打破范式只适用于低修改性、高查询率的场景再往前一步的做法是把题库当成测试用例。比如索引相关题目建一张100万行的测试表分别测试有索引和无索引时等值查询、范围查询、count(*)的耗时差异用真实数据扭转索引永远好用的直觉。事务隔离级别相关的题则用两个终端会话一边开事务写数据一边开另一个会话去读观察不同隔离级别下看到的快照是否符合预期。这些实验做完那些判断题就不再是背诵项而是自己验证过的结论。还有一件事我会提醒自己不同数据库的黑盒经验并不完全互通题库里那道在一个数据库上取得的经验无法应用到另一个数据库上说法是错误的恰恰说明成熟的数据库设计者都会提炼通用原则但具体实现上必须回归官方文档。比如Oracle和MySQL对隔离级别的实现细节就不同你拿着Oracle的分页写法去写MySQL会报语法错误拿着MySQL的replace into去跑Oracle就是另一套语义。每切换到一种新数据库都要用最小验证脚本确认边界行为。从那以后我每接触一份数据库题库或者手册都强制自己先把它改写成可执行的验证脚本再入库光看不练的内容基本留不住。刷这份MOOC题库也一样光记答案只能应付考试只有亲手建表、插数据、看执行计划才能把那些易混淆的判断转成自己的SQL肌肉记忆。希望帮到你。本文还有配套的精品资源点击获取