简介一份面向南京大学中国大学MOOC《数据库开发技术》课程学员的章节答案与期末考试题库尤其适合正在备考或希望系统梳理数据库开发核心概念的学习者。内容覆盖索引管理、SQL查询优化、数据类型转换、多表连接、并发控制、MVCC机制、性能调优及数据库部署等高频考点通过选择题和判断题形式呈现每题均附参考答案部分题目还提供了简明的易错点说明。资源为单个docx文档共1个文件压缩包大小约15KB轻量便于在移动端或电脑上随时查看。目前已有125人学习下载题库中不仅包含“CAST转换”“concat函数”“FLOAT与DOUBLE精度”等基础细节也涉及“读写分离”“软解析与硬解析”“大尺度问题”等进阶话题可帮助读者快速检验知识掌握程度、查漏补缺为期末复习提供有效支撑。1. 为什么数据库开发技术总在MOOC期末翻车先看清这门课考什么当你第一次搜索“数据库开发技术 南京大学 中国大学MOOC”时大概率会看到类似《数据库开发技术_南京大学中国大学mooc课后章节答案期末考试题库2023年.docx》这样的资源。先别急着把它当作救命稻草。我带过不少跟这门MOOC的人最常见的翻车模式是课后题刷了三遍答案背得滚瓜烂熟一进期末上机房建表语句都写不利索。问题不是不努力而是把“记住答案”当成了“学会开发”。数据库开发技术这门课考的是建库、建表、事务、索引、存储过程这些能真实跑起来的技能不是背几个概念就能过关。这篇笔记就顺着这门课的典型考核路径从最小可运行的建表开始一直写到索引调优和存储过程把课后题和期末题库背后的考点拆成你能照做的步骤。2. 从建表到事务用最小示例跑通数据库开发的核心闭环2.1 三范式是课后题第一关先建一张能通过检查的成绩表数据库开发技术的课后题几乎必有一道“把成绩表拆成符合三范式的结构”。这里的坑不是不会拆而是拆过头。常见做法是学生表、课程表、成绩表三张表但很多人把成绩表的主键设成自增id丢掉了学号和课程号的联合唯一约束结果同一门课可以插入两条成绩第二天就被助教点名。我一般会建议先按第一范式1NF列原子字段再按第二范式2NF消除部分依赖最后按第三范式3NF消除传递依赖。以下是最小可运行的建表脚本用MySQL语法CREATE DATABASE IF NOT EXISTS school DEFAULT CHARACTER SET utf8mb4; USE school; CREATE TABLE student ( student_id VARCHAR(20) NOT NULL COMMENT 学号, student_name VARCHAR(50) NOT NULL COMMENT 姓名, class_no VARCHAR(20) COMMENT 班级号, PRIMARY KEY (student_id) ) ENGINEInnoDB COMMENT学生表; CREATE TABLE course ( course_id VARCHAR(20) NOT NULL COMMENT 课程号, course_name VARCHAR(100) NOT NULL COMMENT 课程名, credit DECIMAL(3,1) NOT NULL DEFAULT 1.0 COMMENT 学分, PRIMARY KEY (course_id) ) ENGINEInnoDB COMMENT课程表; CREATE TABLE score ( student_id VARCHAR(20) NOT NULL COMMENT 学号, course_id VARCHAR(20) NOT NULL COMMENT 课程号, score DECIMAL(5,2) NOT NULL DEFAULT 0 COMMENT 成绩, exam_date DATE COMMENT 考试日期, PRIMARY KEY (student_id, course_id), CONSTRAINT fk_score_student FOREIGN KEY (student_id) REFERENCES student(student_id) ON DELETE CASCADE, CONSTRAINT fk_score_course FOREIGN KEY (course_id) REFERENCES course(course_id) ON DELETE CASCADE ) ENGINEInnoDB COMMENT成绩表;这段脚本的逻辑核心在score表的联合主键PRIMARY KEY (student_id, course_id)。它保证了同一个学生同一门课只能有一条成绩记录这是合法业务逻辑的底线。外键fk_score_student和fk_score_course是第二层保护防止你插入一个不存在的学号或课程号。参数方面需要注意ENGINEInnoDB因为InnoDB支持事务和外键约束而默认的MyISAM不支持。DEFAULT CHARACTER SET utf8mb4是为了让中文正常存储避免课堂上老师演示时不报错、你本地跑起来变成问号。课后题如果要求“把成绩表拆成三张表”一般就是把上面的student、course、score拆开。你还要能解释为什么student_id和course_id不能直接当作成绩表的一列放一起因为那样会产生部分依赖班级号只依赖学号不依赖课程号。把这些写在答案里分数通常不会低。2.2 主键、外键和唯一约束三个最容易让插入语句翻车的对象课后题里有一类实验题给出几个INSERT语句问你哪条会失败。这类题的考点就是约束。很多人只在建表时写了主键忽略了外键和唯一约束导致程序在运行时插入脏数据最后报表对不上号。常见做法是在应用层插入前先用子查询检查外键是否存在但更稳的做法是让数据库自己把关。你可以把上一节的成绩表增加一个唯一约束防止重复录入补考成绩ALTER TABLE score ADD UNIQUE KEY uk_student_course (student_id, course_id);这条语句给score表加了一个唯一索引。注意如果你的score表已经定义了联合主键(student_id, course_id)再加唯一约束会覆盖建议直接用主键即可。这里要说明的是当你在应用中执行INSERT INTO score (student_id, course_id, score) VALUES (1001, C001, 95); INSERT INTO score (student_id, course_id, score) VALUES (1001, C001, 60);第二条会报Duplicate entry 1001-C001 for key PRIMARY。这是数据库在保护你的业务规则不是Bug。很多新手第一次看到这个错误第一反应是“数据表坏了”其实只要检查一下主键或唯一约束就行。你在做课后题时如果题目问“如何防止成绩重复录入”答案有两种应用层先SELECT判断或者数据库层加唯一约束。数据库层是最不容易被程序Bug绕过的方案这也是为什么学数据库开发技术不能只写一遍SELECT。2.3 事务的四种隔离级别概念题和实操题都绕不开期末题库里有一类选择题给你两个事务并发操作问最终结果。如果只背“读未提交、读已提交、可重复读、串行化”这四个名字很容易答错。你需要把每种隔离级别和它允许的并发现象对应起来。最常见的考核场景是连接A开启事务写一行数据但不提交连接B去读同一行。在READ UNCOMMITTED下B能看到A未提交的数据这叫脏读。在READ COMMITTED下B看不到未提交数据但A一旦提交B再读就能看到新值这叫不可重复读。在REPEATABLE READ下B在同一事务里多次读取结果一致不管A是否提交但可能产生幻读——B查询某个范围A插入了新行B再查时多了一行。SERIALIZABLE完全串行化所有问题都没有但并发性最差。你可以用两条SQL语句在MySQL里验证-- 会话1 START TRANSACTION; INSERT INTO score (student_id, course_id, score) VALUES (1002, C002, 80); -- 先不COMMIT -- 会话2 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; START TRANSACTION; SELECT * FROM score WHERE student_id 1002;如果会话1没有提交会话2照样能查到1002那条记录说明当前隔离级别允许脏读。把会话2改成SET TRANSACTION ISOLATION LEVEL READ COMMITTED;再试就查不到这正是读已提交的语义。这里有一个参数容易被忽略MySQL默认隔离级别是REPEATABLE READ但很多云数据库默认改成了READ COMMITTED。你做课后题前先执行SELECT tx_isolation;确认当前环境否则实验结果会和书上的结论对不上。这个动作是我从教训里学来的曾经因为没查环境把可重复读的结果解释成脏读被自己坑了一晚。3. 索引设计期末题库里分值最高的一类题3.1 单列索引和联合索引最左前缀原则到底怎么用数据库开发技术课的期末题最后两道大题经常是“给一个表让你设计索引”或“解释为什么某条查询慢”。这里最核心的考点是联合索引的最左前缀原则。很多人把多个字段拆成多个单列索引以为都能提速结果执行计划里一个都没用上。最左前缀原则说的是联合索引(a, b, c)可以被a、a,b、a,b,c三种查询条件使用但b,c或a,c不会完整使用只能在a上走索引c还得回表过滤。举个例子成绩表上经常要查“某学生的某门课成绩”你可以建联合索引ALTER TABLE score ADD INDEX idx_student_course_date (student_id, course_id, exam_date);建完索引后如果查询写的是SELECT * FROM score WHERE student_id 1001 AND course_id C001;这个查询会走idx_student_course_date索引因为条件命中最左前缀student_id, course_id。但如果查询只写SELECT * FROM score WHERE course_id C001 AND exam_date 2024-01-01;这个查询无法使用该索引因为条件里没有包含student_id。这就是为什么很多同学建了索引执行计划显示还是全表扫描——不是索引没用是查询条件没按最左前缀写。3.2 explain输出里的type和key判断索引有没有生效期末操作题里老师会让你分析一条慢查询。你要是只回答“加索引”三个字得不了几分。正确做法是用EXPLAIN看执行计划从type和key两个字段判断索引是否生效。EXPLAIN SELECT * FROM score WHERE student_id 1001 AND course_id C001;如果结果里的type是const或ref说明索引生效。如果type是ALL说明全表扫描。key显示的是实际用到的索引名key_len是索引长度。课后题如果问你“为什么这个查询不走索引”答案常见有三种第一查询条件没有开头的联合索引字段第二对索引列用了函数或计算比如WHERE YEAR(exam_date) 2023第三like条件以通配符开头比如WHERE course_name LIKE %数据库%。type字段的值从快到慢大致是system-const-eq_ref-ref-range-index-ALL。其中index虽然是扫索引但比ALL快不少。写题时看到typeALL基本可以断定“这条SQL该优化了”。看到typeref说明用到了索引但可能有多个匹配行这在成绩表场景很正常。3.3 覆盖索引和回表用字段列表减少一次磁盘读还有一个容易被忽略的考点如果你只查索引中的列数据库就不用回表。这个技巧叫覆盖索引。期末题里常有“优化如下查询SELECT student_id, course_id FROM score WHERE student_id 1001;”。如果用(student_id, course_id)的复合索引直接返回索引里的字段不会去主表读数据。CREATE INDEX idx_student_course ON score (student_id, course_id);这个索引同时支持了最左前缀和覆盖索引。当你执行EXPLAIN SELECT student_id, course_id FROM score WHERE student_id 1001;执行计划里的Extra字段会显示Using index说明覆盖索引生效。很多人在写优化答案时只记得WHERE列要建索引忘了SELECT列也要包含在索引里。如果把查询改成SELECT score那就会多一步回表Extra里会出现Using where; Using index或者没有Using index。这里有个参数选择索引不是越多越好。你每建一个索引插入和更新时都要额外维护B树。课后题如果问“设计索引时要不要给每个字段都建索引”标准答案是只给高频查询和排序字段建。你把覆盖索引和回表的关系讲清楚期末大题丢分会少很多。4. 存储过程与触发器南京大学MOOC期末代码题的隐藏考点4.1 存储过程的参数模式IN、OUT、INOUT用错会怎样当课后题开始出现存储过程时说明课程进入后半段。很多人写存储过程语法都对但参数模式用错。MySQL存储过程的参数有三种模式IN表示调用方传入值过程内部不能改OUT表示过程往调用方输出值传入时忽略INOUT既能传进又能传出。看一个典型例子写一个根据学号和课程号返回成绩等级的存储过程DELIMITER // CREATE PROCEDURE get_score_level( IN p_student_id VARCHAR(20), IN p_course_id VARCHAR(20), OUT p_level VARCHAR(10) ) BEGIN DECLARE v_score DECIMAL(5,2); SELECT score INTO v_score FROM score WHERE student_id p_student_id AND course_id p_course_id; IF v_score 90 THEN SET p_level A; ELSEIF v_score 60 THEN SET p_level B; ELSE SET p_level C; END IF; END // DELIMITER ;DELIMITER //的作用是把;分隔符临时改成//不然CREATE PROCEDURE内部每个分号都会被客户端当成语句结束。这是一个典型的坑很多人在命令行直接粘贴结果只创建了一个空过程。另一边OUT p_level必须在过程内赋值调用时你要用一个会话变量接收比如CALL get_score_level(1001, C001, level); SELECT level;如果你把OUT写成IN调用时传一个常量过程内想给它赋值会直接报语法错误。如果写成INOUT调用前必须初始化变量否则会出现NULL。期末题里如果给你一段存储过程代码问参数模式选哪个最合适记得看过程内部有没有对参数做修改。只读就是IN写出去就是OUT又要带进来又要带出去就是INOUT。4.2 循环和游标批量更新成绩时别把集合操作写成逐行存储过程里另一个考点是循环。很多同学习惯用游标逐行处理但数据库本身是集合操作的能用一条UPDATE解决就别写游标。比如把不及格的成绩统一加5分标准做法是UPDATE score SET score score 5 WHERE score 60;但课后题非要让你用存储过程实现这时你可以用WHILE循环配合一个临时变量。有一个边界条件要特别注意如果加到60分以上你还会继续加吗真实业务逻辑往往是“最多补到60分”。写成存储过程DELIMITER // CREATE PROCEDURE pass_fail_fix() BEGIN DECLARE done INT DEFAULT 0; DECLARE v_student_id VARCHAR(20); DECLARE v_course_id VARCHAR(20); DECLARE cur CURSOR FOR SELECT student_id, course_id FROM score WHERE score 60; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done 1; OPEN cur; read_loop: LOOP FETCH cur INTO v_student_id, v_course_id; IF done THEN LEAVE read_loop; END IF; UPDATE score SET score LEAST(score 5, 60) WHERE student_id v_student_id AND course_id v_course_id; END LOOP; CLOSE cur; END // DELIMITER ;这段代码用LEAST(score 5, 60)保证了加分上限。DECLARE CONTINUE HANDLER FOR NOT FOUND SET done 1;是游标结束的标准写法漏了它会导致死循环。实际做题时如果你能用一条UPDATE解决写游标反而容易被扣分因为性能差。老师问“为什么不用UPDATE”你要能回答游标适合处理不能一条SQL表达的行间逻辑比如逐行比较前后差值。4.3 触发器建完不生效前置条件比语法更常出问题触发器是期末题库里的冷门考点但它一旦出现就爱考“为什么触发不了”。最常见的现象是建了INSERT触发器往表里插数据结果触发器里的日志表没有变化。原因通常是FOR EACH ROW语法问题或者触发器里用了不允许的语句。MySQL里触发器语法DELIMITER // CREATE TRIGGER trg_score_insert AFTER INSERT ON score FOR EACH ROW BEGIN INSERT INTO score_log (student_id, course_id, old_score, new_score, op_time) VALUES (NEW.student_id, NEW.course_id, NULL, NEW.score, NOW()); END // DELIMITER ;很多人的触发器看起来没问题但执行插入后日志表没数据。排查顺序是第一步查表名是否写错第二步查触发器是否在正确的库下第三步查NEW.字段名是否和score表的列名一致。还有一个隐蔽问题触发器所在表如果之前已经存在数据触发器不会补历史记录只对触发之后的操作生效。期末题如果让你“给score表加一个历史记录触发器”你要知道历史数据需要手动迁移不是建完触发器就自动补。另外MySQL的触发器不能对同表做递归操作。比如在score表的AFTER UPDATE触发器里再UPDATE score会报Cant update table score in stored function/trigger because it is already used by statement which invoked this stored function/trigger。这个错误信息本身就写在题库里考的就是你知不知道“递归触发限制”。5. 避坑数据库开发技术课后题里常见的5个陷阱5.1 陷阱一DELETE和TRUNCATE功能等价吗现象课后题问“删除表中所有数据DELETE和TRUNCATE有什么区别”很多人回答“都一样”。但期末实操时用TRUNCATE删数据再想用事务回滚发现回滚不了。原因DELETE是DML语句逐行删除会记录日志可以在事务里ROLLBACK。TRUNCATE是DDL语句直接重建表结构不逐行记录日志一旦执行就不能回滚。另一个区别是自增列DELETE不重置自增计数器TRUNCATE会重置。解决如果只是清空测试数据且不需要回滚可以用TRUNCATE速度快很多。但如果题目考查“删除部分数据”或“可恢复”必须用DELETE并且带上WHERE条件。写答案时先说明两者都是删除再点明事务支持、日志、自增和速度四个维度基本能拿满分。5.2 陷阱二COUNT(*)和COUNT(列)结果不一样现象统计成绩表行数SELECT COUNT(*) FROM score返回20SELECT COUNT(score) FROM score返回18。有人以为是Bug。原因COUNT(*)统计所有行包括NULL值COUNT(列名)只统计该列不为NULL的行。scott表里如果有两行score为NULL结果就差2。这也是一道经典期末选择题很多人只看“函数都是计数”忽略NULL处理。解决需要统计记录条数时直接用COUNT()不需要担心NULL。需要统计某字段实际有值数量时用COUNT(字段)。如果题目问“为什么COUNT(列)比COUNT()少”答案就是“该列存在NULL值”。你还可以进一步用COUNT(DISTINCT student_id)统计不重复学生数这也是常考变体。5.3 陷阱三外键约束导致插入失败时你首先看什么现象往score表插入成绩报foreign key constraint fails。很多人的第一反应是“数据表坏了”然后开始重启数据库。原因插入的student_id或course_id在student表或course表里不存在。外键约束就是来防这个的。解决先执行SELECT * FROM student WHERE student_id 1001;确认父表有没有这条记录。如果父表有再看子表外键字段的值是否有肉眼不可见的空格或全角字符。我自己踩过这个坑程序里传了带尾随空格的学生号数据库里没有报外键错误排查了两个小时。解决方法是先用TRIM()处理输入或者在应用层校验。课后题如果问“外键约束的作用”标准答案是保护引用完整性而不是“多建一张表”。5.4 陷阱四隔离级别设成READ UNCOMMITTED会读到什么现象把会话隔离级别改成READ UNCOMMITTED后查询发现结果和期末考试答案不一致以为题目错了。原因READ UNCOMMITTED允许脏读你能读到其他事务未提交的数据。很多教材里都不会让你在生产环境用它但课堂上为了演示却常用它。如果你不小心把整个会话的隔离级别改了后面所有查询都会看到未提交的中间状态。解决每次做实验前先检查当前隔离级别用SELECT transaction_isolation;。设置隔离级别时默认只对下一个事务生效但如果你用SET GLOBAL改了全局所有新连接都会被改掉。一个安全的做法是每次实验结束恢复默认值SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;如果你在Navicat之类的图形工具里连接一定要新开一个连接再验证因为旧连接的会话级隔离级别不会自动重置。5.5 陷阱五存储过程里变量名和列名重名时谁说了算现象存储过程里写SELECT score INTO score FROM score WHERE student_id p_student_id;结果变量被改成NULL或者报错“列不唯一”。原因变量名和字段名重复MySQL会优先把score解释成字段导致INTO子句无法区分。这是一个命名规范问题考试时经常会给你一段故意重名的代码让你判断会不会报错。解决存储过程的参数和局部变量统一加前缀参数用p_局部变量用v_这个习惯能避免九成以上的命名冲突。如果题目已经给了重名代码你就从“作用域和优先级”的角度回答MySQL在解析时局部变量优先于字段名但在SELECT的INTO目标里可能产生歧义。最稳妥的改进方法是改成v_scoreDECLARE v_score DECIMAL(5,2); SELECT score INTO v_score FROM score WHERE ...;这一条虽然简单但在上机考试里特别管用。老师批改时会看你的存储过程有没有歧义而不是只看能不能跑出结果。6. 用一张成绩表把知识点串成闭环我的收尾习惯与验证方法学数据库开发技术最怕的是把每个知识点当独立碎片。我留给自己最后的练习方法是把一张成绩表从头到尾重新“开发”一遍不借助任何答案。具体做法是新建一个空白数据库自己写学生表、课程表、成绩表建联合主键和外键然后插入测试数据执行一次事务用ROLLBACK验证回滚给查询建索引用EXPLAIN确认type从ALL变成ref写一个存储过程批量更新不及格成绩最后建一个触发器记录更新历史。整个过程半小时不到但能把建表、事务、索引、存储过程、触发器全串起来。期末上机前我习惯再做一件事把每一条SQL语句的错误信息截图按错误码整理成一张表。比如1054是字段不存在1062是唯一键冲突1452是外键失败。这些错误码不背遇到时查一查也能定位但提前归纳会快很多。记住上机考试考的是“你遇到报错能不能自己解决”而不是“你背了多少答案”。如果你在MOOC上拿到的课后答案是PDF或docx请把它当作参考而不是圣旨。因为数据库环境的版本、字段类型、字符集都会影响运行结果。我吃过最大的亏是照着一份老答案写ENGINEMyISAM结果老师要求演示事务怎么回滚都没效果。从那以后我先看SELECT VERSION();再看tx_isolation最后才动手写SQL。希望这个过程能帮你少踩一点我当年踩过的坑祝你在数据库开发技术上顺利过关。本文还有配套的精品资源点击获取