首页/新闻资讯/正文详情

医药信息管理系统数据库设计:从E-R图到事务扣库存的完整实战

发布时间:2026/9/26 7:27:35 来源:云帆数科 栏目:资讯中心
医药信息管理系统数据库设计:从E-R图到事务扣库存的完整实战
简介面向数据库课程设计或医药行业信息化入门学习者的完整项目资料包主题为医药信息管理系统。系统围绕基本信息、进货、库房、销售与财务统计五大模块展开覆盖药品/员工/客户/供应商维护、入库盘点、销售退货和日/月报表等典型业务适合用 MySQL 完成课程设计或理解 Java Web 项目结构。压缩包共 335 个文件、约 4.05MB以 54 个 Java 源码文件、38 个 HTML 页面、44 个 JS 脚本和 12 个 CSS 样式为主并含 SQL 数据库脚本、Maven 配置及说明文档150 个 GIF 动图可辅助查看界面效果与操作流程。已有 71 人学习。通过该项目可借鉴模块划分、数据库表设计、前后端组织方式和 Maven 工程搭建思路进而快速改造为符合自身选题的课设作品。1. 数据库课设医药信息管理系统先想清楚这三点再开写已经把选题盯在医药信息管理系统上的人多半是因为它业务贴近生活有药、有供应商、有入库出库、有销售和处方字段多但是不抽象做出来能演示的页面也多。但真的动手后你会发现90%的课设翻车案例不是不会写增删改查而是把表设计成了“药品表一张、销售表一张”的玩具结构答辩时老师问一句“一张处方里包含多个药品怎么查”就卡住。这个课设的核心考点从来不是界面上有多少个按钮而是三件事进销存的库存怎么在不丢流水的前提下扣减、处方明细和主单怎么用外键关联、以及多人同时开单时怎么保证库存不为负。这篇笔记会按我踩过的坑来拆解整套方案适合正在做数据库课设、以及准备把它当作品集项目去优化的同学。先从业务模型说起因为表结构错了后面所有代码都是在给错误打补丁。2. 从业务到E-R图把医药库存、供应商和处方单拆成能交差的模型图2.1 医药信息管理系统的核心业务闭环任何进销存系统都绕不开“采购、入库、建档、在库、出库、销售”这条线。医药信息管理系统比普通商品管理特殊在两点药品有“批准文号”和“有效期”出库的时候必须先进先出不能把快过期的药压在库底销售环节涉及处方一张处方可以开多种药品还要记录医生、药房和数量。所以你在课设里至少要覆盖下面这个闭环药品录入基础档案 → 供应商供货生成入库单 → 入库单明细增加库存 → 销售/处方开单 → 扣减库存 → 生成销售流水。任何一个环节断了比如入库时只改了库存表而没写入库明细或者销售时只写了销售单而没扣库存后面的统计就会对不上。建模的第一件事不是打开工具画图而是把业务规则列成清单。我习惯先写出来“药品必须属于某个分类”“同一供应商可以供应多种药品”“一张入库单包含多条入库明细每个明细对应一种药品和一个库存变动”“一张销售单包含多条明细每条明细对应一种药品和销售数量”“不允许销售库存为0的药品”。这些规则直接决定了实体和关系也决定了外键加在哪里。数据库增删改查只是最后一步业务边界定了表结构就自然出来了。2.2 从业务规则反推实体与关系先定主键和外键按上述规则实体可以拆成药品信息、药品分类、供应商、入库单、入库单明细、销售单处方、销售单明细、用户。其中“销售单”在医药场景里通常就是“处方”可以复用一张表增加字段区分是柜台销售还是处方销售。主键的选择遵守一个原则能用业务编号就用业务编号但业务编号不稳定时就用自增ID。比如药品表的“药品ID”可以用自增而“批准文号”虽然唯一但不同剂型可能同号不能当主键。入库单、销售单建议用单号字段作为逻辑主键同时再加一个自增内部ID原因后面在避坑章节细说。外键关系要画成四条主线。一是“药品分类”和“药品”之间是一对多分类ID是药品表的外键二是“供应商”和“入库单”之间是一对多供应商ID是入库单外键三是“入库单”和“入库单明细”是一对多入库单ID是明细表外键四是“销售单”和“销售单明细”是一对多销售单ID是明细表外键。药品和供应商之间不直接给外键而是通过入库单明细建立的“多对多”关联。这样设计的好处是你以后想查“哪个供应商是某种药的主要来源”时只要对入库明细做GROUP BY不需要用逗号拼接多值字段。2.3 从业务规则反推E-R图的画法要点E-R图在课设报告里占比很高但很多人画成了“一堆表连一堆表”这没有把关系表达清楚。正确的做法是先画最重要的关系在“销售单明细”实体上画两个菱形一边连“销售单”一边连“药品”中间标注“包含N种”。在“入库单明细”实体上同样连“入库单”和“药品”。然后单独抽出一张“用户”实体与“销售单”之间画“审核/开单”的连线。最后才是“供应商”和“分类”这类附属实体从外向里连接到对应的主实体。画E-R图的技巧是用“实体关系实体”的三元组去自检从销售单出发经过“包含关系”到销售单明细再到药品这叫“主表-明细-基础档案”是关系模式里最普遍也最容易考的一对多结构。老师很喜欢问“为什么不把明细直接塞进销售单表”答案是因为一张处方可以写多个药塞进去就要用重复字段或者逗号分隔既违反第一范式也查不了“某种药卖了多少钱”这种聚合SQL。把这句话写进课设报告分数基本稳了。2.4 范式级别的取舍第三范式够用反范式看场景教科书让你规范到第三范式但实际设计里我会给自己留两个后门。第一个是“库存数量”字段。严格说库存可以由“入库明细合计 - 销售明细合计”推导出来属于冗余不满足第二范式。但课设阶段如果每查一次库存都要汇总一次明细表数据量一大就慢所以我保留“药品表.库存数量”字段同时用事务保证每次入库和销售都同步更新它。这个冗余在答辩现场反而能展开讲“用空间换查询时间”属于主动反范式。第二个后门是“药品分类”在药品表里存了个分类名称的冗余字段。理论上应该只存分类ID通过JOIN去查名称但为了方便页面表格展示和减少JOIN次数我在药品表里冗余了“分类名称”。代价是分类改名时要做同步更新这在课设场景基本不发生。其余所有表严格按第三范式来没有重复字段组所有包含非主属性的字段都依赖主键而且依赖的是完整主键而不是部分依赖。中间张表“入库单明细”用的联合主键入库单ID药品ID注意明细里不能出现货物的“仓库位置”这种只依赖入库单的字段否则就是部分依赖需要拆出去。3. 建库建表与基础数据一套可直接跑的MySQL脚本3.1 建库与字符集选择utf8mb4和InnoDB是默认答案写脚本第一步是先定字符集和存储引擎。字符集我固定用utf8mb4不要用utf8因为utf8在MySQL里最多存3字节遇到生僻药品名或特殊符号会出现乱码。排序规则用utf8mb4_unicode_ci它对中文搜索比较友好。存储引擎选InnoDB这是为了保证事务、外键约束和行级锁可用。MyISAM虽然查询快一点但不支持事务和外键在这个项目里没有任何优势。数据库创建语句如下CREATE DATABASE IF NOT EXISTS pharma_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE pharma_db;这里有两个容易被忽略的点。一是“IF NOT EXISTS”在课设里很实用重新跑脚本不会报错二是COLLATE如果不下发后续建表时字段默认排序规则各不相同做WHERE查询比较字符串时容易报“Illegal mix of collations”。建议把所有表、字段统一到同一个COLLATE这是血泪经验。3.2 药品表、供应商表、入库表、销售表的完整建表语句核心表一共五张再加上两张辅助表。先建被依赖的基础表再建主表最后建明细表避免外键引用错误。下面是药品表、供应商表和用户表的示例CREATE TABLE drug_category ( category_id INT AUTO_INCREMENT PRIMARY KEY, category_name VARCHAR(50) NOT NULL UNIQUE, remark VARCHAR(200) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci; CREATE TABLE drug ( drug_id INT AUTO_INCREMENT PRIMARY KEY, drug_code VARCHAR(30) NOT NULL UNIQUE COMMENT 药品编码, drug_name VARCHAR(100) NOT NULL, category_id INT NOT NULL, specification VARCHAR(50) COMMENT 规格, unit VARCHAR(20) DEFAULT 盒, purchase_price DECIMAL(10,2) NOT NULL, sale_price DECIMAL(10,2) NOT NULL, stock_quantity INT NOT NULL DEFAULT 0, expire_date DATE NOT NULL, supplier_id INT COMMENT 默认供应商, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_drug_category FOREIGN KEY (category_id) REFERENCES drug_category(category_id), CONSTRAINT fk_drug_supplier FOREIGN KEY (supplier_id) REFERENCES supplier(supplier_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci; CREATE TABLE supplier ( supplier_id INT AUTO_INCREMENT PRIMARY KEY, supplier_code VARCHAR(20) NOT NULL UNIQUE, supplier_name VARCHAR(100) NOT NULL, contact_person VARCHAR(30), phone VARCHAR(20), address VARCHAR(200) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci; CREATE TABLE sys_user ( user_id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password_hash VARCHAR(64) NOT NULL, real_name VARCHAR(30), role VARCHAR(20) NOT NULL DEFAULT cashier COMMENT admin/manager/cashier ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci;建表顺序很重要先建drug_category再建supplier然后建drug否则drug表外键指向的表还不存在。注意drug表里的supplier_id设为“默认供应商”表示一种药可以对应多个供应商时默认选择其中一个真正的供应商和药品的多对多关系要由入库单明细来体现。price字段用DECIMAL而不是FLOAT后面避坑章节会专门解释。接下来是入库单和入库明细。主表记录这次进货的整体信息明细表记录每一种药品进了多少、单价多少CREATE TABLE stock_in ( stock_in_id INT AUTO_INCREMENT PRIMARY KEY, stock_in_no VARCHAR(30) NOT NULL UNIQUE, supplier_id INT NOT NULL, operator_id INT NOT NULL, stock_in_date DATETIME NOT NULL, total_amount DECIMAL(12,2) NOT NULL, remark VARCHAR(200), CONSTRAINT fk_in_supplier FOREIGN KEY (supplier_id) REFERENCES supplier(supplier_id), CONSTRAINT fk_in_operator FOREIGN KEY (operator_id) REFERENCES sys_user(user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci; CREATE TABLE stock_in_detail ( stock_in_id INT NOT NULL, drug_id INT NOT NULL, quantity INT NOT NULL, cost_price DECIMAL(10,2) NOT NULL, production_date DATE, expire_date DATE, PRIMARY KEY (stock_in_id, drug_id), CONSTRAINT fk_detail_in FOREIGN KEY (stock_in_id) REFERENCES stock_in(stock_in_id), CONSTRAINT fk_detail_drug FOREIGN KEY (drug_id) REFERENCES drug(drug_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci;销售单和销售明细与入库结构类似但多了一个“处方医生”和“客户姓名”的字段用来贴近医药管理场景CREATE TABLE sale_order ( sale_id INT AUTO_INCREMENT PRIMARY KEY, sale_no VARCHAR(30) NOT NULL UNIQUE, customer_name VARCHAR(50), doctor_name VARCHAR(30), cashier_id INT NOT NULL, sale_date DATETIME NOT NULL, total_amount DECIMAL(12,2) NOT NULL, status VARCHAR(20) DEFAULT completed, CONSTRAINT fk_sale_cashier FOREIGN KEY (cashier_id) REFERENCES sys_user(user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci; CREATE TABLE sale_order_detail ( sale_id INT NOT NULL, drug_id INT NOT NULL, quantity INT NOT NULL, price DECIMAL(10,2) NOT NULL, amount DECIMAL(12,2) NOT NULL, PRIMARY KEY (sale_id, drug_id), CONSTRAINT fk_detail_sale FOREIGN KEY (sale_id) REFERENCES sale_order(sale_id), CONSTRAINT fk_detail_sale_drug FOREIGN KEY (drug_id) REFERENCES drug(drug_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci;销售明细表里的price字段保存的是“成交时卖的单价”不要直接去关联药品表的sale_price因为以后药品调价了历史销售记录还要保留当时的金额。这个字段叫“快照字段”在库存类系统里非常常见。入库明细里的cost_price同理也必须是入库那一刻的实际成本价。3.3 约束设计主键、外键、CHECK与默认值怎么配合主键设计上所有自增主键都用INT如果觉得数据量会很大可以换成BIGINT但课设阶段没必要。唯一键用在业务单号上比如stock_in_no、sale_no用UNIQUE约束确保不会重复生成。外键约束必须有这是课设评分能直观看到的数据库知识点但不要在明细表上对明细记录建“无用的级联删除”。正确的是主单删除时明细应该同时删除所以外键用ON DELETE CASCADE药品和分类之间分类不能删要限制所以用ON DELETE RESTRICT。CHECK约束很多同学会写比如库存不能为负、价格大于0但MySQL 8.0.16之前的版本不强制执行CHECK只能在应用层校验。我在建表脚本里仍然写CHECK因为报告里可以写“数据库层面也有约束”同时应用层再拦一道。默认值尽量给全创建时间用DEFAULT CURRENT_TIMESTAMP销售状态用DEFAULT completed数量用DEFAULT 0。这样INSERT语句可以少写很多字段而且不会因为漏填导致空指针。3.4 初始化数据与视图让演示状态更合理建完表还要造一批演示数据不能用网上的“学生表”随便填。我一般造5个药品分类、20个药品、5个供应商、3个用户再造3张入库单和若干销售单。一个重要技巧是让部分药品库存低于“预警线”比如10盒部分药品的过期时间在3个月内这样后面的“有效期预警”和“库存不足查询”视图就有数据可演示。INSERT语句我就不全贴了给个示例INSERT INTO drug_category (category_name) VALUES (心脑血管类), (消化系统类), (呼吸系统类); INSERT INTO supplier (supplier_code, supplier_name) VALUES (SP001, 华东医药), (SP002, 华北制药); INSERT INTO sys_user (username, password_hash, real_name, role) VALUES (admin, hashed_value, 管理员, admin), (cashier01, hashed_value, 张收银, cashier); INSERT INTO drug (drug_code, drug_name, category_id, unit, purchase_price, sale_price, stock_quantity, expire_date) VALUES (DRUG001, 阿司匹林肠溶片, 1, 盒, 8.50, 15.00, 100, 2026-05-30), (DRUG002, 奥美拉唑胶囊, 2, 盒, 12.00, 20.00, 8, 2026-01-15);这里故意把奥美拉唑的库存设为8低于预警线演示时直接跑查询就能出效果。视图建议建两个一个是“库存预警_含分类名”一个是“销售统计_按日按月”。视图的好处是答辩时不用现场敲很长的SQL让评委看视图定义更直观CREATE VIEW low_stock_view AS SELECT d.drug_id, d.drug_name, d.stock_quantity, d.expire_date FROM drug d WHERE d.stock_quantity 10 OR d.expire_date DATE_ADD(CURDATE(), INTERVAL 90 DAY);4. 核心业务SQL写法增删改查之外的加分项4.1 库存扣减与流水记录一个销售事务怎么写医药信息管理系统的核心不是“药品增删改查”而是“销售完成后销售单、明细、库存、台账要同时更新”。建议把这个过程写成一个存储过程或者事务模板答辩时展示数据库并发锁和事务的一致性控制。下面是我常用的销售开单事务写法START TRANSACTION; -- 1. 插入销售主单 INSERT INTO sale_order (sale_no, customer_name, doctor_name, cashier_id, sale_date, total_amount) VALUES (SO20250601001, 张三, 王医生, 2, NOW(), 0); SET sale_id LAST_INSERT_ID(); -- 2. 插入销售明细同时计算总金额 INSERT INTO sale_order_detail (sale_id, drug_id, quantity, price, amount) SELECT sale_id, d.drug_id, 2, d.sale_price, d.sale_price * 2 FROM drug d WHERE d.drug_code DRUG001; -- 3. 更新药品库存并且强制要求库存不能为负 UPDATE drug SET stock_quantity stock_quantity - 2 WHERE drug_id 1 AND stock_quantity 2; -- 4. 检查是否有行被更新如果没有表示库存不足 SELECT ROW_COUNT() AS affected_rows; -- 5. 如果affected_rows为0则回滚 -- 注意在存储过程中可以用IF判断这里仅演示SQL顺序 COMMIT;这个事务的写法和纯增删改查的区别在第三步UPDATE语句里带了AND stock_quantity 2这在MySQL里是“条件更新”相当于数据库层帮你做了一次库存校验。执行UPDATE后如果影响行数为0说明库存不够就应该回滚。更规范的写法是用SELECT ... FOR UPDATE给药品行加锁再判断库存量但因为课设单机环境下并发不高用条件更新已经足够。这种写法能避免“库存被扣成负数”的经典错误。4.2 药品有效期预警和库存下限查询用视图与条件查询实现预警功能很出效果而且只要一条SQL。除了前面建的低库存视图还需要按有效期排序的查询。MySQL的DATEDIFF函数可以直接算出过期距离天数SELECT drug_name, expire_date, DATEDIFF(expire_date, CURDATE()) AS remain_days FROM drug WHERE DATEDIFF(expire_date, CURDATE()) 90 ORDER BY remain_days ASC;这条查询放在“库存管理”页面按照剩余天数从少到多排序就是很直观的效期提醒。如果你想升级成“每批药品独立效期管理”就要在入库明细里增加生产日期和效期字段出库时按照“先入先出”的原则选择最早批次的库存。课设里挂在药品表上的expire_date一个字段就够演示但你在报告里如果能写清楚“药品表上的效期是最新批次的效期更严格的做法是把效期放明细”老师会觉得你想过这个问题。4.3 存储过程与触发器课程设计的加分项很多学校的数据库课设评分点里有“存储过程”和“触发器”这两个数据库对象。存储过程建议封装两个一个是“药品采购入库”在insert入库单和明细后循环更新药品的库存和最近入库价另一个是“按时间范围统计销售排名”用GROUP BY生成报表。下面是一个简单入库存储过程的模板DELIMITER $$ CREATE PROCEDURE sp_stock_in( IN p_supplier_id INT, IN p_operator_id INT, IN p_drug_code VARCHAR(30), IN p_quantity INT, IN p_cost_price DECIMAL(10,2) ) BEGIN DECLARE v_drug_id INT; DECLARE v_stock_in_id INT; SELECT drug_id INTO v_drug_id FROM drug WHERE drug_code p_drug_code; INSERT INTO stock_in (stock_in_no, supplier_id, operator_id, stock_in_date, total_amount) VALUES (CONCAT(SI, DATE_FORMAT(NOW(), %Y%m%d%H%i%s)), p_supplier_id, p_operator_id, NOW(), p_quantity * p_cost_price); SET v_stock_in_id LAST_INSERT_ID(); INSERT INTO stock_in_detail (stock_in_id, drug_id, quantity, cost_price, expire_date) VALUES (v_stock_in_id, v_drug_id, p_quantity, p_cost_price, DATE_ADD(NOW(), INTERVAL 2 YEAR)); UPDATE drug SET stock_quantity stock_quantity p_quantity, purchase_price p_cost_price WHERE drug_id v_drug_id; END$$ DELIMITER ;注意DELIMITER的使用在命令行和Navicat里存储过程体中多个分号会被MySQL当成语句结束DELIMITER $$就是为了临时把终止符改成$$避免过程体内插值报错。参数名前面的p_后缀是为了避免和列名冲突这算是个小习惯。触发器可以作为“库存不足自动提醒”的补充。比如在销售明细表上做一个AFTER INSERT触发器自动更新药品表的库存触发器代码不长但它会隐藏业务逻辑让日后排错变难。我的建议是课设里写一个触发器证明你会用就行核心业务逻辑放存储过程或应用层不要把大量规则都压进触发器。触发器一旦出错SQL很难调试而且触发器里的SELECT不能往临时表以外的地方返回结果容易被绕晕。4.4 权限控制不同角色看到不同的菜单和记录数据库层的权限控制要做到基于角色的访问控制。我的做法是在sys_user表里加role字段应用层根据角色选择查询语句。比如管理员可以看到所有用户的销售记录收银员只能看到自己的。SQL层面可以用WHERE条件拼角色入参-- 应用层传递当前用户的 role 和 user_id SELECT so.sale_no, so.sale_date, so.total_amount, u.real_name AS cashier FROM sale_order so JOIN sys_user u ON so.cashier_id u.user_id WHERE ( role admin OR so.cashier_id user_id ) ORDER BY so.sale_date DESC;这个SP里用变量role和user_id表示从应用层传入的会话变量。数据库课上讲视图时也可以给一个“我的销售记录”视图视图定义里带上DATABASE用户函数但MySQL里做行级安全不太方便所以我在视图和存储过程之间选择了存储过程作为主力。数据库连接池场景下每个请求建立连接后要执行SET role ?才能保证不同人的数据隔离。5. 课设避坑数据库设计阶段的五个高频问题5.1 药品和供应商关系做成单表导致供应商覆盖现象药品表里有一个supplier_name字段一次给同一个药品维护了两个供应商后来发现只能存一个另一个被覆盖了。原因把多对多关系硬塞进基础档案表药品表根本没法表达“同一药品由多个供应商供货”。解决拆中间表或依赖入库明细表让每次进货同时记录“供应商药品批次”这样既能查某个药品的历史供应商又能在报表里按供应商聚合采购金额。课设报告里明确写出“供应商和药品是多对多关系通过入库单明细表实现”这一句话就能证明你懂数据库关系。5.2 金额用FLOAT存储对账差了几分钱现象录入采购金额后列表页显示108.60导出的报表却是108.599999。原因FLOAT和DOUBLE是浮点数二进制不能精确表示十进制小数拿来做金额会产生舍入误差。解决所有金额字段一律改成DECIMAL(10,2)或DECIMAL(12,2)。DECIMAL是按数字存储的加减乘除都精确。这个坑在医药系统里很致命因为进销存对账差一分都会引起麻烦。如果已经建错了表可以用ALTER TABLE来改字段类型但要暴露给所有相关表和存储过程。5.3 销售后没有流水记录库存和报表对不上现象演示时先做一笔销售然后打开库存查询发现库存没变。原因只往sale_order表插了一条记录没有同步更新drug表的stock_quantity也没有在sale_order_detail里记录明细。解决把销售流程收敛到同一个事务里或者在应用层用一个Service方法把“插入主表”“插入明细”“更新库存”三步包在Transactional里。数据库课设推荐用存储过程因为课堂环境里前端语言可能没法演示事务存储过程能直接把事务行为展示在数据库客户端。别忘了最后再查一次库存把扣减后的值显示在界面上。5.4 外键滥用导致插入失败考场手忙脚乱现象在明细表里插入数据时报“Cannot add or update a child row”然后整个销售流程中断。原因外键约束要求外键值必须在主表中存在。常见情况是先在明细分录里引用了sale_id但sale_order还没来得及提交自动生成的ID。解决先INSERT主表用LAST_INSERT_ID()拿到新ID再插入明细表。另一个典型错误是给“药品分类”表外键加了ON DELETE CASCADE导致删除一个分类时把所有药品全删了。分类属于“被引用数据”应该用RESTRICT禁止级联删除只有明细表才算“依赖数据”才适合用CASCADE。5.5 并发销售时库存变成负数现象两个窗口同时卖最后一件药两个窗口都显示库存为1也都扣减成功最后库存变成-1。原因先查库存再更新库存是“非原子操作”两个会话在“查询”阶段读到相同值后面各自做了减一导致数据库并发锁没有真正保住库存。解决把“判断库存大于0”和“扣减库存”合并到一条UPDATE语句里也就是前面写的UPDATE ... WHERE stock_quantity n。InnoDB默认行级锁这条UPDATE会锁住该药品行第二个会话必须等第一个提交或回滚才能继续这样就避免了负库存。如果有意演示悲观锁可以用SELECT stock_quantity FROM drug WHERE drug_id ? FOR UPDATE但注意事务结束后要立即提交否则锁会一直占着这就是数据库死锁的高发场景。6. 把数据量做大用索引和执行计划给系统加分课设答辩到后期老师通常会问“如果这张表有几十万条记录你的查询还会快吗”。这个问题别空谈索引直接在MySQL里做一做往sale_order_detail表里用存储过程灌入10万条模拟数据再跑两条等价的统计SQL对比执行计划里的扫描行数。我给一个可跑的模拟数据生成脚本DROP PROCEDURE IF EXISTS sp_generate_sale_data; DELIMITER $$ CREATE PROCEDURE sp_generate_sale_data(IN p_loop_count INT) BEGIN DECLARE i INT DEFAULT 0; DECLARE v_sale_id INT; SET AUTOCOMMIT0; WHILE i p_loop_count DO INSERT INTO sale_order (sale_no, customer_name, cashier_id, sale_date, total_amount) VALUES (CONCAT(BIG, DATE_FORMAT(NOW(), %Y%m%d%H%i%s), LPAD(i, 6, 0)), 批量客户, 1, NOW(), 0); SET v_sale_id LAST_INSERT_ID(); INSERT INTO sale_order_detail (sale_id, drug_id, quantity, price, amount) VALUES (v_sale_id, FLOOR(1 RAND() * 20), FLOOR(1 RAND() * 5), 10.00, 0); SET i i 1; END WHILE; COMMIT; END$$ DELIMITER ; CALL sp_generate_sale_data(100000);跑完后再执行EXPLAINEXPLAIN SELECT so.sale_date, so.total_amount FROM sale_order so WHERE so.sale_date BETWEEN 2025-01-01 AND 2025-12-31;在没有索引时type列通常是ALL行数是全表统计随后你在sale_date上建立索引再跑一遍EXPLAINtype会变成range。这就是一个很有说服力的现场调优演示。接着做一个更贴近业务的口径统计每一种药卖了多少钱。SELECT d.drug_name, SUM(sod.amount) AS total_sale_amount FROM sale_order_detail sod JOIN drug d ON sod.drug_id d.drug_id GROUP BY d.drug_id, d.drug_name ORDER BY total_sale_amount DESC LIMIT 10;这条SQL在数据量上去以后要保证drug_id是主键、sale_order_detail的drug_id上有索引不然JOIN慢。这也是“数据库同步工具”概念里经常提到的索引问题如果两张表关联字段忘了建索引数据同步和实时查询都会卡住。我的个人习惯是给每条核心查询写“优化前”和“优化后”两版并把EXPLAIN结果截图贴进课设报告。老师其实很清楚学生做的数据量不大他更在意你有没有验证的意识和排查手段。你只要做一次并不复杂的索引对比就能和其他交了个增删改查的课设拉开差距。最后再说一句实在话数据库课设的分数不是靠堆功能堆出来的而是靠表结构合理、事务不丢数据、查询有据可查这三个维度撑起来的。医药信息管理系统是个很好的载体把进销存和中小型业务数据库的常见问题都覆盖了值得你多花两个晚上把细节打磨好。希望这篇笔记能帮你在做课设和答辩的路上少踩几个坑。本文还有配套的精品资源点击获取

相关推荐

30分钟搭好自己的无代码数据库:Baserow 实战指南
30分钟搭好自己的无代码数据库:Baserow 实战指南

30分钟搭好自己的无代码数据库:Baserow 实战指南 【免费下载链接】baserow Build databases, automations, apps & agents with AI — no code. Open source platform available on cloud and self-hosted. GDPR, HIPAA, SOC 2 compliant. Best Airtable altern… · 2026/9/26 7:27:29

Python + SQL Server 图书管理系统课程设计方案
Python + SQL Server 图书管理系统课程设计方案

简介:一套基于Python与SQL Server开发的Web图书管理系统完整课程设计源码包,主要面向高校计算机专业需要完成数据库或Web开发课设的学生。系统参考学校图书馆借阅流程,包含学生/教师借阅端与管理人员后台:借阅者可执行登录、借书、… · 2026/9/26 7:27:29

KNN实战指南:从数据预处理到交叉验证选参的完整流程
KNN实战指南:从数据预处理到交叉验证选参的完整流程

简介:这是一份鸢尾花数据集KNN分类的Python实现,面向机器学习入门者或正在完成KNN相关实验、作业的高校学生。资源围绕鸢尾花数据完成全流程建模:先通过箱式图观察各特征分布,再进行特征预处理,按8:2划分训练集与测试集… · 2026/9/26 7:27:29

UNet改进模型大全:37种改进分类与统一训练验证脚本实战
UNet改进模型大全:37种改进分类与统一训练验证脚本实战

简介:这份资源面向图像分割方向的深度学习学习者与研究者,系统整理了37种UNet改进方案,覆盖注意力机制、特征融合与轻量化主干等主流思路,帮助读者在语义分割任务中快速对比不同模块的增益效果。包内共370个文件,以148… · 2026/9/26 7:57:06

SpringBoot SpringCloud SpringFramework版本对应关系与迁移实战指南
SpringBoot SpringCloud SpringFramework版本对应关系与迁移实战指南

如果你手头正在维护一个 Java 后端项目,或者刚接手别人留下一堆“能跑但没人敢动”的历史代码,那你迟早会和“SpringBoot、SpringCloud、SpringFramework 三者版本对应”这件事撞个满怀。它不是面试里背出来的知识点,而是每次新建工程、每次升… · 2026/9/26 7:57:06

2026 AI智能体RAG优化实战:从切块到检索的全链路调优
2026 AI智能体RAG优化实战:从切块到检索的全链路调优

先问一个问题:2026年了,你的AI智能体是不是还在“一本正经地胡说八道”?不管是制度条例学习助手、电力设计规范查询,还是本地ERP产品检索、电影解说生成器,凡是干过这类活儿的应该都有同感——光有LLM不够,… · 2026/9/26 7:57:06

基于Django+Flask的智能物流配送管理系统设计与实践
基于Django+Flask的智能物流配送管理系统设计与实践

做物流调度最头疼的是什么?我的答案不是订单多,而是"车在外边跑,调度室里两眼一抹黑"。去年接手一个城市配送项目时,每天不到三百单,用Excel排线,靠微信群调度,司机到哪了、哪几单顺路… · 2026/9/26 7:57:06

CTF取证利器foremost:文件雕刻与隐藏信息提取实战指南
CTF取证利器foremost:文件雕刻与隐藏信息提取实战指南

在CTF杂项(Misc)和取证类题目里,文件恢复与隐藏信息提取几乎是绕不开的一环。很多新手拿到一个镜像文件或者一张看似普通的图片,第一反应是用binwalk跑一遍,结果发现只能看到几个文件头,真正需要的内容却提… · 2026/9/26 7:57:06

北大青鸟AI大模型课程深度拆解:RAG、Agent与模型微调实战
北大青鸟AI大模型课程深度拆解:RAG、Agent与模型微调实战

每年都会有人来问我北大青鸟的AI大模型课程到底值不值得学,更多人关心的是:这门课讲的东西,和市面上那些“AI提示词技巧课”到底有什么区别。我的回答向来很直接——真正的AI大模型课程,核心从来不是教你怎么和模型聊天&#xff0… · 2026/9/26 7:57:00

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21

OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40

向下兼容与向上兼容:接口设计中的兼容性策略与工程实践
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践

一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码