ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

图书管理系统课程设计:MySQL数据库设计到事务并发实现全解析

图书管理系统课程设计:MySQL数据库设计到事务并发实现全解析 简介这是一份数据库课程设计领域的图书管理系统完整项目包面向正在完成课程设计或希望掌握SQL Server实际应用的高校学生。资源集中在B/S架构与GUI客户端的双版本实现涵盖图书查询、借阅、归还、读者与借阅记录管理等核心模块。压缩包共168个文件约1.38MB以67个jsp页面、27个class、23个java源码、3个sql脚本为主体另附数据库文件、设计文档和项目配置文件便于直接导入开发环境并对照学习。已有6927人学习下载。借助这套资源读者可了解图书管理系统中ER图设计、关系范式优化、存储过程与触发器应用等数据库建模要点同时观察JSP页面与后端Java类的调用关系掌握从数据表构建到业务逻辑实现的全过程。附带的SQL脚本与文档还能帮助快速还原Library数据库结构适合作为课程设计参考模板或数据库原理的实战练习。1. 图书管理系统的课程设计到底在考什么数据库能力图书管理系统是数据库课程设计里出现频率最高的一道题。它看起来只是“图书 读者 借阅”三个词实际上考的是从需求分析、ER 建模、范式拆表、SQL 增删改查到事务与并发控制、视图、存储过程、触发器这一整条数据库设计链路。这门课设适合所有要交数据库相关作业的本科生和专科生也适合刚学完 MySQL 想用一个综合项目练手的人。很多人把题目做成了“能跑就行”结果答辩时连为什么这样建表都答不上来。下面内容按我实际做课设的顺序给你一套能复现、能讲清楚的方案。2. 从需求到 ER 图图书管理系统的数据模型怎么立住做课程设计的第一步不是写代码而是把业务拆成实体和关系。这章先建模再落 SQL确保后面的所有操作都建立在一个稳定的数据模型上。2.1 图书管理系统的业务边界读者、图书、管理员与借阅记录图书管理系统的核心业务就三块读者管理、图书管理、借还书管理。对应实体有 reader读者、book图书、admin管理员、borrow借阅记录。管理员这个实体在不少课设里会被忽略但建议保留因为后面做登录、做权限区分时用得上报告里也能多写一个实体。实体之间的关系要能说清楚。一个读者可以反复借多本图书一本图书也会被不同读者借过所以 reader 和 book 之间是典型的多对多关系。多对多不能直接建两张表必须拆出一张中间表也就是借阅记录 borrow。borrow 每行代表一次借书行为同时引用读者和图书的主键。这样设计之后读者和图书之间就变成了两个独立的一对多关系reader 1:N borrowbook 1:N borrow。这是 ER 建模里最重要的一步报告里画 ER 图时也是把这一步画对。还要注意业务规则同一读者不能对同一本未归还图书重复借图书库存有限借出时必须检查 available 是否大于 0超过应还日期需要计算罚金。这些规则在 ER 图阶段先写在 borrow 实体上后面再落到 SQL 约束、存储过程和触发器中。2.2 第三范式检查为什么借阅记录不能塞进图书表很多初学者会把借阅次数、当前借阅人直接加在 book 表里例如在 book 表增加一个 current_reader_id 字段。这会在一次借书操作里同时修改 book 和 reader 的状态删除读者时还会连带错删图书信息这就是更新异常。按第三范式要求非主键字段之间不能存在传递依赖每个字段必须只描述它所在实体的属性。book 表只描述图书本身读者借了哪本书属于“业务事件”应该放在 borrow 表。拆分后三个表的职责非常清晰book 表记录图书固有信息和库存available 这个状态字段为了查询效率保留但要用事务和触发器保证它不被写错reader 表记录读者基本信息和有效状态borrow 表只记录每一次借还事件的时间、状态和罚金。这种设计在答辩时会被追问“如果某本书被借出了 5 次你在哪里能看到”答案是 borrow 表里有 5 条记录book 表的 available 会减少 5但不需要在 book 表里存 5 个借阅人字段。把这个逻辑讲清楚评审老师就知道你是真懂范式而不是背概念。2.3 建库建表 SQL主键、外键、索引与默认值我一般用 MySQL 8.0这是课程设计和毕业设计里最常见也最容易装的环境。建库之前先定字符集统一用 utf8mb4否则后面中文乱码会很痛苦。下面是完整建库建表脚本表引擎全部用 InnoDB只有它支持事务和外键。CREATE DATABASE IF NOT EXISTS library_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE library_db; CREATE TABLE admin ( admin_id INT AUTO_INCREMENT PRIMARY KEY, admin_name VARCHAR(30) NOT NULL UNIQUE, password_hash VARCHAR(64) NOT NULL, role VARCHAR(20) NOT NULL DEFAULT librarian ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE reader ( reader_id INT AUTO_INCREMENT PRIMARY KEY, reader_name VARCHAR(30) NOT NULL, phone VARCHAR(20) UNIQUE, register_date DATE NOT NULL, status TINYINT NOT NULL DEFAULT 1 COMMENT 1正常 0停用 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE book ( book_id INT AUTO_INCREMENT PRIMARY KEY, isbn VARCHAR(20) NOT NULL UNIQUE COMMENT 图书标准编号, title VARCHAR(100) NOT NULL, author VARCHAR(50) NOT NULL, publisher VARCHAR(50) NOT NULL, category VARCHAR(30), total INT NOT NULL DEFAULT 1, available INT NOT NULL DEFAULT 1, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, INDEX idx_title (title), CONSTRAINT chk_available CHECK (available 0) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; 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, status TINYINT NOT NULL DEFAULT 0 COMMENT 0借出 1已还 2逾期归还, fine DECIMAL(8,2) NOT NULL DEFAULT 0.00, CONSTRAINT fk_borrow_reader FOREIGN KEY (reader_id) REFERENCES reader(reader_id) ON DELETE RESTRICT, CONSTRAINT fk_borrow_book FOREIGN KEY (book_id) REFERENCES book(book_id) ON DELETE RESTRICT, INDEX idx_reader (reader_id), INDEX idx_book (book_id), INDEX idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段脚本里有三个容易被问到的点。外键用ON DELETE RESTRICT而不是CASCADE是为了防止删除读者时把他的借阅历史连带删掉。课程设计演示删除功能时一定要先演示没有借阅记录的读者否则会因外键约束失败这个坑后面专门讲。chk_available在 MySQL 8.0.16 之后才默认生效如果你的库里是 5.7这条 CHECK 会被解析但不拦截需要在触发器里补一道防线。borrow 表里的 status 和 fine 是计算逾期罚金的依据不能放在 book 表里。接着插入一份可复现的初始数据。这里显式指定主键是为了让后面存储过程示例里的 reader_id1001、book_id1 可以直接对上。INSERT INTO admin (admin_name, password_hash) VALUES (admin, 123456); INSERT INTO reader (reader_id, reader_name, phone, register_date) VALUES (1001, 张三, 13800000001, 2024-01-10), (1002, 李四, 13800000002, 2024-02-15); INSERT INTO book (book_id, isbn, title, author, publisher, category, total, available) VALUES (1, 9787115428028, 数据库系统概论, 王珊, 高等教育出版社, 教材, 3, 3), (2, 9787111213826, MySQL必知必会, Ben Forta, 人民邮电出版社, 技术, 2, 2);初始数据中张三的 reader_id 固定为 1001数据库系统概论的 book_id 固定为 1available 和 total 都是 3。后面所有演示示例都围绕这两个编号展开方便你和评委对照。插入完可以执行SHOW CREATE TABLE borrow;确认外键和索引都生效。到这一步数据库的骨架已经搭好接下来把借还书逻辑写成可靠的 SQL。3. 用 MySQL 实现图书管理核心 SQL借书、还书、逾期与统计这一章聚焦数据库层核心逻辑也是课设里真正拉开差距的地方。借书、还书、统计分别考察事务、日期函数、聚合视图和触发器。3.1 借书事务为什么必须用 SELECT ... FOR UPDATE图书管理系统最容易翻车的操作是借书。你首先想到的可能是先查库存判断大于 0再插入再减库存。但两个管理员同时给同一位读者借同一本书时两个事务可能同时读到 available1最后库存变成 -1。解决方法是给这条查询加上排他锁让同一时刻只有一个事务能修改这一行的库存。在 MySQL 里正确写法如下START TRANSACTION; SELECT available FROM book WHERE book_id 1 FOR UPDATE; INSERT INTO borrow (reader_id, book_id, borrow_date, due_date) VALUES (1001, 1, CURDATE(), DATE_ADD(CURDATE(), INTERVAL 30 DAY)); UPDATE book SET available available - 1 WHERE book_id 1; COMMIT;这段逻辑需要由程序来判断SELECT返回的值是否大于 0然后在大于 0 时执行插入和更新否则回滚。FOR UPDATE是 InnoDB 里的行锁它锁住的是 book_id1 这一行而不是整张表。另一个事务执行相同语句时会被阻塞直到当前事务提交或回滚。due_date用DATE_ADD(CURDATE(), INTERVAL 30 DAY)生成表示默认借期 30 天。如果只是写课程设计在程序代码里判断库存就行但想在答辩时拿加分建议封装成存储过程。存储过程把事务写在数据库里程序只调用一个名字逻辑更好讲DELIMITER // CREATE PROCEDURE sp_borrow_book( IN p_reader_id INT, IN p_book_id INT, OUT p_msg VARCHAR(30) ) BEGIN DECLARE v_available INT; START TRANSACTION; SELECT available INTO v_available FROM book WHERE book_id p_book_id FOR UPDATE; IF v_available 0 THEN INSERT INTO borrow (reader_id, book_id, borrow_date, due_date) VALUES (p_reader_id, p_book_id, CURDATE(), DATE_ADD(CURDATE(), INTERVAL 30 DAY)); UPDATE book SET available available - 1 WHERE book_id p_book_id; SET p_msg success; COMMIT; ELSE SET p_msg no_available; ROLLBACK; END IF; END// DELIMITER ;这个存储过程有三个参数OUT p_msg在调用后能拿到结果字符串。调用方式是CALL sp_borrow_book(1001, 1, result); SELECT result;。存储过程里没有处理“读者不存在”和“读者已停用”这两类前置校验我建议放在应用层数据库层只保证库存一致性。如果你想把所有规则收进数据库可以在 IF 前后再查询读者状态然后通过SIGNAL SQLSTATE抛异常。3.2 还书与逾期罚金日期函数的使用还书逻辑同样不能简单 UPDATE 一条记录。需要先确认这条借阅记录没有被还过否则重复还书会让库存凭空增加。还书时要算是否逾期逾期一天罚金 0.5 元这个标准可以在报告里自定义。下面是带事务的还书 SQLSTART TRANSACTION; SELECT borrow_id, due_date, return_date FROM borrow WHERE borrow_id 1 AND return_date IS NULL FOR UPDATE; UPDATE borrow SET return_date CURDATE(), status IF(CURDATE() due_date, 2, 1), fine GREATEST(DATEDIFF(CURDATE(), due_date), 0) * 0.50 WHERE borrow_id 1; UPDATE book b JOIN borrow br ON b.book_id br.book_id SET b.available b.available 1 WHERE br.borrow_id 1; COMMIT;这里的SELECT ... FOR UPDATE防止两个终端同时还同一条记录。DATEDIFF(CURDATE(), due_date)会得到逾期天数没逾期就是负数再用GREATEST(..., 0)归零最后乘 0.5 等于罚金。status 用 1 表示正常归还、2 表示逾期归还是为了统计时不把逾期归并成普通归还。最后更新库存时通过 JOIN 找到对应的 book_id避免手工再写一遍图书编号。如果想让逻辑更稳可以把 UPDATE borrow 拆开执行先执行UPDATE borrow ... WHERE borrow_id ? AND return_date IS NULL然后用ROW_COUNT()检查受影响行数如果为 0 说明这条记录已经归还过直接回滚。课程设计不强制要求这么细但答辩时讲出来老师会认为你考虑过数据一致性。3.3 统计报表借阅排行、库存余量与一条视图课程设计报告里统计查询是拉开差距的地方。最常考三个借阅次数最多的图书、分类库存余量、当前未归还记录。下面是三条可直接跑的查询。-- 借阅排行榜 SELECT b.title, b.author, COUNT(bd.borrow_id) AS borrow_times FROM borrow bd JOIN book b ON bd.book_id b.book_id GROUP BY b.title, b.author ORDER BY borrow_times DESC LIMIT 10; -- 各分类库存余量 SELECT category, COUNT(*) AS kind_cnt, SUM(available) AS remain_total FROM book GROUP BY category; -- 当前未归还的借阅记录 SELECT r.reader_name, b.title, bd.borrow_date, bd.due_date FROM borrow bd JOIN reader r ON bd.reader_id r.reader_id JOIN book b ON bd.book_id b.book_id WHERE bd.status 0;排行榜用GROUP BY后按次数降序排列。分类库存里COUNT(*)统计的是图书种类数SUM(available)统计的是可借总册数这两个概念容易混淆评委问的时候要区分开。未归还查询是管理员最常用的通常还会再拼一个AND bd.due_date CURDATE()来查逾期名单。为了让代码更干净可以创建一个视图把借阅详情这个高频 JOIN 暴露成一张“虚拟表”CREATE VIEW v_borrow_detail AS SELECT bd.borrow_id, r.reader_id, r.reader_name, b.book_id, b.title, b.isbn, bd.borrow_date, bd.due_date, bd.return_date, bd.status, bd.fine FROM borrow bd JOIN reader r ON bd.reader_id r.reader_id JOIN book b ON bd.book_id b.book_id;创建后上面的第三条查询可以写成SELECT * FROM v_borrow_detail WHERE status 0;可读性提高不少。视图不占实际存储每次查询时动态执行底层 JOIN所以在视图上继续加 WHERE 不会影响正确性。报告里放一段视图定义是安全且有效的加分项。3.4 触发器把库存负值拒在数据库门外存储过程能管住借书流程但如果你用图形化管理工具直接改数据或者应用层代码漏了库存判断available 仍然可能被改成负数。为了避免状态被写坏可以在 book 表上加 UPDATE 触发器让任何会把 available 改到负数的语句直接报错。DELIMITER // CREATE TRIGGER trg_book_available_nonnegative BEFORE UPDATE ON book FOR EACH ROW BEGIN IF NEW.available 0 THEN SIGNAL SQLSTATE 45000 SET MESSAGE_TEXT book available cannot be negative; END IF; END// DELIMITER ;触发器是存储过程之外的第二道防线。它在每次 UPDATE 执行前检查新的 available 值如果为负数就抛出异常让整个事务回滚。这个触发器配合sp_borrow_book能保证所有路径的借书操作都不会超借。答辩时可以现场演示“我故意在图形界面里把 available 改成 -1触发器会拦住它。”这个效果比背概念强很多。到这一章结束数据库层已经覆盖了增删改查、事务、并发锁、存储过程、触发器和视图。这些正是“数据库课程设计”里“数据库”三个字的分量。下一章给它加一个能点按钮的界面让课设从“SQL 好用”变成“系统可用”。4. 用 Flask 给图书管理系统套一个可演示的界面4.1 选型理由为什么用 Python Flask 而不是 Java Swing图书管理系统的展示层有两条路线一条是写 Java Swing 或 JavaFX 客户端另一条是写 Web 页面。如果学校没有强制语言我更推荐 Python Flask因为课程设计的重点是数据库设计与实现界面只要能把 CRUD 调起来、能在答辩现场跑通即可。Flask 的代码量比 Servlet JSP 少得多一个app.py就能承载全部路由而且 PyMySQL 操作 MySQL 的语法直白新手能看懂每一行在干什么。如果你的评委明确要求必须是 Java Web换成 Spring Boot 的思路也完全一样数据库层不变控制器里执行同样的 SQL。本文给出的 SQL 和存储过程在两种技术栈下都可以复用。界面不要做得太重不要花时间研究 Vue 路由和前端框架课程设计打分点不在这里。4.2 项目结构与 MySQL 连接池配置一个规范的课设项目至少要有四个文件requirements.txt声明依赖db.py负责数据库连接app.py写 Flask 路由templates/index.html写前端页面。我习惯把连接封装成一个独立模块避免每个路由都重复创建连接导致连接数爆掉。Flask3.0.0 PyMySQL1.1.0 DBUtils3.0.0DBUtils 提供连接池它让程序不用频繁和 MySQL 建立新连接而是在一个池里复用连接。课程设计本地数据量不大但答辩时连续刷新页面没有连接池可能会出现连接超时用池之后这个问题基本消失。下面是db.py的完整写法from dbutils.pooled_db import PooledDB import pymysql db_pool PooledDB( creatorpymysql, maxconnections10, mincached2, blockingTrue, host127.0.0.1, port3306, userroot, password123456, databaselibrary_db, charsetutf8mb4, cursorclasspymysql.cursors.DictCursor, autocommitFalse, ) def get_conn(): return db_pool.connection()这里最关键的是maxconnections10和mincached2。maxconnections限制最大连接数防止程序开几千个连接把 MySQL 拖死mincached2表示启动时预先创建两个空闲连接第一次查询就很快。blockingTrue表示连接被用完时后续请求排队等待而不是直接报错。charsetutf8mb4必须写否则程序读取中文会乱码。账号密码一定要改成你自己本地的值。4.3 图书列表与借还书路由接下来写app.py。为了控制课程设计代码量这里把前端模板也放在同一个文件里用 Flask 的render_template_string渲染。生产项目不推荐这样但对课设来说文件越少越容易在答辩时说明白。from flask import Flask, request, render_template_string, redirect, url_for from db import get_conn app Flask(__name__) PAGE html headmeta charsetutf-8title图书管理系统/title/head body h2图书管理系统/h2 form action/borrow methodpost 读者ID input namereader_id value1001 图书ID input namebook_id value1 button typesubmit借书/button /form form action/return methodpost 借阅ID input nameborrow_id value1 button typesubmit还书/button /form table border1 trth图书ID/thth标题/thth作者/thth库存/thth可借/th/tr {% for b in books %} trtd{{ b.book_id }}/tdtd{{ b.title }}/tdtd{{ b.author }}/td td{{ b.total }}/tdtd{{ b.available }}/td/tr {% endfor %} /table /body /html app.route(/) def index(): conn get_conn() cur conn.cursor() cur.execute(SELECT book_id, title, author, total, available FROM book ORDER BY book_id) books cur.fetchall() conn.close() return render_template_string(PAGE, booksbooks) app.route(/borrow, methods[POST]) def borrow(): reader_id request.form[reader_id] book_id request.form[book_id] conn get_conn() cur conn.cursor() try: conn.begin() cur.execute(SELECT available FROM book WHERE book_id%s FOR UPDATE, (book_id,)) row cur.fetchone() if row is None or row[available] 0: return 图书不存在或无库存 cur.execute( INSERT INTO borrow(reader_id, book_id, borrow_date, due_date) VALUES(%s, %s, CURDATE(), DATE_ADD(CURDATE(), INTERVAL 30 DAY)), (reader_id, book_id)) cur.execute(UPDATE book SET available available - 1 WHERE book_id%s, (book_id,)) conn.commit() except Exception as e: conn.rollback() return f借书失败: {e} finally: conn.close() return redirect(url_for(index)) app.route(/return, methods[POST]) def return_book(): borrow_id request.form[borrow_id] conn get_conn() cur conn.cursor() try: conn.begin() cur.execute(SELECT borrow_id FROM borrow WHERE borrow_id%s AND return_date IS NULL FOR UPDATE, (borrow_id,)) if cur.fetchone() is None: return 借阅记录不存在或已归还 cur.execute( UPDATE borrow SET return_date CURDATE(), status IF(CURDATE() due_date, 2, 1), fine GREATEST(DATEDIFF(CURDATE(), due_date), 0) * 0.50 WHERE borrow_id %s , (borrow_id,)) cur.execute( UPDATE book b JOIN borrow br ON b.book_id br.book_id SET b.available b.available 1 WHERE br.borrow_id %s , (borrow_id,)) conn.commit() except Exception as e: conn.rollback() return f还书失败: {e} finally: conn.close() return redirect(url_for(index)) if __name__ __main__: app.run(port5000, debugFalse)这段路由把 SQL 执行流程完整搬到 Web 层借书时先FOR UPDATE锁住目标图书行确认有库存才插入借阅记录并减库还书时先用return_date IS NULL拦掉重复归还再更新罚金和库存。注意commit和rollback的配对出现异常就回滚最后一定close()把连接归还给连接池。连接池里的 close 并不真正断开连接而是放回池子这也是它能扛住连续请求的原因。4.4 跑起来之前初始化测试数据与验证路径运行前先确保数据库里已经有第 2 章的建表数据和初始数据。初始数据里没有 borrow 记录所以第一次打开页面时表格里不会出现借阅记录。先执行pip install -r requirements.txt python app.py然后在浏览器打开http://127.0.0.1:5000先点“借书”此时 reader_id 默认 1001book_id 默认 1观察表格里那本书的“可借”数量从 3 变成 2。再点一次“借书”数量变成 1。第三次借书时库存已经为 0页面提示“图书不存在或无库存”。这就是并发锁和 available 判断在 Web 层生效的结果。还书时网页表单默认 borrow_id1对应第一次借出的记录点击后“可借”数量恢复为 2。如果借书时填了一个不存在的 reader_id因为 borrow 表有外键会报错并回滚页面提示“借书失败”。答辩时遇到这种情况不用慌直接演示给评委看“数据库拒绝了这条非法数据这是外键约束在起作用数据完整性得到了保证。”这也是界面上保留完整错误信息的原因。5. 课程设计验收避坑常见问题与排查清单这一章集中讲我见过最多、最影响验收结果的 5 个坑。每条按现象、原因、解决三层写你可以直接对照排查。5.1 MySQL 服务连不上端口、驱动与权限三件事现象是运行app.py后借书或还书请求返回Cant connect to MySQL server on 127.0.0.1 (10061)有时候 Navicat 也连不上本地库。原因是 MySQL 服务没启动、端口被占用或者 root 用户只允许 localhost 登录。解决时先检查服务Windows 按 WinR 输入services.msc找到MySQL80服务确认状态是“已启动”Linux 用systemctl status mysql。然后执行netstat -ano | findstr 3306看端口是否被监听如果有监听说明 MySQL 实例在运行。确认服务正常后再用mysql -u root -p在命令行测试本地登录。如果命令行能进、Python 连不上多半是db.py里的password与 MySQL 的 root 密码不一致。安装 MySQL 时设置的临时密码和现在登录密码不同这种问题很常见。还有一种情况是 root 账号只授权了 localhost而db.py里写的是127.0.0.1在 MySQL 看来 localhost 和 127.0.0.1 是两条不同的连接路径需要在 MySQL 里执行GRANT ALL PRIVILEGES ON *.* TO root127.0.0.1 IDENTIFIED BY 你的密码; FLUSH PRIVILEGES;排查顺序建议是服务是否启动 → 3306 端口是否被占用 → 命令行能否登录 → Python 连接串是否与命令行一致。这套顺序能在 5 分钟内解决 90% 的连接问题。5.2 中文乱码建库字符集到连接串全链路排查现象是网页上的“数据库系统概论”变成了æ•°æ®åº或一串问号MySQL 命令行里却是正常的。原因一般是某一段走了 latin1 或 utf8而不是 utf8mb4。解决要查四个地方建库语句是否写DEFAULT CHARACTER SET utf8mb4建表语句是不是DEFAULT CHARSETutf8mb4db.py的PooledDB里有没有charsetutf8mb4MySQL 的my.ini里character-set-serverutf8mb4。这四处只要有一处是旧字符集插入的中文就可能损坏。可以用这条 SQL 快速查当前生效的字符集SHOW VARIABLES LIKE character%;重点是character_set_client、character_set_connection、character_set_database和character_set_server如果出现 latin1就要从配置层面修改。注意乱码一旦写进表改字符集不一定能修复已存数据最简单的方法是把表 drop 掉按第 2 章脚本重建后重新插入。数据还没成型时不要浪费时间做字符集转换重建比修复快。5.3 外键约束导致删除失败先处理子表还是先删主表现象是页面或 SQL 里执行DELETE FROM book WHERE book_id1;时报错Cannot delete or update a parent row: a foreign key constraint fails。原因是 book 表是父表borrow 表通过外键引用了它只要存在任何一条 borrow 记录指向该图书数据库就不允许直接删父记录。解决有三种方式如果只是想删掉这台破损图书应该先处理 borrow 表里的关联记录执行DELETE FROM borrow WHERE book_id1;再删除 book如果是要演示“删除图书”功能可以先删借阅记录再删图书并把这一步写进演示脚本如果你确实想保留借阅历史就不该物理删除而应该给 book 表加一个is_deleted标志字段默认 0删除时改成 1查询时过滤WHERE is_deleted 0。这就是软件删除。答辩时能主动讲出“我是故意用 RESTRICT 来保护历史数据”比直接改成ON DELETE CASCADE更容易拿高分。因为 CASCADE 会把所有借阅记录一起删掉在真实系统里是高风险操作。5.4 并发借书超借FOR UPDATE 不起作用怎么办现象是写好的FOR UPDATE看起来没有问题但 MySQL 命令行的两个终端同时执行借书存储过程库存还是变成了 -1。原因是两个终端没有真正开启事务或者表引擎不是 InnoDB。MyISAM 不支持行锁FOR UPDATE会直接失效。解决先确认SHOW CREATE TABLE book里是ENGINEInnoDB。再确认存储过程里先写了START TRANSACTION如果直接执行SELECT ... FOR UPDATE前没有开启事务某些连接驱动下锁不会保持到 UPDATE 阶段。建议做一次真实并发测试开两个 MySQL 终端在终端 A 执行START TRANSACTION; SELECT available FROM book WHERE book_id1 FOR UPDATE;先不提交再到终端 B 执行同样的语句。如果 B 阻塞等待说明行锁生效如果 B 立刻返回说明表是 MyISAM 或事务没开。等待几秒后在终端 A 执行ROLLBACK;B 会立即读到数据。这种并发锁的演示在答辩时非常有说服力评委一看就知道你理解数据库并发控制而不是只会调接口。5.5 演示时 MySQL 报 Too many connections连接真的归还了吗现象是页面连续刷新很多次后MySQL 报Too many connections。原因是代码里每次get_conn()后忘了close()或者maxconnections设得比数据库的max_connections还大。解决第一步是检查每一条路由确保finally里放了conn.close()。第二步把maxconnections调小比如 10mincached调成 2。第三步如果仍然报错去 MySQL 执行SHOW PROCESSLIST;这条命令会列出所有客户端连接可以看到大量来自你的应用的连接一直处于Sleep状态。临时救急可以用SET GLOBAL max_connections200;但根本解法是修代码补上 finally 中的 close。连接池不是玄学它只能复用连接不能帮你关掉泄漏的连接代码不释放再大的池也会被占满。答辩前如果预计现场网络环境一般建议把maxconnections写小一点至少不会一上来就把数据库打崩。6. 给课程设计加分的演示剧本与答辩讲法课设最后容易翻车的不是代码而是演示没有节奏。我一般会准备一份demo.sql把要现场执行的操作按顺序写好演示时照着跑一遍。这份脚本通常分四步第一步SELECT * FROM book;记录某本书当前 available第二步调用CALL sp_borrow_book(1001, 1, msg); SELECT msg;借书再查一次 book 确认库存减一第三步执行还书 SQL再查一次库存第四步故意借一本已经无库存的书展示no_available和触发器拦截。这个顺序能一口气覆盖增删改查、事务、并发锁、存储过程、触发器和完整性约束。答辩讲法上我会先说“我做这个系统时最在意的是数据库一致性”然后指着借书存储过程讲三句话为什么加锁、为什么用事务、为什么用外键。不建议上来念代码评委更想听设计时的取舍。例如可以这样讲“图书的 available 字段我故意保留而不是每次都 count 借阅记录是为了查询快为了不让它和 borrow 表不一致我用同一事务加减库存并加了触发器兜底。这算是数据库优化里典型的空间换时间思路。”这样讲完即使代码有缺漏也会让人觉得你理解到位。我的习惯是答辩前一晚把整个 demo 从建库到跑通连做三遍期间把所有残留的旧数据库删掉确保评审现场拿到全新环境也能一次通过。这个习惯帮我避开过很多次“刚才还好的怎么现在没了”的尴尬。做课设最怕的是只验收功能、不验收过程希望这篇记录能帮你把数据库基本功和演示技巧一次补齐希望帮到你。本文还有配套的精品资源点击获取
返回列表