简介:这份资源是山东科技大学信息科学与工程学院《数据库系统概论》课程设计的实验报告,面向计算机科学与技术专业学生及需要完成类似DBMS实践任务的学习者。报告围绕“实现一个简单的DBMS——创建表和修改表”展开,完整记录了从需求分析、设计思想到程序实现的全过程,涵盖CREATE TABLE与ALTER TABLE语句的解析、表物理存储结构设计、命令行与图形化界面两种交互方式,以及“新建表”“修改表”“显示已建表信息”“保存并退出”四个核心模块的实现思路。资源包内为1个doc文档,约408KB,内容包含课程设计任务书、设计要求、需求分析、设计思想、程序流程图及关键代码说明,并列出《数据库系统概论(第四版)》《C程序设计(第三版)》等参考资料。目前已有931人学习下载,适合作为数据库课程设计参考、实验报告撰写范例及DBMS入门实践的学习材料。
1. 数据库系统概论课程设计:从选题到落地的完整拆解
很多同学拿到“数据库系统概论课程设计”这个任务时,第一反应是打开某本书的课后习题,或者去搜一份现成的模板改改交差。但真正做过的人都知道,课程设计跟平时作业完全是两码事——它要求你从需求分析开始,独立完成概念设计、逻辑设计、物理设计,最后还要把系统跑起来,能增删改查、能处理并发、能给出测试数据。我带过几届学生的课程设计,也帮不少朋友看过他们的实验报告,发现一个很普遍的现象:大部分人卡住的地方根本不是SQL写不出来,而是不知道一个“完整的数据库系统”到底该包含哪些东西,以及每一步该输出什么文档、用什么工具、踩什么坑。这篇笔记就按一线开发的思路,把数据库系统概论课程设计从选题到答辩的完整路径拆开,每一步都给出可复现的操作和参数说明。适合正在做课设的本科生、需要快速搭一个数据库原型的研究生,以及想系统梳理数据库设计流程的开发者。
2. 选题与需求分析:别一上来就建表
2.1 课程设计选题的三个筛选标准
选题决定了后面所有工作的难度上限。我一般建议从三个维度筛:数据实体数量、业务逻辑复杂度、你手头能拿到的数据。实体数量控制在5到8个之间最舒服——少于5个,E-R图太单薄,体现不出关系设计能力;多于8个,光是画图就能耗掉你一半时间,而且很容易在范式分解时翻车。业务逻辑复杂度看的是有没有多对多关系、有没有层次结构、有没有需要事务保证的操作。比如“学生选课系统”有学生、课程、教师、选课记录四个核心实体,选课记录本身就是一个多对多关系的桥接表,还能延伸出成绩、排课冲突等逻辑,复杂度刚好。“图书管理系统”也类似,但要注意别选“电商系统”——订单、支付、物流、库存、用户、商品、评价,实体太多,一个人做不完。
第三个标准最容易被忽略:数据从哪来。课程设计通常要求你插入测试数据,至少每张表几十条。如果你选了一个需要真实地理信息或者行业数据的题目,光造数据就能让你崩溃。我一般会建议选那些可以程序化生成数据的题目,比如用Python脚本批量生成学生姓名、课程名、成绩,这样后面做性能测试也方便。
注意:选题时一定要确认学校有没有指定题目范围。有些老师会给定几个方向让你选,有些则完全自由。如果自由选题,尽量避开“社交网络”“推荐系统”这类听起来高大上但数据模型极其复杂的题目,课设的重点是数据库设计,不是算法。
2.2 需求分析要产出什么:数据字典与数据流图
需求分析阶段的核心产出是数据字典和数据流图。数据字典不是简单列个表名和字段,而是要写清楚每个数据项的名称、含义、类型、长度、取值范围、是否为空、默认值、约束条件。我见过很多实验报告的数据字典只写了“学号 char(10)”,这远远不够。完整的写法应该是:
| 数据项名 | 含义 | 类型 | 长度 | 取值范围 | 空值 | 默认值 | 约束 |
|---|---|---|---|---|---|---|---|
| stu_id | 学号 | char | 10 | 首位为字母,后9位数字 | 否 | 无 | 主键 |
| stu_name | 姓名 | varchar | 20 | 2-20个字符 | 否 | 无 | 无 |
| gender | 性别 | char | 1 | 男/女 | 是 | 男 | 无 |
数据流图(DFD)用Visio或Draw.io画,顶层图一张,0层图一张,1层图按主要功能展开。画的时候注意数据流的方向和加工过程的编号,这些细节在答辩时老师会看。如果你不想画得太复杂,至少保证顶层图和0层图是完整的,1层图可以只展开核心功能。
需求分析做完后,写一份简短的说明文档,把系统要解决的问题、涉及的角色、主要功能列出来。这份文档后面写实验报告时可以直接用。
3. 概念设计与逻辑设计:E-R图到关系模式的完整转换
3.1 用Chen氏表示法画E-R图并标注基数
概念设计阶段的核心是E-R图。我推荐用Chen氏表示法:矩形表示实体,椭圆表示属性,菱形表示联系,连线标注1、n、m。工具用Draw.io或者Visio都行,但要注意导出图片的分辨率,别到报告里糊成一团。画E-R图时最容易出错的地方是基数标注。比如“学生”和“课程”之间是选课联系,一个学生可以选多门课,一门课可以被多个学生选,所以是m:n。但“教师”和“课程”之间,如果规定一门课只有一个主讲教师,那就是1:n。这些基数直接决定了后面关系模式的数量。
画完E-R图后,检查有没有冗余联系。比如“学生”和“班级”之间已经有“属于”联系,“班级”和“院系”之间有“归属”联系,那“学生”和“院系”之间就不需要再画一个“属于”联系,否则会产生传递依赖。这个检查能帮你省掉后面范式分解的很多麻烦。
3.2 E-R图转关系模式的五条规则与一个实例
转换规则其实就五条,但很多人记不住或者用错。我把它总结成一张表:
| 联系类型 | 转换方法 | 例子 |
|---|---|---|
| 1:1 | 合并到任意一端,加入另一端主键和联系属性 | 班级与班长:班长信息并入班级表 |
| 1:n | 合并到n端,加入1端主键和联系属性 | 院系与班级:院系号加入班级表 |
| m:n | 独立建表,主键为两端主键组合,加入联系属性 | 学生与课程:选课表(学号,课程号,成绩) |
| 多元联系 | 独立建表,主键为各端主键组合 | 供应商-项目-零件:供应表 |
| 自反联系 | 按1:1、1:n、m:n分别处理 | 员工与经理:经理号加入员工表 |
拿“学生选课系统”举例。实体有学生、课程、教师。联系有:学生选课(m:n,属性成绩)、教师授课(1:n,一个教师教多门课)、学生属于班级(n:1)。转换后的关系模式:
- 学生(学号, 姓名, 性别, 出生日期, 班级号)
- 班级(班级号, 班级名, 院系号)
- 院系(院系号, 院系名)
- 课程(课程号, 课程名, 学分, 教师号)
- 教师(教师号, 姓名, 职称)
- 选课(学号, 课程号, 成绩)
这里选课表的主键是(学号, 课程号),成绩是联系属性。注意外键:学生表的班级号引用班级表,课程表的教师号引用教师表,选课表的学号和课程号分别引用学生表和课程表。
3.3 用函数依赖做3NF分解的实操步骤
逻辑设计的最后一步是规范化。课程设计一般要求达到3NF,有些老师会要求BCNF。我一般用函数依赖来检查。先写出每个关系模式的所有函数依赖,然后看有没有部分依赖和传递依赖。
以选课表(学号, 课程号, 成绩)为例,函数依赖是(学号, 课程号)→成绩,没有部分依赖也没有传递依赖,已经是3NF。但如果把学生姓名也放进去,变成(学号, 课程号, 姓名, 成绩),就有学号→姓名,这是部分依赖,需要把姓名拆回学生表。
分解的步骤:先找候选键,然后看非主属性对候选键的依赖类型。如果是部分依赖,就拆成两个关系;如果是传递依赖,也拆成两个关系。拆完后检查无损连接和保持函数依赖。无损连接的判断方法是:两个关系模式的交集是其中一个的候选键,则分解是无损的。保持函数依赖的判断稍微麻烦,一般课程设计做到3NF就够,不用太纠结。
提示:分解后一定要把外键关系标清楚,否则后面建表时容易漏掉参照完整性约束。我一般会在关系模式下面用注释写出外键引用,比如“班级号 引用 班级(班级号)”。
4. 物理设计与建库建表:从SQL脚本到索引优化
4.1 用SQL脚本建库建表的完整模板
物理设计阶段就是把关系模式变成实际的SQL。我一般会写一个完整的建库脚本,包含建库、建表、主键、外键、索引、注释。下面是一个模板:
-- 创建数据库,指定字符集和排序规则 CREATE DATABASE IF NOT EXISTS course_design DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; USE course_design; -- 创建院系表 CREATE TABLE department ( dept_id CHAR(4) NOT NULL COMMENT '院系号', dept_name VARCHAR(40) NOT NULL COMMENT '院系名称', PRIMARY KEY (dept_id) ) ENGINE=InnoDB COMMENT='院系信息表'; -- 创建班级表,外键引用院系表 CREATE TABLE class ( class_id CHAR(8) NOT NULL COMMENT '班级号', class_name VARCHAR(40) NOT NULL COMMENT '班级名称', dept_id CHAR(4) NOT NULL COMMENT '所属院系号', PRIMARY KEY (class_id), CONSTRAINT fk_class_dept FOREIGN KEY (dept_id) REFERENCES department(dept_id) ON DELETE RESTRICT ON UPDATE CASCADE ) ENGINE=InnoDB COMMENT='班级信息表'; -- 创建学生表,外键引用班级表 CREATE TABLE student ( stu_id CHAR(10) NOT NULL COMMENT '学号', stu_name VARCHAR(20) NOT NULL COMMENT '姓名', gender CHAR(1) DEFAULT '男' COMMENT '性别', birth_date DATE COMMENT '出生日期', class_id CHAR(8) NOT NULL COMMENT '班级号', PRIMARY KEY (stu_id), CONSTRAINT fk_student_class FOREIGN KEY (class_id) REFERENCES class(class_id) ON DELETE RESTRICT ON UPDATE CASCADE ) ENGINE=InnoDB COMMENT='学生信息表'; -- 创建教师表 CREATE TABLE teacher ( teacher_id CHAR(8) NOT NULL COMMENT '教师号', teacher_name VARCHAR(20) NOT NULL COMMENT '姓名', title VARCHAR(20) COMMENT '职称', PRIMARY KEY (teacher_id) ) ENGINE=InnoDB COMMENT='教师信息表'; -- 创建课程表,外键引用教师表 CREATE TABLE course ( course_id CHAR(8) NOT NULL COMMENT '课程号', course_name VARCHAR(40) NOT NULL COMMENT '课程名称', credit DECIMAL(3,1) NOT NULL DEFAULT 2.0 COMMENT '学分', teacher_id CHAR(8) COMMENT '授课教师号', PRIMARY KEY (course_id), CONSTRAINT fk_course_teacher FOREIGN KEY (teacher_id) REFERENCES teacher(teacher_id) ON DELETE SET NULL ON UPDATE CASCADE ) ENGINE=InnoDB COMMENT='课程信息表'; -- 创建选课表,复合主键,两个外键 CREATE TABLE enrollment ( stu_id CHAR(10) NOT NULL COMMENT '学号', course_id CHAR(8) NOT NULL COMMENT '课程号', score DECIMAL(5,1) COMMENT '成绩', PRIMARY KEY (stu_id, course_id), CONSTRAINT fk_enroll_student FOREIGN KEY (stu_id) REFERENCES student(stu_id) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT fk_enroll_course FOREIGN KEY (course_id) REFERENCES course(course_id) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB COMMENT='选课记录表';这段脚本的逻辑说明:先建被引用的表,再建引用表,避免外键约束报错。字符集用utf8mb4是为了支持中文和特殊字符。存储引擎用InnoDB,因为只有InnoDB支持外键和事务。外键的ON DELETE和ON UPDATE行为要根据业务定:学生表引用班级表用RESTRICT,防止误删班级导致学生丢失归属;选课表引用学生和课程用CASCADE,因为选课记录依附于学生和课程存在。
参数说明:CHAR适合定长字段如学号、课程号,VARCHAR适合变长字段如姓名。DECIMAL(5,1)表示总共5位,小数点后1位,成绩范围0.0到999.9,实际用0到100。注释用COMMENT关键字,方便后面用SHOW FULL COLUMNS查看。
4.2 索引怎么加:三个必须建索引的场景
索引不是越多越好,但下面三个场景必须建。第一,外键列。InnoDB会自动为外键建索引,但如果你用的是其他引擎或者手动管理,一定要建。第二,经常出现在WHERE子句中的列。比如学生表按姓名查询,就在stu_name上建索引。第三,经常用于JOIN的列。比如选课表按课程号统计选课人数,course_id上已经有复合主键的前缀索引,但单独查课程号时可能用不到,需要额外建。
-- 在学生姓名上建普通索引,加速按姓名查询 CREATE INDEX idx_student_name ON student(stu_name); -- 在选课表的课程号上建索引,加速按课程统计 CREATE INDEX idx_enrollment_course ON enrollment(course_id); -- 在课程名称上建索引,加速模糊查询的前缀匹配 CREATE INDEX idx_course_name ON course(course_name);索引的代价是插入和更新变慢,因为要维护索引结构。课程设计的数据量小,这个代价可以忽略,但你要在实验报告里写清楚为什么选这些列建索引,体现你理解索引的权衡。
4.3 插入测试数据的三种方式与批量生成脚本
测试数据要覆盖正常值、边界值、空值。我一般用三种方式结合:手动插入几条典型数据,用存储过程批量生成,用Python脚本生成CSV再导入。存储过程适合生成有规律的数据,比如学号从2024001到2024100。
-- 创建存储过程批量插入学生数据 DELIMITER $$ CREATE PROCEDURE batch_insert_student() BEGIN DECLARE i INT DEFAULT 1; WHILE i <= 100 DO INSERT INTO student(stu_id, stu_name, gender, birth_date, class_id) VALUES ( CONCAT('S', LPAD(i, 9, '0')), CONCAT('学生', i), IF(i % 2 = 0, '女', '男'), DATE_SUB('2005-01-01', INTERVAL i DAY), CONCAT('C', LPAD((i % 5) + 1, 7, '0')) ); SET i = i + 1; END WHILE; END$$ DELIMITER ; CALL batch_insert_student();这段代码用LPAD生成固定长度的学号,用IF交替性别,用DATE_SUB生成不同的出生日期。注意DELIMITER的改变,因为存储过程内部有分号。执行完后记得把DELIMITER改回分号。
注意:批量插入前先关掉外键检查可以提速,但插入完必须重新打开,否则数据完整性没法保证。命令是SET FOREIGN_KEY_CHECKS = 0; 和 SET FOREIGN_KEY_CHECKS = 1;
5. 避坑与排查:课程设计中最容易翻车的五个地方
5.1 外键约束导致插入失败
现象:插入选课记录时提示“Cannot add or update a child row: a foreign key constraint fails”。原因:选课表中的学号或课程号在对应的学生表或课程表中不存在。解决:先插学生和课程,再插选课。或者临时关闭外键检查,但这不是好习惯。我一般会写一个插入顺序清单:院系→班级→学生→教师→课程→选课。
5.2 中文乱码问题
现象:插入中文数据后查询显示问号。原因:数据库、表、连接三处的字符集不一致。解决:建库时指定utf8mb4,建表时继承库的字符集,连接时设置SET NAMES utf8mb4。如果用命令行客户端,还要确认客户端的默认字符集。我一般会在脚本开头加SET NAMES utf8mb4;。
5.3 事务不回滚导致数据不一致
现象:转账操作中,扣款成功但收款失败,数据对不上。原因:没有用事务包裹,或者用了事务但没提交/回滚。解决:在需要原子性的操作前后加START TRANSACTION和COMMIT,出错时ROLLBACK。课程设计中选课操作也涉及事务:先查容量,再插记录,再更新已选人数,这三步必须在一个事务里。
5.4 范式分解过度导致查询性能下降
现象:按3NF分解后,查一个学生选了哪些课要JOIN四张表,速度很慢。原因:过度规范化,把本该冗余的字段拆得太散。解决:在3NF基础上适当反规范化,比如在选课表中冗余课程名,避免每次JOIN课程表。但反规范化要在实验报告里说明理由,否则老师会认为你范式没学好。
5.5 实验报告里E-R图与关系模式不一致
现象:E-R图里画了某个属性,但关系模式里没有;或者关系模式里多了E-R图没画的表。原因:画图和转换分两步做,中间改了设计但没同步。解决:画完E-R图后,用一张对照表逐项检查。我一般会列一个清单:每个实体对应哪个关系,每个联系对应哪个关系或外键,每个属性是否都出现在关系模式中。
6. 进阶技巧:用视图和存储过程提升实验报告的技术含量
6.1 用视图封装复杂查询
视图可以把多表JOIN的复杂查询封装成一个虚拟表,既简化查询又体现设计能力。比如创建一个学生选课详情视图:
CREATE VIEW v_student_course AS SELECT s.stu_id, s.stu_name, c.course_name, t.teacher_name, e.score FROM student s JOIN enrollment e ON s.stu_id = e.stu_id JOIN course c ON e.course_id = c.course_id LEFT JOIN teacher t ON c.teacher_id = t.teacher_id;这个视图把学生、选课、课程、教师四张表连起来,查询时直接SELECT * FROM v_student_course WHERE stu_id = 'S000000001'。视图的好处是不占存储空间,而且能控制权限——你可以只让某些用户看视图,不给看基表。但视图的缺点是每次查询都要执行底层的JOIN,数据量大时性能不如物化视图。课程设计的数据量小,用视图完全没问题。
6.2 用存储过程实现业务逻辑
存储过程可以把业务逻辑放在数据库层,减少应用层的代码量。比如实现选课操作,包含容量检查和重复选课检查:
DELIMITER $$ CREATE PROCEDURE enroll_course( IN p_stu_id CHAR(10), IN p_course_id CHAR(8), OUT p_result VARCHAR(50) ) BEGIN DECLARE v_count INT DEFAULT 0; DECLARE v_exists INT DEFAULT 0; -- 检查是否已经选过 SELECT COUNT(*) INTO v_exists FROM enrollment WHERE stu_id = p_stu_id AND course_id = p_course_id; IF v_exists > 0 THEN SET p_result = '已选过该课程'; ELSE -- 检查课程容量(假设容量为50) SELECT COUNT(*) INTO v_count FROM enrollment WHERE course_id = p_course_id; IF v_count >= 50 THEN SET p_result = '课程已满'; ELSE INSERT INTO enrollment(stu_id, course_id, score) VALUES (p_stu_id, p_course_id, NULL); SET p_result = '选课成功'; END IF; END IF; END$$ DELIMITER ;调用方式:CALL enroll_course('S000000001', 'C0000001', @result); SELECT @result;。这个存储过程把选课的业务规则封装在数据库里,应用层只需要调用和读取结果。参数说明:IN表示输入参数,OUT表示输出参数。DECLARE声明局部变量,INTO把查询结果赋给变量。
6.3 用EXPLAIN分析查询执行计划
EXPLAIN是排查慢查询的利器。在SELECT前面加EXPLAIN,可以看到MySQL怎么执行这条查询:用了哪个索引、扫描了多少行、有没有用临时表。比如:
EXPLAIN SELECT s.stu_name, c.course_name FROM student s JOIN enrollment e ON s.stu_id = e.stu_id JOIN course c ON e.course_id = c.course_id WHERE s.stu_name = '学生1';看type列:如果是ALL,说明全表扫描,需要加索引;如果是ref或eq_ref,说明用了索引。看rows列:扫描行数越少越好。看Extra列:如果出现Using temporary或Using filesort,说明有排序或临时表,可能需要优化。课程设计的查询简单,一般不会太慢,但你在实验报告里放一条EXPLAIN的结果,能体现你懂性能分析。
6.4 备份与恢复:别等到数据丢了才后悔
课程设计最后要提交数据库文件,但很多人只交了SQL脚本,没交数据。我一般会做两件事:用mysqldump导出结构和数据,用SELECT INTO OUTFILE导出关键表为CSV。mysqldump的命令:
mysqldump -u root -p course_design > course_design_backup.sql恢复时:mysql -u root -p course_design < course_design_backup.sql。注意导出前先确认字符集,加--default-character-set=utf8mb4参数。如果数据量大,可以加--single-transaction避免锁表。
我自己的习惯是每完成一个阶段就导出一份备份,命名带日期,比如course_design_20250101.sql。这样万一改坏了,还能回退到上一个版本。数据库课设的坑大多不在SQL本身,而在环境配置、字符集、外键顺序这些细节上。把每一步的脚本和输出都存好,答辩时老师问什么你都能翻出来。希望帮到你。
本文还有配套的精品资源,点击获取