多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

数据库系统小型MIS开发实验报告:从E-R图到事务索引的完整实践

数据库系统小型MIS开发实验报告:从E-R图到事务索引的完整实践 简介这份南京邮电大学数据库系统小型MIS开发实验报告面向计算机与软件工程专业学生及数据库初学者帮助读者理解C/S或B/S结构系统的开发思路掌握ODBC与ADO的作用并学会通过界面访问数据库。报告围绕航班、飞机、乘客与机票四类信息的存储管理展开涵盖创建fightds数据库、设计table_fight、table_air、table_passenger、table_ticket四张表、添加唯一索引、插入与查询数据、创建视图及编写存储过程判断航班是否售空等完整环节可作为课程实验的参考范例与复盘材料。资源包共1个doc文件约372KB内容为完整实验报告文档结构清晰、步骤详实。目前已有242人学习浏览适合需要完成数据库课程设计或巩固SQL建库建表、视图与存储过程实操的读者参考借鉴。1. 从一份实验报告说起数据库系统小型MIS到底在练什么很多人第一次接触“数据库系统小型MIS开发实验报告”这个题目脑子里浮现的是课程设计那种“建几张表、拖几个控件、截几张图”的套路。但真正做过一轮的人会告诉你这个实验的核心不是写代码而是把“需求→数据模型→事务边界→可演示系统”这条链路完整走一遍。标题里的“数据库系统”是理论底座“小型MIS”是落地形态“实验报告”则是把设计决策和验证过程讲清楚。它适合正在学数据库系统概论、需要交课程设计、或者想用一个完整小项目把SQL、范式、索引、事务串起来的人。换句话说这不是做一个能跑就行的Demo而是做一个能讲清楚“为什么这样设计”的管理信息系统。2. 需求与数据模型先画E-R图还是先写建表语句2.1 小型MIS的功能边界怎么定小型MIS最容易翻车的地方是功能贪多。常见做法是先锁定一个业务域比如“实验室设备借用管理”或“小型仓储出入库”只做三到四个核心实体。我一般会按“谁用、做什么、留下什么记录”三步拆角色决定权限表操作决定业务表记录决定日志表。这样拆完实体通常不超过六个关系不超过八条后续建表和写查询都不会失控。具体操作上先列一张功能清单每一条都写成“角色动作对象”的格式例如“管理员新增设备”“普通用户申请借用单”。然后判断哪些动作会产生持久化数据哪些只是查询。只有产生数据的动作才需要建表纯查询动作只需要视图或索引支持。这一步做完E-R图基本就水到渠成了。2.2 从E-R图到关系模式的转换规则E-R图转关系模式有固定套路实体转表属性转列主键选业务无关的代理键或自然键。一对多关系把“一”端主键放到“多”端做外键多对多关系必须单独建关联表关联表的主键通常是两个外键的组合。这里有个血泪经验如果关联表除了两个外键还有业务属性比如“借用时间”“归还时间”那它其实已经是一个独立实体不要硬塞成纯关联表。下面是一个设备借用管理的建表片段用SQLite语法方便本地直接跑-- 用户表代理键 唯一用户名 CREATE TABLE users ( user_id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, role TEXT NOT NULL CHECK(role IN (admin,user)) ); -- 设备表状态字段用枚举约束避免脏数据 CREATE TABLE devices ( device_id INTEGER PRIMARY KEY AUTOINCREMENT, device_name TEXT NOT NULL, status TEXT NOT NULL DEFAULT available CHECK(status IN (available,borrowed,maintenance)) ); -- 借用记录表多对多关系的落地带业务时间字段 CREATE TABLE borrow_records ( record_id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, device_id INTEGER NOT NULL, borrow_time TEXT NOT NULL, return_time TEXT, FOREIGN KEY(user_id) REFERENCES users(user_id), FOREIGN KEY(device_id) REFERENCES devices(device_id) );逻辑说明users和devices是基础实体表borrow_records是关联表但带业务属性。参数上AUTOINCREMENT保证代理键单调递增CHECK约束把状态值锁死避免应用层写错。外键在SQLite里需要运行时开启PRAGMA foreign_keys ON;否则约束不生效这是很多人建完表以为万事大吉、结果插入脏数据才发现的原因。2.3 范式检查与反范式取舍建完表要过一遍范式。第一范式要求列不可再分第二范式要求非主属性完全依赖主键第三范式要求消除传递依赖。小型MIS里最常见的违规是把“设备名称”冗余到借用记录表里表面看查询方便实际上设备改名后历史记录就对不上。正确做法是只存device_id查询时JOIN。但反范式也不是完全不能用。如果某个统计查询频率极高比如“每个设备的借用次数”可以在设备表加一个borrow_count冗余列用触发器或应用层维护。代价是写入变慢、一致性风险上升。我的建议是实验阶段先严格按第三范式建表等性能真的成为瓶颈再考虑反范式并且要在实验报告里写清楚取舍理由。3. 事务、索引与查询让小型MIS从能跑到能用3.1 事务边界怎么划才不出错小型MIS里最典型的事务是“借用设备”先检查设备状态是否可用再插入借用记录最后更新设备状态为已借用。这三步必须在一个事务里否则并发下会出现同一台设备被两个人同时借走。下面是一个用PythonSQLite演示的事务写法import sqlite3 def borrow_device(conn, user_id, device_id): cur conn.cursor() try: conn.execute(BEGIN IMMEDIATE) # 立即加写锁避免并发读后写 cur.execute(SELECT status FROM devices WHERE device_id?, (device_id,)) row cur.fetchone() if row is None or row[0] ! available: raise ValueError(设备不可借用) cur.execute( INSERT INTO borrow_records(user_id, device_id, borrow_time) VALUES(?,?,datetime(now)), (user_id, device_id) ) cur.execute(UPDATE devices SET statusborrowed WHERE device_id?, (device_id,)) conn.commit() except Exception: conn.rollback() raise逻辑说明BEGIN IMMEDIATE在事务开始时就申请写锁防止两个事务同时读到available然后都去更新。参数上datetime(now)用数据库时间而不是应用时间避免多客户端时钟不一致。注意SQLite默认是自动提交模式必须显式BEGIN才能把多条语句包成一个事务。如果换成MySQL或PostgreSQL隔离级别默认是REPEATABLE READ或READ COMMITTED行为会有差异实验报告里最好注明数据库类型和隔离级别。3.2 索引不是越多越好三个必调参数索引能加速查询但会拖慢写入并占空间。小型MIS里我一般只建三类索引主键索引自动、外键索引、高频查询条件索引。外键索引很多人会漏导致JOIN时全表扫描。下面是在借用记录表上补索引的语句-- 外键索引加速按用户或设备查借用记录 CREATE INDEX idx_borrow_user ON borrow_records(user_id); CREATE INDEX idx_borrow_device ON borrow_records(device_id); -- 复合索引加速“某设备在某时间段内的借用记录”查询 CREATE INDEX idx_borrow_device_time ON borrow_records(device_id, borrow_time);参数说明复合索引的列顺序很重要device_id在前是因为等值查询优先borrow_time在后支持范围查询。如果反过来范围查询会阻断后续列的索引使用。另外索引不是越多越好每多一个索引插入和更新就要多维护一棵B树。实验阶段可以用EXPLAIN QUERY PLAN看查询是否走索引走全表扫描就补走了就停。3.3 查询优化从N1到一次JOIN新手写查询容易犯N1错误先查设备列表再循环查每个设备的借用次数。数据量小的时候看不出来数据一多就慢得离谱。正确做法是用GROUP BY一次查完-- 一次查询每个设备的借用次数避免N1 SELECT d.device_id, d.device_name, COUNT(b.record_id) AS borrow_count FROM devices d LEFT JOIN borrow_records b ON d.device_id b.device_id GROUP BY d.device_id, d.device_name;逻辑说明LEFT JOIN保证没有借用记录的设备也出现在结果里COUNT(b.record_id)只统计非空记录。参数上GROUP BY要包含所有非聚合列否则在严格模式下会报错。如果数据量再大可以考虑物化视图或定时统计表但实验阶段一次JOIN足够。4. 避坑与排查小型MIS开发中最容易翻车的五件事4.1 外键约束没生效脏数据悄悄进库现象删除了用户表里的记录借用记录表里还留着对应的user_id查询时JOIN不出结果。原因SQLite默认不开启外键约束MySQL的MyISAM引擎也不支持外键。解决SQLite每次连接后执行PRAGMA foreign_keys ON;MySQL改用InnoDB引擎并在建表时显式写ENGINEInnoDB。4.2 事务没回滚设备状态卡在“已借用”现象借用过程中程序抛异常但设备状态已经改成borrowed用户却查不到借用记录。原因异常发生时没有调用rollback()或者用了自动提交模式。解决把事务操作包在try/except里异常分支必须rollback()并且确认数据库连接没有开启自动提交。4.3 时间字段用字符串比较跨时区或格式不一致现象按时间范围查借用记录结果漏掉一部分。原因时间存成TEXT但格式不统一有的带秒有的不带字符串比较结果不可靠。解决统一用YYYY-MM-DD HH:MM:SS格式或者直接用数据库的DATETIME类型。如果涉及多时区存UTC时间展示时再转换。4.4 并发测试没做演示时两个人同时操作就崩现象答辩演示时两个客户端同时借用同一台设备系统没有报错但数据乱了。原因没有做并发控制两个事务都读到了available。解决用BEGIN IMMEDIATE或数据库的行锁并在实验报告里补一组并发测试用例证明事务隔离生效。4.5 实验报告只贴代码不讲设计决策现象报告交上去被退回评语是“缺乏分析”。原因只写了“建了哪些表、写了哪些查询”没写“为什么这样建、为什么不那样建”。解决每个设计点补一段取舍说明比如“选择代理键而不是自然键是因为设备名称可能重复且会变更”。报告的价值在于展示思考过程不是代码堆砌。5. 进阶技巧用触发器维护一致性用视图简化查询小型MIS做到后面会发现有些一致性逻辑散落在应用层容易漏。比如“归还设备”要同时更新借用记录的return_time和设备状态如果应用层忘了改状态数据就不一致。这时候可以用触发器把逻辑收进数据库-- 归还时自动更新设备状态 CREATE TRIGGER trg_return_device AFTER UPDATE OF return_time ON borrow_records FOR EACH ROW WHEN NEW.return_time IS NOT NULL AND OLD.return_time IS NULL BEGIN UPDATE devices SET statusavailable WHERE device_id NEW.device_id; END;逻辑说明触发器在borrow_records的return_time从空变为非空时触发自动把对应设备状态改回available。参数上AFTER UPDATE OF限定只监听该列的更新WHEN子句避免重复触发。触发器的好处是逻辑集中坏处是调试困难所以实验报告里要写清楚触发时机和测试用例。另一个进阶技巧是用视图封装复杂查询。比如“当前借用中的设备列表”涉及多表JOIN和条件过滤可以建一个视图应用层直接查视图不用重复写SQLCREATE VIEW v_active_borrows AS SELECT u.username, d.device_name, b.borrow_time FROM borrow_records b JOIN users u ON b.user_id u.user_id JOIN devices d ON b.device_id d.device_id WHERE b.return_time IS NULL;视图不存数据每次查询实时计算适合读多写少的场景。如果视图查询很慢可以考虑物化视图但SQLite不支持物化视图需要用定时任务把结果写进实体表。验证方法上我习惯在实验报告里附一组“边界测试”空表查询、重复借用、归还后再次借用、删除有借用记录的用户。每组测试写清楚输入、预期输出、实际输出。这样报告不仅有设计还有验证说服力完全不一样。最后说个我自己的教训第一次做这个实验时我把所有逻辑都写在应用层数据库只当存储用结果并发测试直接翻车报告也被批“没有体现数据库系统课程的核心”。后来把事务、约束、触发器用起来代码反而更短逻辑更清晰。数据库不是文件柜它有自己的表达能力用好了能省很多事。希望帮到你。本文还有配套的精品资源点击获取
返回列表