ARTICLE DETAIL

资讯详情

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

数据库课程设计机房管理系统:ER建模、MySQL建表与事务实战

数据库课程设计机房管理系统:ER建模、MySQL建表与事务实战 简介广东工业大学数据库课程设计机房管理系统设计完整Word版是一份以管理学院2013年课程设计为背景的机房管理信息系统报告面向需要完成数据库课程设计或参与机房管理项目开发的读者。文档围绕系统需求分析、总体设计、数据库设计、应用程序调试与界面设计展开覆盖设备采购、登记、借用归还、消耗品领用、设备维修报废以及机房上机安排与查询统计等功能并给出开发环境Windows XP、SQL Server 2005、PowerBuilder与系统模块图、菜单设计。资源包内含1个doc文档大小约1.27MB目录结构清晰便于对照报告章节逐段学习。目前已有185人浏览/学习。读者可从报告中获得E-R图设计、数据库逻辑模型、T-SQL建表过程、模块查询调试方法等关键内容对撰写课程设计文档、设计SQL Server数据库和理解PowerBuilder应用开发具有直接参考价值。1. 机房管理系统课程设计这道题到底在考什么“数据库课程设计机房管理系统”是高校数据库课最常见的综合大作业尤其像广东工业大学的课程设计清单里机房管理系统几乎年年出现。很多同学拿到题目先去找代码但更值得做的是把“需求分析—ER设计—关系模式—建表SQL—业务逻辑—报表”这整条链路在自己机器上跑通。机房管理系统这个题目好在业务足够接地气上机、下机、计时、收费、设备报修每一个动作都能对应到数据库的增删改查和事务比单纯做一个“图书管理”更能体现并发控制、统计查询和触发的设计。这篇笔记就按我当年完成并答辩通过的完整思路来拆从ER模型到MySQL建表再到C#/Java调用的事务逻辑最后把容易翻车的地方一一列出来。适合正在做课程设计、准备毕业设计“基于springboot的机房管理系统”的同学对照着改也适合已经写完却不知道怎么补墙数据库设计深度的朋友。2. 需求拆解与数据模型从机房管理员视角设计ER和表2.1 先列清楚机房管理的核心流程和角色课程设计最忌一上来就写create table机房管理系统更是这样。你得先把自己当成机房管理员把一学期从早到晚要面对的事情列出来学生刷卡上机、管理员分配机器、机器开始计时、下机时结算费用、学生充值卡余额、机器出现故障报修、维修人员登记修复结果。列完这些流程角色自然就浮出来了——学生、管理员、维修人员。有些同学的漏洞在于把“管理员”和“维修人员”当成同一种角色其实它们的操作权限和关注的数据完全不一样。管理员关心的是“哪台机器空闲、谁在上机、上机多久了、应收多少钱”维修人员关心的是“哪些机器报修、报修了多久、修好没有”。在ER设计里这两种角色应该对应不同的关系模式至少要用不同的视图或权限做区分否则后面的统计报表很难写。我一般会先把主流程画出来学生上机机器空闲→分配→开始计时→ 下机结算停止计时→算费→从余额扣款→生成流水→ 设备维护报修→维修→恢复可用。这个流程图不是给老师看的是为下一步定位每个实体之间的联系。比如“上机记录”应该同时关联学生和机器而“费用流水”只和上机记录关联不必把学生字段冗余进去。这一阶段最终要产出的是需求说明书里的“功能模块图”和“数据流图”但落到数据库设计上核心是确定实体、属性和联系。我的习惯是先用思维导图列出所有名词再去掉重叠和冗余最后留下学生、管理员、机器、上机记录、充值记录、维修记录这六张表。这个数量是经过考虑的既能展示规范化设计又不至于像企业级系统那样写十几个表导致自己答辩讲不清楚。2.2 ER图与关系模式把实体、联系落到字段有了流程和角色下一步是画ER图。机房管理系统的ER图不需要特别花哨但要有“联系”和“基数”的表达。学生与上机记录是1:N机器与上机记录是1:N管理员与机器是1:N。这三个关系是核心其余如学生与充值记录是1:N机器与维修记录是1:N都是围绕主流程的附属。在把ER图转成关系模式时最容易犯的错是过度合并。比如把“上机记录”和“费用流水”合并成一张表用“是否已结算”字段区分。这样做在界面操作上看似方便但会导致未上机结束的记录没有费用字段而结算时的费用计算又必须写进同一行事务逻辑变得别扭。我建议严格拆开上机记录表只记录开始和结束时间、机器编号、学号、状态结算时另写一条收费流水包含金额、结算时间、操作员。两张表通过上机记录号关联既符合第三范式也方便后续统计“正在上机的机器数量”和“历史收入”。另一个要注意的是字段命名的可读性。很多课程设计为了图省事用 m_id、s_id、r_id 这种缩写到写SQL时自己都分不清。我一般用全称或能一眼看懂的前缀student_no、machine_id、record_id、admin_no。主键统一用自增int业务编号学号、工号单独做唯一约束。这样做的好处是外键关联时不会因为类型不一致导致连接失败也为后面做系统集成留了余地。字段类型的选择直接决定你答辩时能不能扛住提问。学号用char(12)而不是varchar因为学号长度固定且不需要变长存储机器编号用varchar(10)但要考虑可能带机房缩写比如“A-301”金额用decimal(10,2)而不是floatfloat算费用会出精度问题这是最经典的坑时间用datetime而不是timestamp虽然timestamp占的空间更少但课程设计跨时区场景少datetime在展示和查询时更直观。关于时间字段建议上机时间和下机时间都设为NOT NULL下机时间初始用“1970-01-01 00:00:00”或者用状态字段区分避免空值带来的统计误差。2.3 字段设计要点为什么课时设计要比通用设计多两张表很多网上流传的“机房管理系统”表结构只有学生表、机器表、上机记录表看起来简单但答辩时老师一问“学生余额如何记录”“故障设备如何跟踪”就卡住了。我建议在基本三表之外增加充值记录表和维修记录表这样你的设计才能覆盖所“计费”和“设备维护”这两个关键需求。充值记录表至少要包含充值单号、学号、充值金额、充值时间、操作员、支付方式。这里要注意的是学生余额不要在学生表里单独设计一个字段然后每次充值直接update学生表那样做虽然查询余额方便但丢失了充值流水也无法回答“某个学生这个月充了多少钱”。正确做法是每次充值先写充值记录表再同步更新学生表的余额字段用事务保证两条操作要么同时成功要么同时失败。这个设计在答辩时非常加分因为它体现出了对“数据一致性”的理解。维修记录表要包含维修单号、机器编号、报修时间、故障描述、维修状态、维修完成时间。设计这张表的关键是不要和机器表的状态字段混杂。机器表可以有一个“运行状态”字段值域为空闲/使用中/故障/维修中而维修记录表是历史的、不可被覆盖的。当机器报修时先更新机器表状态为“故障”再插入维修记录维修完成时更新维修记录状态并扫描这期间是否有人预约了这台机器。这个流程看起来简单但能体现你对“状态流转”的理解。外键约束方面很多课程设计怕麻烦建表时不加外键全部靠应用层控制。这在演示时问题不大但老师翻开你的建表SQL会直接问“为什么没有外键”你只能尴尬解释。我的建议是上机记录表的学生编号和机器编号、充值记录表的学生编号、维修记录表的机器编号都加上外键约束并且对删除行为做限制。至于是否需要级联删除我一般不使用ON DELETE CASCADE因为学生记录和机器记录属于基础数据历史流水必须保留。这个取舍要写进文档的“数据库设计说明”里答辩时就是你的论据。3. 用MySQL实现核心业务建表、上机结算与统计SQL3.1 建库建表脚本字段类型、主外键与索引怎么定前面ER设计完成后就该在MySQL里把表落下来了。我用的是MySQL 8.0字符集统一utf8mb4引擎用InnoDB因为事务和行级锁都得靠它。下面这份建表脚本是按课程设计文档的标准写的你可以直接复制调整注意把字符集、存储引擎显式写出来这也方便答辩时讲解。CREATE DATABASE IF NOT EXISTS lab_room_db DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci; USE lab_room_db; -- 机房位置表一个机房信息比如“实验楼A-301” CREATE TABLE computer_room ( room_id TINYINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, room_name VARCHAR(30) NOT NULL UNIQUE, position VARCHAR(50) NOT NULL, manager_no CHAR(10) NULL, created_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; -- 机器表属于某个机房 CREATE TABLE machine ( machine_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, machine_code VARCHAR(20) NOT NULL UNIQUE, room_id TINYINT UNSIGNED NOT NULL, ip_address VARCHAR(15) NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0空闲 1使用中 2故障 3维修中, purchase_date DATE NULL, FOREIGN KEY (room_id) REFERENCES computer_room(room_id) ) ENGINEInnoDB; -- 学生表 CREATE TABLE student ( student_no CHAR(12) PRIMARY KEY, student_name VARCHAR(30) NOT NULL, class_name VARCHAR(30) NOT NULL, balance DECIMAL(10,2) NOT NULL DEFAULT 0.00, register_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; -- 管理员表 CREATE TABLE admin ( admin_no CHAR(10) PRIMARY KEY, admin_name VARCHAR(30) NOT NULL, phone VARCHAR(15) NULL, role TINYINT NOT NULL DEFAULT 1 COMMENT 1普通管理员 2维修员 ) ENGINEInnoDB; -- 上机记录表 CREATE TABLE usage_record ( record_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, student_no CHAR(12) NOT NULL, machine_id INT UNSIGNED NOT NULL, start_time DATETIME NOT NULL, end_time DATETIME NULL COMMENT NULL表示未下机, cost DECIMAL(10,2) NULL, admin_no CHAR(10) NOT NULL, FOREIGN KEY (student_no) REFERENCES student(student_no), FOREIGN KEY (machine_id) REFERENCES machine(machine_id), FOREIGN KEY (admin_no) REFERENCES admin(admin_no), INDEX idx_record_student (student_no, start_time), INDEX idx_record_machine (machine_id, start_time) ) ENGINEInnoDB; -- 充值记录表 CREATE TABLE recharge_record ( recharge_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, student_no CHAR(12) NOT NULL, amount DECIMAL(10,2) NOT NULL, recharge_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, admin_no CHAR(10) NOT NULL, FOREIGN KEY (student_no) REFERENCES student(student_no), FOREIGN KEY (admin_no) REFERENCES admin(admin_no) ) ENGINEInnoDB; -- 维修记录表 CREATE TABLE repair_record ( repair_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, machine_id INT UNSIGNED NOT NULL, report_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, fault_desc VARCHAR(200) NOT NULL, repair_status TINYINT NOT NULL DEFAULT 0 COMMENT 0待维修 1维修中 2已修复, finish_time DATETIME NULL, repair_admin CHAR(10) NULL, FOREIGN KEY (machine_id) REFERENCES machine(machine_id), FOREIGN KEY (repair_admin) REFERENCES admin(admin_no) ) ENGINEInnoDB;这段脚本里有两个容易被忽略的设计。第一个是usage_record的end_time允许为空用一个空值代表“正在上机”而不是用某个特殊日期这样在统计当前在线人数时可以直接WHERE end_time IS NULL。第二个是machine_code和student_no都加了UNIQUE约束这是业务层面的自然主键但为了关联方便我又单独设了自增主键。两者不冲突反而能在日志记录和索引效率之间取得平衡。关于索引我额外建了idx_record_student和idx_record_machine两个复合索引。上机记录表是业务量最大的表查询往往按学生和按机器两个维度进行比如“查某学生这学期的上机记录”和“查某台机器的使用情况”。复合索引能同时覆盖WHERE条件和排序避免文件排序。但要注意复合索引的列顺序把student_no放前面start_time放后面因为学生维度更常被过滤。3.2 上机登记和下机结算两条事务SQL的写法与参数上机操作的核心是“给机器占位”和“生成一条未完成记录”。很多人会先update机器状态再insert记录其实顺序无关紧要但必须在同一个事务里。我这里用存储过程演示一个标准写法因为课程设计界面层调用时通常只需要传学号和机器编号进去。DELIMITER $$ CREATE PROCEDURE proc_start_usage ( IN p_student_no CHAR(12), IN p_machine_id INT UNSIGNED, IN p_admin_no CHAR(10) ) BEGIN DECLARE v_balance DECIMAL(10,2); DECLARE v_machine_status TINYINT; -- 开启事务前先锁定学生余额防止并发扣款 SELECT balance INTO v_balance FROM student WHERE student_no p_student_no FOR UPDATE; SELECT status INTO v_machine_status FROM machine WHERE machine_id p_machine_id FOR UPDATE; IF v_machine_status 0 THEN SIGNAL SQLSTATE 45000 SET MESSAGE_TEXT 机器不可用; END IF; IF v_balance 1.00 THEN SIGNAL SQLSTATE 45000 SET MESSAGE_TEXT 余额不足; END IF; -- 写上机记录 INSERT INTO usage_record (student_no, machine_id, start_time, admin_no) VALUES (p_student_no, p_machine_id, NOW(), p_admin_no); -- 更新机器状态 UPDATE machine SET status 1 WHERE machine_id p_machine_id; COMMIT; END$$ DELIMITER ;这里的FOR UPDATE是必须的没有它两个人同时抢同一台机器时两个会话可能都读到status0然后各自insert成功机器状态错乱。虽然是课程设计但老师很喜欢问“并发下怎么办”这句FOR UPDATE就是答案。SIGNAL语句是MySQL 5.6以后提供的报错机制应用层可以捕获到异常并提示学生。下机结算比上机更复杂。要计算时长、金额、扣余额、更新记录。计费规则我采用“不足1小时按1小时计每小时2元超过1分钟也算下一小时”。这个规则需要用SQL来计算而不是把开始时间和结束时间传给Java算完再回传因为数据库的NOW()才是唯一可信时间源。DELIMITER $$ CREATE PROCEDURE proc_finish_usage ( IN p_record_id INT UNSIGNED, IN p_admin_no CHAR(10) ) BEGIN DECLARE v_machine_id INT; DECLARE v_student_no CHAR(12); DECLARE v_start DATETIME; DECLARE v_cost DECIMAL(10,2); DECLARE v_minutes INT; -- 锁定未结算的上机记录 SELECT machine_id, student_no, start_time INTO v_machine_id, v_student_no, v_start FROM usage_record WHERE record_id p_record_id AND end_time IS NULL FOR UPDATE; IF v_machine_id IS NULL THEN SIGNAL SQLSTATE 45000 SET MESSAGE_TEXT 记录不存在或已结算; END IF; -- 计算时长向上取整到小时 SET v_minutes TIMESTAMPDIFF(MINUTE, v_start, NOW()); SET v_cost CEIL(v_minutes / 60) * 2.00; -- 扣减余额并留余额负数兜底 UPDATE usage_record SET end_time NOW(), cost v_cost, admin_no p_admin_no WHERE record_id p_record_id; UPDATE student SET balance balance - v_cost WHERE student_no v_student_no; UPDATE machine SET status 0 WHERE machine_id v_machine_id; -- 费用流水单独记这里简化为插入一条收费日志表可自行定义 INSERT INTO usage_record_log (record_id, amount, settle_time, admin_no) VALUES (p_record_id, v_cost, NOW(), p_admin_no); COMMIT; END$$ DELIMITER ;注意我加了usage_record_log表这个表并不在最初的六张表里。实际做的时候可以在线加也可以提前预留。它的作用是记录每次结算的金额快照避免usage_record的业务字段被频繁修改。由于我们不能回头改脑图这里建议你在自己的设计文档里补上这张日志表它能让“费用核算”模块更完整。这个存储过程最关键的是“读-计算-写”三步都在事务内并且对记录行加了FOR UPDATE。如果两个管理员同时收到同一记录的下机请求只有一个能成功另一个会等待锁释放后重新读到end_time不为空然后走“已结算”的报错分支。这个并发保护是数据库课程设计里最值得写进答辩词的点。3.3 常用统计查询上座率、收入、设备故障率的写法统计功能是机房管理系统设计文档里必须包含的模块通常需要三个报表实时上座率、某段时间的上机收入、设备故障率。每个报表对应一条或几条SQL我直接贴出可运行的版本。获取当前上座率时“正在使用”的机器数除以总机器数SELECT r.room_name, COUNT(DISTINCT m.machine_id) AS total_machines, COUNT(DISTINCT CASE WHEN u.end_time IS NULL THEN u.machine_id END) AS using_machines, ROUND( COUNT(DISTINCT CASE WHEN u.end_time IS NULL THEN u.machine_id END) / NULLIF(COUNT(DISTINCT m.machine_id), 0) * 100, 2 ) AS usage_rate FROM computer_room r LEFT JOIN machine m ON r.room_id m.room_id LEFT JOIN usage_record u ON m.machine_id u.machine_id GROUP BY r.room_id, r.room_name;这里用LEFT JOIN是为了把没有使用记录的空闲机器也统计进来。COUNT(DISTINCT CASE WHEN ... THEN ... END) 避免了同一台机器被多次上机记录重复计数。NULLIF防止除数为零。统计某学期收入时要小心按结算时间还是按上机时间。一般按end_time即真实收入到账时间SELECT DATE_FORMAT(end_time, %Y-%m) AS month, COUNT(*) AS settle_count, ROUND(SUM(cost), 2) AS income FROM usage_record_log WHERE end_time BETWEEN 2024-02-26 00:00:00 AND 2024-07-15 23:59:59 GROUP BY DATE_FORMAT(end_time, %Y-%m) ORDER BY month;注意end_time在usage_record_log里是settle_time这里我用了别名示意你自己建表时保持字段一致。日期的选择用BETWEEN并显式包含23:59:59避免漏掉最后一天的最后一秒。这是老生常谈但课程设计里很多人真的写BETWEEN 2024-02-26 AND 2024-07-15结果7月15日当天的记录全部没进报表。设备故障率需要关联machine和repair_record但故障率应该按“报修次数/总机器数”还是“故障时长/总可用时长”定义不同文档标准不一样。最简单可解释的做法是统计每台机器的报修次数和平均维修时长SELECT m.machine_code, COUNT(r.repair_id) AS repair_times, AVG(TIMESTAMPDIFF(HOUR, r.report_time, r.finish_time)) AS avg_repair_hours, SUM(TIMESTAMPDIFF(HOUR, r.report_time, r.finish_time)) AS total_repair_hours FROM machine m LEFT JOIN repair_record r ON m.machine_id r.machine_id WHERE r.repair_status 2 GROUP BY m.machine_id ORDER BY repair_times DESC LIMIT 10;这里AVG和SUM都是在完成维修的记录上计算没有完成的记录finish_time为空自然被过滤掉。LIMIT 10不是必须但不加可能输出几十行演示时不直观。答辩时可以说“我们重点关注故障频次最高的前10台机器”。4. 机房管理系统避坑指南5个让课程设计熬夜的常见问题4.1 现象上机记录无故丢失或重复很多同学用Java或C#直接在界面里先写“UPDATE machine SET status1”再写“INSERT INTO usage_record”然后发现偶尔机器状态变了但没有产生上机记录或者一次上机动作产生了两条记录。原因几乎都是应用程序把两步操作放在两个数据库连接里执行第一步成功第二步抛出异常又没开事务导致数据不一致。解决方法是把所有写操作收敛到一个方法里使用同一连接并开启事务。如果坚持用存储过程就最省心。另外为了从根上防止重复上机可以在usage_record上加一个“部分唯一索引”对同一个机器在同一时间段只能有一条未结算记录。MySQL支持函数索引你可以这样写CREATE UNIQUE INDEX idx_unfinished ON usage_record (machine_id, (CAST(end_time AS DATETIME)))? 实际上MySQL 8.0对NULL不参与唯一约束所以用普通唯一索引无法阻止多行NULL。更简单粗暴的方式是在应用层查一次“该机器是否有未结算记录”但这又有并发窗口。最稳妥的是在学生表或机器表上加“当前状态”字段由数据库判重但那样会破坏范式。折中方案是存储过程里对machine_id FOR UPDATE后再检查status正如前面写的已能解决这个问题。4.2 现象下机结算金额与人工计算对不上我自己做课程设计时翻过车学生上了2小时05分手算是4元但系统显示6元。查到最后是Java端用float计算时长和金额又出现精度丢失。或者数据库里start_time是DATETIME应用层拿到的Date对象时区不对导致时长计算错误。解决的方法只有一个不要在应用层算钱统一在数据库层用TIMESTAMPDIFF和DECIMAL计算。金额字段也必须是DECIMAL(10,2)不能是float或double。如果想彻底避免参数传递造成的误差就把计费规则写成存储过程的一部分。另外注意CEIL和ROUND的区别不足1小时按1小时算要用CEIL而不是ROUND如果计价单位是“每分钟0.1元”则用ROUND(..., 2)两位小数。4.3 现象多人同时下机时卡死或死锁机房下机高峰集中在同一个课间十几个人同时点下机MySQL突然报Deadlock found。原因是多个事务同时在usage_record、student、machine三张表上做更新加锁顺序不一致比如事务A先锁usage_record再锁student事务B先锁student再锁usage_record互相等对方释放锁。解决方法是让所有事务按同一个顺序加锁。我设计的存储过程固定先处理usage_record行FOR UPDATE再处理student行最后处理machine行。数据库会检测死锁并回滚其中一个事务应用层捕获到死锁异常后提示用户重新提交一次即可。更彻底的写法是在下机存储过程中先对所有涉及的记录按主键进行排序后统一加锁但课程设计没必要做得那么复杂统一顺序就够了。4.4 现象统计报表和Excel手算差几分钱这个问题通常出现在使用float做SUM或者SQL里对不同金额列做了ROUND后没有保持一致精度。例如某一列存的是2.35另一列存的是2.4SUM结果在显示器上正常导出到Excel后却变成7.0499999。解决办法金额字段一律decimal(10,2)所有运算用SQL完成不在应用层重新计算。如果遇到“四舍五入到分为单位汇总”的场景先按分计算再转换成元比如SUM(ROUND(cost * 100)) / 100。还有一点MySQL的ROUND(x, 2)是四舍五入但Excel里有些情况是“四舍六入五成双”两者对“5”的处理不一致如果老师拿Excel核验不要在0.005这种边界值上较真说清楚数据库的舍入规则即可。4.5 现象机房电脑IP地址老变导致记录错乱机房大多开启DHCP学生今天用A-101明天可能同一IP分给A-102。如果设计时把ip_address作为机器的唯一标识就会出现同一台机器IP变化后历史记录关联错乱甚至新机器插入失败。解决方法是机器表的主键和业务唯一键都用machine_code物理编号ip_address只做展示不做关联。上机记录里只存machine_id外键不存ip。如果确实需要查“某IP上过哪些机器”可以新建一个ip分配历史表与usage_record不直接关联。这个问题很能体现“主键设计判断力”答辩时主动提出来老师会觉得你考虑过实际部署环境。5. 答辩前的最后一步造数据、查完整性、写验证脚本5.1 用存储过程快速生成一学期的演示数据课程设计交上去不能只有表结构和空数据演示时必须有一批像模像样的记录。手插太慢而且数据之间关联容易出漏洞我习惯用一个存储过程批量造数据。下面的脚本生成60个学生、20台机器、一学期的随机上机记录金额按规则自动计算。DELIMITER $$ CREATE PROCEDURE proc_generate_demo_data() BEGIN DECLARE i INT DEFAULT 1; DECLARE j INT DEFAULT 1; DECLARE stu_no CHAR(12); DECLARE mach_id INT; DECLARE start_time DATETIME; DECLARE dur_minutes INT; DECLARE cost_amount DECIMAL(10,2); -- 清空业务表保留基础表 SET FOREIGN_KEY_CHECKS 0; TRUNCATE usage_record; TRUNCATE recharge_record; TRUNCATE repair_record; SET FOREIGN_KEY_CHECKS 1; -- 插入60个学生 WHILE i 60 DO SET stu_no CONCAT(2024, LPAD(i, 5, 0)); INSERT INTO student (student_no, student_name, class_name, balance) VALUES (stu_no, CONCAT(学生, i), CONCAT(计算机, CEIL(i/30), 班), 50.00); SET i i 1; END WHILE; -- 插入20台机器 SET i 1; WHILE i 20 DO INSERT INTO machine (machine_code, room_id, status) VALUES (CONCAT(A-, LPAD(i, 3, 0)), 1, 0); SET i i 1; END WHILE; -- 给每位学生随机生成5~10条上机记录 SET j 1; WHILE j 60 DO SET stu_no CONCAT(2024, LPAD(j, 5, 0)); SET i 1; SET start_time 2024-03-01 08:00:00; WHILE i 8 DO SET mach_id CEIL(RAND() * 20); SET start_time DATE_ADD(start_time, INTERVAL FLOOR(RAND() * 3) DAY); SET start_time DATE_ADD(start_time, INTERVAL FLOOR(RAND() * 12) HOUR); SET dur_minutes FLOOR(RAND() * 180) 30; SET cost_amount CEIL(dur_minutes / 60) * 2.00; INSERT INTO usage_record (student_no, machine_id, start_time, end_time, cost, admin_no) VALUES (stu_no, mach_id, start_time, DATE_ADD(start_time, INTERVAL dur_minutes MINUTE), cost_amount, A001); SET i i 1; END WHILE; SET j j 1; END WHILE; END$$ DELIMITER ;这个造数脚本关键在于先关闭外键检查否则TRUNCATE有外键依赖的业务表会失败。插入上机记录时start_time通过变量不断递增避免所有记录挤在同一时刻。RAND()配合CEIL生成随机机器编号但是可能同一个学生连续两次选同一台机器没关系只要end_time不重叠即可。课程设计演示时老师往往只看数据量是否足够、时间分布是否合理这个脚本够用了。5.2 完整性自检SQL外键、金额、时间交叉验证演示之前最好在跑一遍自检SQL把逻辑漏洞提前暴露。我常用三条“矛盾查询”来测试数据完整性。第一条是查有开始时间但没有结束时间的记录数这部分是在线记录没问题但如果有结束时间却用cost为空的就是结算逻辑漏了。SELECT COUNT(*) AS abnormal_cost FROM usage_record WHERE end_time IS NOT NULL AND cost IS NULL;第二条查上机时长和费用是否匹配匹配规则用CEIL时长/60 * 2如果结果不等于cost说明结算时用了错误公式SELECT record_id, student_no, machine_id, TIMESTAMPDIFF(MINUTE, start_time, end_time) AS minutes, cost, CEIL(TIMESTAMPDIFF(MINUTE, start_time, end_time) / 60) * 2.00 AS should_cost FROM usage_record WHERE end_time IS NOT NULL AND cost CEIL(TIMESTAMPDIFF(MINUTE, start_time, end_time) / 60) * 2.00;第三条查充值记录与余额的对账因为余额是通过程序更新可能存在充值记录存在但余额未增加的情况SELECT s.student_no, s.balance, IFNULL(SUM(r.amount), 0) AS total_recharge FROM student s LEFT JOIN recharge_record r ON s.student_no r.student_no GROUP BY s.student_no, s.balance HAVING ABS(s.balance - total_recharge) 0.01;注意这里余额充值总额-消费总额所以不能直接用balance和total_recharge比较需要再加上消费总额但作为自检脚本先看充值是否都对上再结合上机消费核对。课程设计阶段不需要做严格的财务对账只要能让这些查询返回空结果就能证明数据没有明显脏数据。5.3 演示时如何应对老师提问机房管理系统答辩时老师最常问的三个问题外键有什么用事务什么时候需要如何保证不超卖机器这三个问题其实在前面章节的存储过程和建表脚本里都有答案外键保证引用完整性事务保证多表更新的原子性FOR UPDATE解决并发抢机器。你只需要在演示时打开一条SQL日志或者用SHOW PROCESSLIST展示连接然后把存储过程里的FOR UPDATE指给老师看比背课本定义可信得多。最后一招是“刻意制造异常”在演示机上临时把学生余额改成0.5元然后执行上机操作界面弹出“余额不足”再改回正常余额。这个验证能直观地展示业务规则和异常处理比满屏正常数据更能留下印象。我每次带课程设计都留这一步答辩老师看到后通常会点点头不再追加深挖。如果你用的是Java/Swing或C# WinForm记得把数据库连接字符串单独配置在config文件里不要写死代码。答辩机器如果没装MySQL至少带一个数据导出脚本的SQL文件老师要求现场看可以直接导入。这个习惯救过我一次当时教室电脑连不上服务器靠本机导出的SQL文件完成了演示。希望帮到你。本文还有配套的精品资源点击获取
返回列表