简介数据库课程设计报告花店管理系统是一份面向数据库初学者的完整教学型文档。它以SQL Server 2005/2008为开发环境围绕系统规划、用户需求分析、功能结构设计、数据库结构设计与实现等环节展开覆盖业务流程图、权限管理、花信息/顾客/订购/仓库等核心数据实体、ER概念模型、逻辑表结构、第一至第五范式规范化分析、物理存储与索引策略、关键模块设计实现以及缺陷改进与总结体会等知识点适合需要完成数据库课程设计或复习数据库原理的在校学生使用。压缩包内共1个PDF文件约566KB报告目录结构清晰包含系统流程图、功能结构图、数据表设计、关键模块代码截图和人员分工安排可据此快速了解一套小型管理系统的完整设计流程与文档撰写框架。资源已有1925人浏览学习可作为课程设计报告撰写的结构参考和数据库实践入门的辅助材料。1. 数据库课程设计选花店管理系统为什么它能练到关系型数据库的全部核心选数据库课程设计题目时很多人第一反应是图书馆管理系统、学生信息管理系统。但花店管理系统这个题目有个明显优势它天然带着“商品库存 客户关系 订单流水”三条业务线散布在四五张表里主外键、多对多、事务、分组统计全都能碰到。你不需要虚构业务花店每一天的进货、卖花、打折、盘点都是真实世界的问题。跟“你好数据库”这样的演示项目相比这个题目能证明你确实把关系模型用起来了。这篇笔记按我帮学生搭课程设计时惯用的路线走先拆业务定表再把建表 SQL 和 Python 增删改查跑通最后补上索引、事务和答辩必问的坑让新手能照做熟手也能在参数和边界上找到参考。2. 把花店业务拆成五张表需求分析、ER 图与建库 DDL花店管理系统不是把“花”做成一张表就完事。课程设计的评分点通常落在三件事上表结构是否满足第三范式、外键与约束是否合理、统计查询能不能覆盖真实经营场景。如果一开始就把字段堆在一张大表里后面所有 SQL 都会写得很别扭所以我建议先花半天把需求拆清楚再动手建库。2.1 先分清角色和业务流程库里到底要存哪些东西花店常见的经营角色有三个老板、店员、客户。老板要看到每日营业额和热销花材店员要维护花材库存、录入客户信息、代客下单客户自己不直接操作后台但他的信息要在订单里留痕。于是数据流就清楚了花材从进货入库开始库存随订单被扣减订单产生销售明细销售明细汇聚成营业额报表。基于这个流程我一般建议拆出五个实体employee、customer、flower、orders、order_item。其中订单和花材之间是多对多联系一张订单可以包含多种花一种花也会出现在多张订单里所以中间表 order_item 必须存在。这也是课程设计里最容易拿到设计分的地方很多学生把订单细节直接塞进 orders 表导致一张订单里出现多种花时无从下手。各实体的职责要分清楚employee 管员工登录和值班记录customer 存客户档案后面订单要用flower 存花材的基本信息和实时库存orders 是订单头只放时间、客户、员工、总金额和状态order_item 是订单明细一行一种花记录购买数量和成交价。把“订单头”和“订单明细”拆开是第三个范式的典型体现课程设计报告里写这段时最好附上说明。2.2 从 ER 图到表结构主键、外键和联系怎么落成字段画 ER 图时实体是矩形联系是菱形关系最终要靠主外键来体现。到表结构这一步每实体一张表每个实体的属性一列字段。三个关键决定在这里做表名主键核心非主字段外键说明employeeemp_idemp_name, role, phone, hire_date无角色分为 boss 与 clerkcustomercust_idcust_name, phone, address, join_date无客户资料订单关联用flowerflower_idflower_name, category, unit_price, stock无库存实时扣减ordersorder_idorder_date, order_amount, statuscust_id, emp_id订单头记录总金额order_itemitem_idquantity, unit_priceorder_id, flower_id订单明细多对多中间表这里有两个设计决策值得展开。第一order_amount 严格来说是冗余字段它可以通过 order_item 聚合计算出来但在实际运营里保留它会大幅简化报表查询属于很合理的“用空间换时间”。答辩如果被问“为什么冗余”你就回答“写多读少查询频率远高于写入频率保留冗余字段避免每次报表都去 JOIN 明细表”。第二订单状态 status 字段必须预留哪怕是简单的 pending、completed、cancelled 三个值也能让事务演示时多一个可操作点同时让报表的过滤条件更接近真实业务。2.3 建库建表 DDLMySQL 里把花店库完整跑起来把设计落到 MySQL 时字符集、引擎和建表顺序是三个最容易翻车的地方。字符集官方推荐 utf8mb4 而不是 utf8因为中文和 emoji 都要靠它兜底引擎必须统一用 InnoDBMyISAM 不支持事务后面做下单扣库存的演示会当场露馅。建表顺序上先建无外键的表再建有外键的表否则外键引用的表还没创建SQL 直接报错。有两次我只能提醒学生“顺序错了不代表你的设计错了”但答辩机器上看到红字心态还是容易崩。-- 创建数据库字符集必须写成 utf8mb4排序规则一起指定 CREATE DATABASE IF NOT EXISTS flower_shop DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; USE flower_shop; -- 1. 员工表无外键先建 CREATE TABLE employee ( emp_id INT UNSIGNED AUTO_INCREMENT COMMENT 员工编号, emp_name VARCHAR(20) NOT NULL COMMENT 员工姓名, role VARCHAR(10) NOT NULL DEFAULT clerk COMMENT 角色: boss/clerk, phone VARCHAR(15) DEFAULT NULL, hire_date DATE DEFAULT NULL, PRIMARY KEY (emp_id), UNIQUE KEY uk_phone (phone) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT员工表; -- 2. 客户表也属于父表第二个建 CREATE TABLE customer ( cust_id INT UNSIGNED AUTO_INCREMENT COMMENT 客户编号, cust_name VARCHAR(20) NOT NULL COMMENT 客户姓名, phone VARCHAR(15) NOT NULL COMMENT 手机号, address VARCHAR(100) DEFAULT NULL, join_date DATE NOT NULL DEFAULT (CURRENT_DATE), PRIMARY KEY (cust_id), UNIQUE KEY uk_cust_phone (phone) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT客户表; -- 3. 花材表主数据表 CREATE TABLE flower ( flower_id INT UNSIGNED AUTO_INCREMENT COMMENT 花材编号, flower_name VARCHAR(30) NOT NULL COMMENT 花材名称, category VARCHAR(20) DEFAULT NULL COMMENT 分类: 玫瑰/百合/盆栽等, unit_price DECIMAL(10,2) NOT NULL COMMENT 销售单价, stock INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 当前库存, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (flower_id), KEY idx_category (category) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT花材表; -- 4. 订单表依赖 customer 和 employee后建 CREATE TABLE orders ( order_id INT UNSIGNED AUTO_INCREMENT, cust_id INT UNSIGNED NOT NULL, emp_id INT UNSIGNED NOT NULL, order_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, order_amount DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT 订单总额, status VARCHAR(10) NOT NULL DEFAULT pending, PRIMARY KEY (order_id), KEY idx_order_date (order_date), CONSTRAINT fk_orders_cust FOREIGN KEY (cust_id) REFERENCES customer (cust_id), CONSTRAINT fk_orders_emp FOREIGN KEY (emp_id) REFERENCES employee (emp_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表; -- 5. 订单明细表依赖 orders 和 flower最后建 CREATE TABLE order_item ( item_id INT UNSIGNED AUTO_INCREMENT, order_id INT UNSIGNED NOT NULL, flower_id INT UNSIGNED NOT NULL, quantity INT UNSIGNED NOT NULL COMMENT 购买数量, unit_price DECIMAL(10,2) NOT NULL COMMENT 成交单价下单时快照, PRIMARY KEY (item_id), UNIQUE KEY uk_order_flower (order_id, flower_id), CONSTRAINT fk_item_order FOREIGN KEY (order_id) REFERENCES orders (order_id) ON DELETE CASCADE, CONSTRAINT fk_item_flower FOREIGN KEY (flower_id) REFERENCES flower (flower_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单明细表;这段 DDL 里有几个参数不是随手写的。DECIMAL(10,2) 用来存钱而不是 FLOATFLOAT 在金额累加时会出现浮点误差答辩时老师一定会问“为什么不用 double”UNIQUE KEY uk_order_flower 防止同一订单里重复录入同一种花order_item 的 unit_price 存的是成交时的快照价花价改了不影响历史订单ON DELETE CASCADE 放在明细表上删订单时明细一并清掉避免残留孤儿数据。至于花材表不要给它加级联删除花材被历史订单引用了就应该被阻止这在语义上是对的。建表之后马上验证一下外键是否生效这一步花两分钟就能做-- 查看建表语句是否带上了正确的约束 SHOW CREATE TABLE order_item; -- 故意插入一个不存在的客户触发外键错误 INSERT INTO orders (cust_id, emp_id) VALUES (9999, 1); -- 输出: ERROR 1452 (23000): Cannot add or update a child row: -- a foreign key constraint fails这一步很多人会跳过但我建议把它留在备查笔记里。外键约束生效的证据在答辩演示时比任何口头描述都有说服力。字符集对齐的问题还要再强调一次库、表、连接串三者字符集必须一致常见的“中文乱码”基本都是库是 utf8mb4、连接时却写了 gbk或者表字段漏了字符集设置这个我第 5 章还会展开讲。3. 花店管理系统的增删改查从 SQL 到 Python 后端落地DDL 建完之后课程设计的大头在业务功能的增删改查上。评审老师会重点看三件事能不能连上数据库、参数化查询有没有用、事务场景处理得是否完整。这一章按“连接、SQL、Python 封装”三层往下走。3.1 连接数据库pymysql 直连和连接池的取舍课程设计阶段我用 pymysql 直连就完全够用。它简单、纯 Python、没有 ORM 的编译成本能把 SQL 原汁原味写出来调试时想要看到的所有 SQL 错误都能直接抛出来。连接池在这个体量下不是必须的但如果想展示“我知道真实环境的问题”可以加一个最小连接池版本我一般会在答辩口头描述里提一句而不是强行上线。# db.py —— 最小数据库连接封装 import pymysql DB_CONFIG { host: localhost, port: 3306, user: root, password: 123456, database: flower_shop, charset: utf8mb4, cursorclass: pymysql.cursors.DictCursor, # 返回字典取字段名直白 autocommit: False, # 事务手动控制 } def get_conn(): return pymysql.connect(**DB_CONFIG)参数说明里藏着两个关键点。cursorclass 用 DictCursor 后查询结果是一个个 dict写row[flower_name]比row[0]可读性好太多答辩演示时也更好看。autocommit 必须手动关掉否则后面演示回滚时你会看到“明明调了 rollback 数据还在”的诡异现象实际上是因为每条语句自动提交了。连接时最容易踩的坑是密码里带特殊字符比如admin2024直接拼字符串会让 pymysql 把后面的内容当端口解析。解决办法是像上面那样用关键字参数传不要拼 DSN。还有一个隐藏问题是 MySQL 8 默认的认证插件是 caching_sha2_password老版本 pymysql 会报认证错误升级到 pymysql 1.0 或改用 mysqlclient 能解决这个坑几乎每个学生在机房都撞过。3.2 核心业务 SQL订单入库、库存扣减与当日营业额花店系统离不开三个 SQL下单、查库存、算营业额。下单在真实场景里不是一个 INSERT 能搞定的它要拆成事务里的一组操作。看看这个典型的下单流程——先插订单头拿到 order_id再对订单里的每一种花锁行查价和库存校验通过后扣库存、写明细最后回写订单总额。每一步都不能少少了后面统计就对不上。-- 步骤 1创建订单头 INSERT INTO orders (cust_id, emp_id, status) VALUES (1, 2, pending); -- 拿到 LAST_INSERT_ID() 作为后续明细的 order_id -- 步骤 2查花材的当前单价和库存注意 FOR UPDATE 锁行 SELECT unit_price, stock FROM flower WHERE flower_id 3 FOR UPDATE; -- 步骤 3扣库存WHERE 条件带上数量校验做二次保险 UPDATE flower SET stock stock - 3 WHERE flower_id 3 AND stock 3; -- 步骤 4写明细unit_price 用刚才查到的快照价 INSERT INTO order_item (order_id, flower_id, quantity, unit_price) VALUES (1, 3, 3, 15.50); -- 步骤 5更新订单总额 UPDATE orders SET order_amount ( SELECT SUM(quantity * unit_price) FROM order_item WHERE order_id 1 ) WHERE order_id 1;这里有两个细节值得展开。FOR UPDATE 是悲观锁它在事务里锁住那一行花材别的连接此刻不能改它防止两个人同时买同一种花导致库存扣成负数。UPDATE 的 WHERE 条件再加一次stock 3是双保险即使前面 SELECT 的锁没生效比如忘开事务这条 UPDATE 也会因为影响行数为 0 而让应用层知道库存不足。这只是伪装的超卖防线MySQL 的 InnoDB 在提交前持有锁真正的高并发还得靠唯一约束或者 Redis 预扣但课程设计里做到这层已经够了。当日营业额的计算是所有报表的基础SQL 写法也有讲究-- 按天汇总已完成订单 SELECT DATE(order_date) AS sale_date, COUNT(*) AS order_cnt, SUM(order_amount) AS total_amount FROM orders WHERE status completed GROUP BY DATE(order_date) ORDER BY sale_date DESC;GROUP BY DATE(order_date) 是把 DATETIME 截断成天再分组GROUP BY order_date 会把你每天的所有订单按秒拆成不同组这个错误我在帮别人改课程设计时几乎每次都能看到。ORDER BY 一定要跟着 GROUP BY 写否则结果顺序是随机的演示时容易出洋相。3.3 把增删改查封装成 Python 函数一个最小可用的后端后端我习惯分层db.py 管连接dao.py 管 SQLmain.py 用命令行菜单做演示。课程设计阶段不需要上 Flask一个命令行菜单足够但分层思路要保留后面如果愿意扩展成网页dao 层可以直接复用。# dao.py —— 对花材表做增删改查 import pymysql from db import get_conn def add_flower(name, category, price, stock): with get_conn() as conn: with conn.cursor() as cur: cur.execute( INSERT INTO flower (flower_name, category, unit_price, stock) VALUES (%s, %s, %s, %s), (name, category, price, stock), ) conn.commit() return cur.lastrowid def update_flower_price(flower_id, new_price): with get_conn() as conn: with conn.cursor() as cur: cur.execute( UPDATE flower SET unit_price%s WHERE flower_id%s, (new_price, flower_id), ) conn.commit() return cur.rowcount # 返回受影响行数0 表示没找到 def search_flowers(keyword): with get_conn() as conn: with conn.cursor() as cur: cur.execute( SELECT flower_id, flower_name, category, unit_price, stock FROM flower WHERE flower_name LIKE %s, (f%{keyword}%,), ) return cur.fetchall() def delete_flower(flower_id): with get_conn() as conn: with conn.cursor() as cur: try: cur.execute(DELETE FROM flower WHERE flower_id%s, (flower_id,)) conn.commit() return cur.rowcount except pymysql.err.IntegrityError: conn.rollback() return -1 # 被外键引用时返回 -1上层提示不能删除这段代码的三个细节就是答辩加分点。第一用%s占位符传参而不是拼字符串能防 SQL 注入——这个老师问的概率极高。第二delete_flower 里捕获 IntegrityError说明程序意识到外键约束拒绝了删除而不是让整个程序崩掉返回 -1 给上层提示“该花材已有历史订单不能删”。第三with get_conn() as conn会在代码块走完后自动关闭连接不会出现连接泄漏。不过要留神pymysql 的with conn只保证关闭连接不自动提交或回滚所以 commit 得自己显式调用。写到这里增删改查已经覆盖了单表的 90%。剩下那 10% 是“订单 订单明细”这种多表级联操作我在第 4 章讲事务时会补齐。4. 索引、事务与视图让花店系统从“能跑”到“能扛”建表和增删改查只是及格线。评分更高的那档通常看你有没有做三件事给高频查询建索引、用事务保护资金相关的操作、用视图把复杂查询封装起来。这一章按照三个方向把系统从“能跑”推到“能扛”。4.1 索引怎么建看查询计划别拍脑袋也别全加索引不是越多越好。每个索引都要占磁盘空间写入时还要维护 B 树但课程设计阶段的重点是思路正确。我一般只给三类字段建索引外键、日期查询条件、高频 where 条件。-- 订单明细表的外键上加索引避免 JOIN 时全表扫描 CREATE INDEX idx_item_flower ON order_item(flower_id); -- 复合索引状态 日期覆盖后台最常见的过滤条件 CREATE INDEX idx_orders_status_date ON orders(status, order_date); -- 花材表按分类查询也常见单列索引就够 CREATE INDEX idx_flower_category ON flower(category);这里有个高频误区以为索引建了就一定快但复合索引要满足最左前缀原则。idx_orders_status_date 这种复合索引单独查 status 能走索引单独查 order_date 却不走因为最左列是 status。所以在设计复合列顺序前先搞清楚哪个条件最常用。调试时用 EXPLAIN 看查询计划别靠猜EXPLAIN SELECT * FROM orders WHERE status completed AND order_date 2024-01-01; -- 关注 type 列ref 或 range 说明用上了索引ALL 就是全表扫描如果看到 type 是 ALL就得想想为什么没走索引可能数据量太小优化器觉得全表扫更快可能索引建错了列可能查询条件里对索引列做了函数运算。花店这个数据量下mysql 还会选择不用索引优化的策略所以不要迷信说明要会看 EXPLAIN 输出再下判断。我在课程设计报告里会把 EXPLAIN 截图放进去答辩时老师能直观看到你会用工具。4.2 事务下单扣库存为什么必须放在同一个事务里资金和库存相关的操作我只讲一个原则要么全部成功要么全部回滚。手写事务的范例放在 3.2 节对应的 Python 函数里现在再补一个完整版本展示 commit 和 rollback 的位置。# order_dao.py —— 事务版下单函数 from db import get_conn def create_order(customer_id, emp_id, items): conn get_conn() try: conn.begin() with conn.cursor() as cur: # 1. 创建订单头拿到自增主键 cur.execute( INSERT INTO orders (cust_id, emp_id) VALUES (%s, %s), (customer_id, emp_id), ) order_id cur.lastrowid total 0.0 # 2. 逐条处理明细 for flower_id, qty in items: cur.execute( SELECT unit_price, stock FROM flower WHERE flower_id%s FOR UPDATE, (flower_id,), ) row cur.fetchone() if row is None: raise RuntimeError(f花材 {flower_id} 不存在) if row[stock] qty: raise RuntimeError(f花材 {flower_id} 库存不足) cur.execute( UPDATE flower SET stockstock-%s WHERE flower_id%s, (qty, flower_id), ) cur.execute( INSERT INTO order_item (order_id, flower_id, quantity, unit_price) VALUES (%s, %s, %s, %s), (order_id, flower_id, qty, row[unit_price]), ) total qty * row[unit_price] # 3. 回写订单总金额 cur.execute( UPDATE orders SET order_amount%s WHERE order_id%s, (total, order_id), ) conn.commit() return order_id except Exception as e: conn.rollback() # 任何一步失败订单、库存、明细全不落库 raise e finally: conn.close()这段代码用事务保证了一条铁律订单头和明细不完整订单不落库库存扣了但明细写失败库存自动回滚。conn.begin()和配套的 commit/rollback 都是手动调用不能靠 pymysql 自动提交。事务的隔离级别默认是 REPEATABLE READMySQL 的 InnoDB 在这个级别下SELECT ... FOR UPDATE会锁住命中的行直到事务结束所以“先查后改”的两个连接之间不会互相踩脚。如果事务里同时更新多张表顺序要一致否则两个事务互相等对方锁住的资源就会死锁MySQL 会报Deadlock found并自动回滚其中一个。答辩时能说清死锁规避的人不多你说了就是亮点。4.3 视图和权限给老板一个只读的营业看板课程设计里最出效果的一步是建两个视图一个给老板看每日营业额一个是库存预警视图。视图不是表它只是个暂存的 SELECT但演示时的意义在于“我只给了老板一个只读入口他没权限直接动底层数据”。权限设计也是数据库课程设计里容易被忽略的得分点。-- 老板看板每日销售汇总 CREATE VIEW v_daily_sales AS SELECT DATE(order_date) AS sale_date, COUNT(DISTINCT order_id) AS order_cnt, SUM(order_amount) AS total_amount FROM orders WHERE status completed GROUP BY DATE(order_date); -- 库存预警低于安全线的花材 CREATE VIEW v_stock_warning AS SELECT flower_id, flower_name, stock FROM flower WHERE stock 10; -- 创建只读账号并授权 CREATE USER IF NOT EXISTS bosslocalhost IDENTIFIED BY boss123; GRANT SELECT ON flower_shop.v_daily_sales TO bosslocalhost; GRANT SELECT ON flower_shop.v_stock_warning TO bosslocalhost;视图和直接写 SQL 的区别是什么它把复杂的分组逻辑封装成一次SELECT * FROM v_daily_sales老板角色哪怕不懂表结构也能用。注意视图不能随便更新底层表多表关联视图的更新约束很严所以我们只做只读查询。权限上GRANT SELECT就够了千万不要给 boss 授 INSERT、UPDATE 这类写权限。别小看这个账号设计课程设计答辩时老师经常问“你的系统怎么区分老板和店员”你直接把这条 SQL 亮出来比解释一堆前端判断强得多。5. 花店管理系统的避坑指南这五个坑我当年都踩过这一章写课程设计里最防不胜防的五个坑。每条按“现象 → 原因 → 解决”展开你可以直接照着排查。5.1 中文乱码表里存的是“???”不是花名现象往 flower 表插入“玫瑰花”select 出来变成“????”或一堆乱码。 原因插入时连接字符集和表字符集不一致。最常见的情况有两种pymysql.connect 里写了 charsetgbk但库表建的是 utf8mb4或者建库时没指定字符集MySQL 兜底用了 latin1。 解决三步统一字符集。第一步库和表的 DEFAULT CHARACTER SET 改 utf8mb4第二步pymysql.connect 显式写 charsetutf8mb4第三步连接后执行SET NAMES utf8mb4兜底。排查时用SHOW CREATE TABLE flower看表的默认字符集再用SHOW VARIABLES LIKE character%看连接端两边一对比就知道谁在捣乱。5.2 外键删不掉为什么 DROP TABLE customer 报错现象想删掉 customer 表重置数据MySQL 报 Error 3730提示有外键依赖。 原因orders 表的外键引用了 customer父表不能在有子表引用的情况下直接删除。报错本身说明你的外键约束生效了这是好事不是软件故障。 解决重置数据时按依赖顺序删——先删 order_item再删 orders最后删 customer或者临时执行SET FOREIGN_KEY_CHECKS0再删演示完记得改回 1。生产环境不推荐直接禁用外键检查课程设计演示时用一下无妨但要能向老师解释清楚“为什么有外键删不掉”这才是考点。5.3 统计对不上当日营业额数字比实际少了一半现象按日算营业额结果比实际订单金额少了好几单怎么对都对不上。 原因orders.order_date 是 DATETIME直接 GROUP BY 会按秒分组有人虽然用了DATE()截断到天但忘了在 WHERE 里过滤statuscompleted把 pending 的订单也统计进去还有一种情况是订单明细里的 unit_price 被改过价格导致 SUM(quantity * unit_price) 与订单头 order_amount 不一致。 解决写报表 SQL 时先加WHERE statuscompleted再用DATE(order_date)分组。至于价格快照问题这就是我在 DDL 里强调的“order_item.unit_price 存在成交快照价”的意义——明细价算出来的总额永远不会被花材改价影响两个口径对得上。5.4 事务回滚不生效库存被扣了但订单丢了现象程序抛异常后打印了 rollback 执行了但重新打开数据库发现库存确实被扣了订单却不存在数据不一致。 原因MySQL 的表引擎是 MyISAM。MyISAM 不支持事务事务内的 UPDATE 会立即生效rollback 等于没执行所以库存扣减和订单插入变成了“一个成功一个失败”的状态。 解决建表语句里显式写ENGINEInnoDB并检查所有业务表的引擎。排查命令用SHOW TABLE STATUS WHERE Engine ! InnoDB;凡是结果里有表的都要 ALTER 改回 InnoDB。这个坑的隐蔽之处在于建表时不写 ENGINE 大概率默认就是 InnoDBMySQL 8但在机房老版本 MySQL 5.5 里默认引擎可能是 MyISAM所以一定要写。5.5 连接泄漏程序跑二十轮后报 Too many connections现象本地测试增删改查前面十几次都正常后面 MySQL 报 “Too many connections”。 原因pymysql 每次 connect 都会占一个连接代码里忘了 close()连接就一直在内存里挂着MySQL 默认最大连接数是 151跑着跑着就被占满了。 解决用上下文管理。3.3 节写的with get_conn() as conn会自动关闭连接这是最省事的方式如果用了自定义连接池记得设置 max_connections 和回收策略。答辩时你可以说“我用 with 语句保证连接生命周期”这句话比滔滔不绝讲半小时代码有说服力得多。排查当前连接数用SHOW PROCESSLIST;就能看到哪些连接还开着。6. 最后一步从“代码能跑”到“答辩能讲”的验证与加分课程设计的最后冲刺很多人只顾着让程序别报错却忽略了“让老师听懂你做了什么”这个更关键的目标。我的习惯是留出半天做三件事数据自查、回归脚本、演示脚本。这三件事做完答辩底气完全不一样。数据自查很简单打开 MySQL 后检查每个表的行数、抽样看几条记录确认没有乱码、没有重复数据、外键关系一致。这是一个徒手查漏的环节比如用这条 SQL 快速检查订单明细里是否有“孤儿数据”——所谓孤儿数据就是明细表里关联不到订单头的数据SELECT * FROM order_item oi LEFT JOIN orders o ON oi.order_id o.order_id WHERE o.order_id IS NULL;这个查询结果应为空如果非空说明某次删除订单头时明细没被级联删除得回头检查外键的 ON DELETE CASCADE 是否生效。然后再跑一遍自动化回归脚本# test_flower_shop.py —— 核心 CRUD 回归脚本 from dao import add_flower, update_flower_price, search_flowers, delete_flower def test_crud(): fid add_flower(测试玫瑰, 玫瑰, 15.00, 50) print(新增花材 ID:, fid) assert update_flower_price(fid, 18.00) 1, 改价失败 rows search_flowers(玫瑰) assert rows and any(r[flower_id] fid for r in rows), 查询失败 assert delete_flower(fid) 1, 删除失败 print(CRUD 回归全部通过) if __name__ __main__: test_crud()回归脚本的价值是答辩前一晚改了某段代码第二天上台前跑一遍脚本能在 10 秒内确认核心功能没被改坏。没有这个脚本你只能手动点几十次菜单点得头晕眼花还漏了某个分支。跑完回归脚本后,顺便把第 4 章讲到的EXPLAIN输出、SHOW CREATE TABLE输出截图准备好答辩演示时直接用 SQL 语句现场展示不要只放截图。最后一个加分项是给 v_daily_sales 视图加一个 CSV 导出功能用 Python 把每日营业额写成报表文件。这只是一次简单的查询加文件写入但它把你的项目从“数据库演示”提升到了“数据应用”的高度——老师经常问“统计结果除了在屏幕上显示还能怎么用”你当场导出一个 CSV 文件这个印象分很实在。# export_daily_sales.py —— 导出每日营业额 import csv from db import get_conn def export_csv(pathdaily_sales.csv): with get_conn() as conn: with conn.cursor() as cur: cur.execute(SELECT * FROM v_daily_sales) rows cur.fetchall() with open(path, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnameslist(rows[0].keys())) writer.writeheader() writer.writerows(rows) print(已导出, path)这里用 utf-8-sig 编码而不是 utf-8是因为 Excel 打开 UTF-8 无 BOM 的 CSV 会把中文显示成乱码加 BOM 后就正常了。这个细节不大但答辩时打开 Excel 文件的那一瞬间老师看到中文正常显示比什么都强。我在课程设计验收季见过太多项目死在“演示时手一抖输错了参数”这种事上。后来养成一个习惯答辩前一天把演示流程从头到尾走一遍包括故意输错密码、查一个不存在的花材、删一个被引用花材把这些“异常路径”的提示消息也截进演示文档里。老师问“你这个系统能处理异常吗”你直接展示那些友好报错,比临时编理由靠谱得多。一句话程序崩了不可怕可怕的是不知道怎么把它讲圆。希望这篇笔记能帮你少走点弯路把花店管理系统做成一块能拿得出手的敲门砖。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?