简介:这份资源是《数据库应用》课程期末大作业的完整课程设计报告,面向高校计算机相关专业学生及需要完成数据库课程设计的自学者,主题为企业人事管理系统。报告从系统需求分析入手,依次展开概念结构设计、逻辑结构设计、物理结构设计、视图设计、数据保护设计与系统实现,完整呈现数据库设计的全流程。内容涵盖员工、考勤、薪资、用户四类实体的抽象过程,以及Staff、Attendance、Salary、Puser四张表的字段定义、数据类型与唯一性约束,并讨论了密码加密、角色权限控制、防止直接操作数据库等安全策略,对理解数据库在实际业务中的落地很有参考价值。资源包共1个doc文档,约2.58MB,结构清晰、章节完整,适合直接作为课程设计模板或复习数据库设计方法的参考资料。目前已有6645人学习下载,说明其在同类课程作业中具有较高的认可度与实用价值。
1. 从一份《数据库应用》期末大作业说起:为什么很多人卡在“能跑但讲不清”
每年期末,总有一批同学在机房熬到凌晨两点,Navicat 里表建好了、数据也塞进去了,可一到答辩就说不清自己为什么这么设计。这份 MySQL《数据库应用》期末大作业,表面上是交一个能增删改查的系统,实际上考的是你能不能把“需求 → 表结构 → 约束 → 查询 → 事务”这条链路讲通。我见过太多作业,功能演示没问题,一问“这个外键为什么加在这里”“这条 SQL 走没走索引”就哑火。这篇笔记不聊虚的,就按一线做项目的思路,把一份能拿得出手、也经得起追问的 MySQL 大作业从选题到交付拆一遍。适合正在赶作业的在校生,也适合想借这个场景把 MySQL 基础重新捋顺的转行者。热词里那些 mysql 安装教程、数据库增删改查、mysql 排序,都会在对应环节落到具体命令上。
2. 选题与建模:别一上来就写 SQL,先把 ER 图钉死
2.1 什么样的题目既好写又不容易翻车
期末大作业最常见的翻车点不是技术难,而是题目选得太大。我一般建议从“三到五张核心表”的规模切入,比如图书借阅、学生选课、小型仓储、电影票务。这类题目的好处是实体关系清晰,天然带一对多和多对多,能自然引出外键、联合主键、级联这些考点。反过来,像“校园综合管理平台”这种题目,表能铺到二十张,最后每张表就两三个字段,答辩时老师一眼就看出是凑数。
选完题先别碰 MySQL,拿纸或者 draw.io 画 ER 图。实体用矩形,属性挂椭圆,关系用菱形,把基数标清楚。这一步花半小时,能省掉后面改表结构的两小时。ER 图定下来之后,再翻译成关系模式:每个实体一张表,多对多关系单独抽一张关联表,一对多把“一”的主键放到“多”那边当外键。
2.2 从 ER 图到建表语句的翻译规则
翻译的时候有几个固定动作。主键统一用自增整数还是业务字段,要提前定。我倾向自增id做主键,业务字段(学号、ISBN)加唯一约束,这样后续关联和修改都省心。字段类型别乱用,金额用DECIMAL(10,2)而不是FLOAT,时间用DATETIME而不是字符串,状态用TINYINT加注释而不是直接存中文。
-- 图书借阅系统核心表:读者、图书、借阅记录 CREATE TABLE reader ( reader_id INT AUTO_INCREMENT PRIMARY KEY, card_no VARCHAR(20) NOT NULL UNIQUE COMMENT '借书证号', name VARCHAR(50) NOT NULL, dept VARCHAR(50), created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE book ( book_id INT AUTO_INCREMENT PRIMARY KEY, isbn VARCHAR(20) NOT NULL UNIQUE, title VARCHAR(100) NOT NULL, author VARCHAR(50), stock INT NOT NULL DEFAULT 0 COMMENT '在馆数量', CONSTRAINT chk_stock CHECK (stock >= 0) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE borrow ( borrow_id INT AUTO_INCREMENT PRIMARY KEY, reader_id INT NOT NULL, book_id INT NOT NULL, borrow_date DATE NOT NULL, due_date DATE NOT NULL, return_date DATE DEFAULT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT '0借出 1已还 2逾期', CONSTRAINT fk_borrow_reader FOREIGN KEY (reader_id) REFERENCES reader(reader_id), CONSTRAINT fk_borrow_book FOREIGN KEY (book_id) REFERENCES book(book_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这段建表语句里,ENGINE=InnoDB是必须的,因为只有 InnoDB 支持外键和事务,MyISAM 建了外键也不生效,这是很多人答辩被问到的点。utf8mb4而不是utf8,是为了存下完整的字符集,避免生僻字或特殊符号报错。CHECK约束在 MySQL 8.0.16 之后才真正生效,5.7 上写了也只是语法通过、不拦截,这点后面避坑章节会再提。外键的ON DELETE和ON UPDATE行为默认是RESTRICT,也就是有借阅记录时不允许删读者,这正好符合业务逻辑,不用额外写。
2.3 三大范式要不要死守
教科书讲三大范式,但真实作业里适度反范式是允许的。比如borrow表里可以冗余一个book_title,查询借阅列表时少一次 join。代价是图书改名要同步更新,如果作业里没有改名功能,这个冗余就是划算的。判断标准很简单:冗余字段会不会变?变了要不要同步?同步逻辑写不写得动?三个问题答完就知道该不该冗余。答辩时主动说出“我这里做了反范式,原因是查询频率高、更新频率低”,比闷头按范式建表更能体现思考。
3. 增删改查落地:把 CRUD 写成能演示、能追问的形态
3.1 插入与批量插入的写法差异
单条插入谁都会,但演示时老师常问“如果一次导入一千条怎么办”。逐条INSERT会慢到怀疑人生,正确做法是批量插入或者LOAD DATA。批量插入的语法是把多组值用逗号拼在一个INSERT里,减少网络往返和事务开销。
-- 批量插入图书,一次提交 INSERT INTO book (isbn, title, author, stock) VALUES ('9787111111111', '数据库系统概论', '王珊', 5), ('9787111222222', 'MySQL技术内幕', '姜承尧', 3), ('9787111333333', '高性能MySQL', 'Baron', 2); -- 从借阅记录里统计每个读者的借书量,用于演示聚合 SELECT r.name, COUNT(b.borrow_id) AS borrow_count FROM reader r LEFT JOIN borrow b ON r.reader_id = b.reader_id GROUP BY r.reader_id, r.name ORDER BY borrow_count DESC;批量插入时如果某条违反唯一约束,整个语句会回滚(InnoDB 下),所以导入前要么清洗数据,要么用INSERT IGNORE跳过冲突。INSERT IGNORE会把错误降级成警告,适合演示容错,但生产里要谨慎,因为它会吞掉真正的数据问题。上面那条聚合查询用了LEFT JOIN,保证没借过书的读者也出现在结果里,COUNT统计的是borrow_id而不是*,这样没借书的读者计数是 0 而不是 1,这个细节答辩时经常被追问。
3.2 更新与删除:先 SELECT 再动手
更新和删除最怕手抖。我的习惯是任何UPDATE或DELETE之前,先把WHERE条件套进SELECT跑一遍,确认影响行数符合预期,再换成写操作。演示时也可以把这个习惯讲出来,体现工程素养。
-- 第一步:确认要更新的范围 SELECT * FROM borrow WHERE status = 0 AND due_date < CURDATE(); -- 第二步:确认无误后更新为逾期 UPDATE borrow SET status = 2 WHERE status = 0 AND due_date < CURDATE(); -- 删除演示:先查再删,且用事务包住 START TRANSACTION; DELETE FROM borrow WHERE borrow_id = 1001; -- 确认影响行数为 1 后 COMMIT;CURDATE()返回当前日期,配合due_date比较就能筛出逾期记录。事务包住删除是为了留后悔药,万一删错,ROLLBACK就能还原。注意 MySQL 默认是自动提交模式,START TRANSACTION之后必须显式COMMIT或ROLLBACK,否则连接断开时行为取决于配置,别赌。
3.3 查询进阶:排序、分页与子查询的组合
热词里 mysql 排序出现频率很高,因为作业演示几乎必有“按某字段排序”。ORDER BY可以多字段,LIMIT做分页,两者组合是列表页的标准写法。子查询用来做“查借过某本书的读者”这类需求。
-- 按借阅日期倒序,每页 10 条,取第 2 页 SELECT b.borrow_id, r.name, bk.title, b.borrow_date FROM borrow b JOIN reader r ON b.reader_id = r.reader_id JOIN book bk ON b.book_id = bk.book_id ORDER BY b.borrow_date DESC, b.borrow_id DESC LIMIT 10 OFFSET 10; -- 子查询:找出借过《高性能MySQL》的读者 SELECT name FROM reader WHERE reader_id IN ( SELECT b.reader_id FROM borrow b JOIN book bk ON b.book_id = bk.book_id WHERE bk.title = '高性能MySQL' );分页的LIMIT 10 OFFSET 10在数据量大时会越翻越慢,因为要扫描并丢弃前 10 条。作业数据量小无所谓,但答辩时能说出“深分页可以用游标或记录上次最大 id 来优化”,就是加分项。ORDER BY里带上borrow_id做次级排序,是为了日期相同时结果稳定,否则分页可能出现重复或漏行,这个坑很隐蔽。
4. 事务、锁与并发:作业里最容易讲糊的部分
4.1 事务的 ACID 在借阅场景里怎么体现
借书这个动作要同时做两件事:borrow表插一条记录,book表stock减一。这两步必须同生共死,否则会出现“记录有了但库存没减”或者反过来。这就是事务的原子性。一致性体现在stock不能减成负数,隔离性靠 InnoDB 的默认隔离级别REPEATABLE READ,持久性靠 redo log。
START TRANSACTION; -- 先锁定该书库存行,防止并发超借 SELECT stock FROM book WHERE book_id = 1 FOR UPDATE; -- 应用层判断 stock > 0 后执行 UPDATE book SET stock = stock - 1 WHERE book_id = 1; INSERT INTO borrow (reader_id, book_id, borrow_date, due_date, status) VALUES (1, 1, CURDATE(), DATE_ADD(CURDATE(), INTERVAL 30 DAY), 0); COMMIT;FOR UPDATE是排他锁,锁住这一行之后,另一个并发事务想改同一行就得等,从而避免两个请求同时读到stock=1都去减。DATE_ADD(CURDATE(), INTERVAL 30 DAY)算出应还日期,比在应用层算好再传更直观。注意FOR UPDATE必须在事务里才有意义,自动提交模式下加锁瞬间就释放了。
4.2 隔离级别与幻读的边界
MySQL 默认REPEATABLE READ,它用 MVCC 保证同一事务里多次读同一行结果一致,同时用间隙锁在一定程度上防幻读。但间隙锁只在特定查询下生效,不是万能的。作业里如果涉及“统计某时间段借阅数量”,并发插入可能让两次统计结果不同,这就是幻读的直观表现。答辩时能说清“RR 级别下普通 SELECT 是快照读,不加锁;SELECT ... FOR UPDATE 是当前读,会加锁”,基本就过关了。
4.3 死锁是怎么在作业里悄悄发生的
死锁的经典场景是两个事务以相反顺序锁两行。比如事务 A 先锁book_id=1再锁book_id=2,事务 B 先锁 2 再锁 1,互相等对方释放,MySQL 检测到后会自动回滚其中一个,报Deadlock found。作业里并发量小,但如果你写了批量更新且顺序不一致,也可能触发。规避办法是统一加锁顺序,比如永远按book_id升序处理。真遇到了,用SHOW ENGINE INNODB STATUS看最近一次死锁的详细信息,里面会列出两个事务各持有什么锁、在等什么锁。
5. 避坑与排查:那些答辩现场被问住的瞬间
5.1 外键建了但插入报 1452
现象:往borrow表插数据,报Cannot add or update a child row: a foreign key constraint fails。原因:reader_id或book_id在被引用表里不存在,或者类型/字符集不一致。解决:先查被引用表有没有对应主键值,再确认两边字段类型完全一致,特别是INT和INT UNSIGNED混用会直接失败。字符集不一致也会导致外键建不上,统一utf8mb4最稳。
5.2 中文乱码,存进去是问号
现象:插入中文后查出来是???。原因:连接字符集、表字符集、字段字符集三者不一致。解决:建库建表统一utf8mb4,连接串里加characterEncoding=utf8,MySQL 8 的驱动一般不用额外配,但 5.7 的 JDBC 老驱动要显式指定。命令行里可以先SET NAMES utf8mb4;再操作,确认是不是连接层的问题。
5.3 CHECK 约束写了不生效
现象:stock字段加了CHECK (stock >= 0),但插入-1居然成功了。原因:MySQL 5.7 及更早版本会解析 CHECK 语法但直接忽略,8.0.16 之后才真正执行。解决:要么升级到 8.0.16+,要么在应用层或触发器里做校验。答辩时如果老师问“你怎么保证库存不为负”,答“数据库层用 CHECK,应用层再兜一层”最稳妥。
5.4 分页查询结果重复或丢数据
现象:翻到第二页,发现第一页出现过的记录又出现了,或者某条记录两页都没出现。原因:ORDER BY的字段有重复值,排序不稳定,MySQL 返回顺序不确定。解决:在ORDER BY里追加一个唯一字段(通常是主键)做次级排序,比如ORDER BY borrow_date DESC, borrow_id DESC,保证全序。
5.5 事务没提交,数据“消失”了
现象:代码里执行了INSERT,程序里查得到,换个客户端查不到。原因:事务没COMMIT,或者连接池归还连接时自动回滚了。解决:确认代码里事务边界,try块里提交、catch里回滚。用命令行演示时,START TRANSACTION之后忘了COMMIT,关掉窗口数据就没了,这个坑新手必踩。
6. 让作业经得起追问:EXPLAIN 与索引的临场技巧
答辩现场最容易被问“你这条查询快不快、走没走索引”。与其背概念,不如当场跑EXPLAIN。在你要展示的查询前面加EXPLAIN,看type列:ALL是全表扫描,ref或range说明用上了索引,key列显示实际用的索引名。如果type是ALL且表数据不少,就可以主动说“这里可以给borrow_date加索引优化”。
-- 查看查询执行计划 EXPLAIN SELECT * FROM borrow WHERE reader_id = 1 AND status = 0; -- 针对高频查询建联合索引,注意最左前缀 CREATE INDEX idx_reader_status ON borrow (reader_id, status);联合索引(reader_id, status)能加速WHERE reader_id = ? AND status = ?,也能加速只带reader_id的查询,但单独查status用不上,这就是最左前缀原则。建索引不是越多越好,每个索引都会拖慢写入,作业里挑两三个高频查询字段建就够了。我一般会在答辩前把自己要演示的每条 SQL 都EXPLAIN一遍,心里有数,被问到就现场跑,比背答案可信得多。
还有个临场技巧:把SHOW CREATE TABLE borrow;也准备着,老师问表结构直接贴出来,字段类型、约束、索引一目了然。导出数据用mysqldump -u root -p 库名 > backup.sql,演示前备份一份,改坏了能立刻还原,这就是你的后悔药。我这些年做演示养成的习惯是:任何要现场改数据的操作,先备份、先 SELECT、再动手,三步走完再敲回车。希望帮到你。
本文还有配套的精品资源,点击获取