简介:基于Python+PyQt+SQLServer的图书管理系统,是一份面向数据库课程设计的高分项目源码,适合计算机相关专业学生、教师及入门开发者用于课设、作业或项目起步。资源共55个文件,涵盖22个Python源码文件、21个编译缓存文件、3个SQL建库与数据脚本以及说明文档、图片等,压缩包整体仅122KB,轻量且目录清晰。已有256人浏览学习。项目提供完整的图书管理功能模块,如书籍管理、借阅归还、读者管理、图书馆证操作等,并附带详细说明和全部数据资料;代码在mac/window10/11/linux下测试通过,可在现有基础上修改扩展,直接用于答辩或进一步学习数据库与桌面应用开发。
1. 数据库课设为什么选这套:Python+PyQt+SQL Server的图书管理系统其实不一般
数据库课设最怕的不是题目难,而是题目太常见。“图书管理系统”几乎每个班都有人做,交上去的版本大多是Java+MySQL或者C#+SqlServer,老师翻两眼就知道工作量在哪。这套基于Python+PyQt+SQL Server的图书管理系统,赢不是赢在窗口多漂亮,而是赢在数据库端能展示的考点足够多:外键约束、事务、存储过程、视图索引,全都有地方落脚。
PyQt把界面画出来,Python管业务逻辑,SQL Server存数据并承担完整性控制,三个角色划分清楚。如果你正在选课设方向,或者手边已经拿到这份源码还在研究怎么跑起来,这篇就按“这个东西是什么→怎么跑通→坑在哪→怎么拿高分”的顺序拆给你。适合谁?适合想借一套现成骨架快速理清课设逻辑、又不想在答辩时被“你的数据库设计体现在哪”问倒的从业者和学生。
2. 拆解系统设计与数据库建模:五张核心表怎么落到三范式
2.1 借阅流程先于编码:先理清读者、图书、借阅之间的约束关系
图书管理系统的业务听起来很简单,但动手建表前必须把流程画清楚。常见流程是:管理员维护图书和读者信息;读者来借书,管理员检查读者是否有未还逾期记录、这本书是否还有可借副本,通过后扣减库存并生成一条借阅记录;还书时更新归还日期并恢复库存,如果超期还要生成罚款记录。
这个流程直接决定了表之间的关系。读者和图书之间是多对多,一个读者借多本书,一本书被多个读者借过,所以需要一张借阅表做桥。每本图书有“总馆藏”和“可借数量”两个概念,因为同一种书在馆里往往有多个副本,借走一本还剩几本,必须分开记录。罚款与借阅是一对一关系,一次借阅最多对应一条罚款记录,不能重复罚。
这些约束如果在写代码之前不定清楚,后面建表、写事务和界面提示全部会乱。答辩时老师最常追问的就是“为什么借书要同时改三张表”“超期罚款怎么保证只产生一次”,提前想清楚流程,答辩就不慌。
2.2 五张核心表的建表语句与完整性约束
我一般会把表拆成五张:管理员表、读者表、图书表、借阅表、罚款表。下面是实际可执行的SQL Server建表脚本,按顺序执行即可:
-- 管理员表:存登录账号和加盐哈希密码 CREATE TABLE admin ( admin_id INT IDENTITY(1,1) PRIMARY KEY, admin_name NVARCHAR(20) NOT NULL UNIQUE, password_hash NVARCHAR(64) NOT NULL, -- SHA-256十六进制结果 salt NVARCHAR(32) NOT NULL, -- 随机盐 create_time DATETIME DEFAULT GETDATE() ); -- 读者表 CREATE TABLE reader ( reader_id INT IDENTITY(1,1) PRIMARY KEY, reader_name NVARCHAR(20) NOT NULL, gender CHAR(2) CHECK (gender IN ('男','女')), phone VARCHAR(20) UNIQUE, max_borrow INT DEFAULT 5 CHECK (max_borrow BETWEEN 1 AND 20), create_date DATE DEFAULT CAST(GETDATE() AS DATE) ); -- 图书表:一个book_id对应一个馆藏副本品种 CREATE TABLE book ( book_id INT IDENTITY(1,1) PRIMARY KEY, isbn VARCHAR(20) NOT NULL, -- 同书不同版可多个ISBN,不设唯一 title NVARCHAR(100) NOT NULL, author NVARCHAR(50) NOT NULL, publisher NVARCHAR(50) NOT NULL, publish_year INT, category NVARCHAR(20), total_copies INT DEFAULT 1 CHECK (total_copies >= 0), available_copies INT DEFAULT 1 CHECK (available_copies >= 0), location NVARCHAR(50) ); -- 借阅表:桥接读者与图书 CREATE TABLE borrow ( borrow_id INT IDENTITY(1,1) PRIMARY KEY, book_id INT NOT NULL FOREIGN KEY REFERENCES book(book_id), reader_id INT NOT NULL FOREIGN KEY REFERENCES reader(reader_id), borrow_date DATE NOT NULL DEFAULT CAST(GETDATE() AS DATE), due_date DATE NOT NULL, return_date DATE NULL, CHECK (due_date >= borrow_date), CHECK (return_date IS NULL OR return_date >= borrow_date) ); -- 罚款表:一次借阅最多一条罚款 CREATE TABLE fine ( fine_id INT IDENTITY(1,1) PRIMARY KEY, borrow_id INT NOT NULL UNIQUE FOREIGN KEY REFERENCES borrow(borrow_id), amount DECIMAL(10,2) NOT NULL CHECK (amount >= 0), status VARCHAR(10) DEFAULT '未缴纳' CHECK (status IN ('未缴纳','已缴纳')), pay_date DATE NULL );字段类型是最值得说的两个点:中文名称字段一律用NVARCHAR而不是VARCHAR,因为SQL Server的VARCHAR默认按数据库排序规则存储,中文很容易在后面对接时变成乱码,NVARCHAR不管数据库排序规则怎么配都能正确存中文。所有数值字段都加了CHECK约束,比如库存不能小于0、罚款金额不能为负数。这些约束看着简单,但它们是数据库层“防御性设计”的第一道关卡,比在Python里一层层if判断显得专业得多。
借阅表里两条CHECK约束保证“应还日期不早于借书日期”“归还日期不早于借书日期”,比在应用程序里校验更可靠,因为数据库约束对任何入口都生效,哪怕以后换一套前端,规则也不会丢。
2.3 为什么不建议强行过度设计:范式够用就好
经常有同学为了让设计“看起来高级”,把出版社单独建一张表、图书分类单独建一张表,再把读者拆成会员等级表。结果是查询的时候每次都要JOIN四张表,数据初始化工作量翻一倍,演示时还容易因为一张表没数据而显得空荡荡。
“三范式”是评价标准,不是强制规范。当前五张表已经是第三范式:没有传递依赖,读者和借阅之间除了主外键不再冗余其他读者字段,图书也一样。出版社、分类这类字段在这个业务里就是图书的一个属性,合并在一张表里不违反范式,也不破坏可用性。过度拆分反而会让系统在演示借阅查询时频繁联表,性能变差不说,答辩老师一句“你这些表设计解决什么具体问题”就可能把你问住。
记住一条边界:课设的目标是证明你理解关系模型、完整性约束、事务和索引,而不是证明你能设计一套企业级数据仓库。把五张表的约束讲清楚,比堆二十张空表更有说服力。
3. 从源码压缩包到本地跑通:文件职责与数据库初始化全流程
3.1 解压后先看什么:源码目录结构与文件职责
拿到压缩包第一件事不是双击main.py,而是先看目录和说明文档。这类Python+PyQt课设项目的目录结构通常是典型的分层写法,不管文件名有没有出入,职责基本一致:
LibraryMS/ ├─ main.py # 程序入口:登录窗口拉起主窗口 ├─ requirements.txt # Python依赖清单 ├─ README.md # 环境配置与运行步骤 ├─ db/ │ ├─ connection.py # 数据库连接封装(驱动、连接串、游标) │ ├─ init.sql # 建库建表初始化脚本 │ └─ sample_data.sql # 演示数据 ├─ services/ │ ├─ auth_service.py # 登录验证、密码哈希 │ ├─ borrow_service.py # 借书、还书、罚款生成 │ └─ query_service.py # 查询、统计、报表 ├─ ui/ │ ├─ login_window.py # 登录窗口 │ ├─ main_window.py # 主窗口框架 │ ├─ book_dialog.py # 图书管理弹窗 │ └─ reader_dialog.py # 读者管理弹窗 └─ resources/ # 图标、QSS样式requirements.txt里一般会有PyQt5、pyodbc或pymssql,也可能有Pandas用于统计。先确认缺什么库,用pip一次性装齐:
pip install PyQt5 pyodbc pandas注意PyQt5在较新版本Python上可能没有对应wheel,如果安装失败,把Python降到3.9或3.10一般就能解决。这是环境配置里最常见的坑之一。
3.2 数据库初始化:用脚本建库,不依赖附加MDF
SQL Server数据库有两种交付方式:一种是直接把MDF和LDF两个文件发给你,用附加的方式挂载;另一种是给SQL脚本,从头执行建库建表。课设项目通常两种都给,但我强烈建议你优先用脚本建库。
原因有二。第一,MDF文件存在版本兼容问题:用高版本SQL Server创建的库,低版本附加不上,但低版本创建的库高版本可以附加。如果你SqlServer版本比作者的低,附加时大概率报“数据库版本高于当前实例”,这时只能靠脚本重建。第二,附加时需要处理文件路径和权限,放错目录直接附加失败,脚本方式则没有这个烦恼。
执行脚本最简单的方式是在SQL Server Management Studio中打开db目录下的init.sql,先创建数据库,再切到该库继续执行建表和初始化数据:
-- init.sql 头部固定写法 IF DB_ID(N'LibraryMS') IS NULL CREATE DATABASE LibraryMS; GO USE LibraryMS; GO -- 接着执行上一章的表结构、索引、存储过程脚本如果你的版本比较老,不支持IF DB_ID这种语法,直接手动创建数据库再执行剩余脚本也行。初始化数据脚本一般包含管理员账号和20条以上图书、读者记录,只有表结构没有数据,做演示时光录入数据就够你折腾半天。
3.3 Python端连接SQL Server:pyodbc与pymssql的选型和连接串说明
连接SQL Server的Python方案常见两种:pyodbc和pymssql。pyodbc走系统ODBC驱动,Windows上配合SQL Server原生驱动最稳定;pymssql是纯Python封装,跨平台更方便,但在事务和某些数据类型上不如pyodbc顺手。Windows上做课设,我一般直接用pyodbc。
# db/connection.py import pyodbc def get_conn(): conn = pyodbc.connect( "DRIVER={ODBC Driver 17 for SQL Server};" "SERVER=localhost,1433;" "DATABASE=LibraryMS;" "UID=sa;" "PWD=你自己的密码;" "TrustServerCertificate=yes;" "Encrypt=no;" ) return conn连接串里每一项都有讲究。SERVER写成localhost,1433,逗号后面是SQL Server默认端口,如果你实例是命名实例或者端口改过,这里必须对应改。UID和PWD对应SQL Server登录账户,不是Windows账户。TrustServerCertificate和Encrypt这两个选项和本地连接安全协议有关,本地课设环境填yes和no最省事,如果你用的是新版本ODBC驱动,不写Encrypt=no有时会强制加密而连接失败。
运行一段测试代码,能取到数据库版本就说明连通了:
import pyodbc from db.connection import get_conn conn = get_conn() cursor = conn.cursor() cursor.execute("SELECT @@VERSION") print(cursor.fetchone()[0])如果这里报错,先别怀疑代码,八成是SQL Server本身没配好,直接跳到第5章排查。
4. 核心功能实现拆解:登录、借还书事务与组合查询
4.1 登录模块:加盐哈希比明文密码靠谱得多
很多课设源码的管理员表直接把密码明文存进去,打开表一看就是admin、123456。演示很方便,但答辩时老师一句“如果数据库泄露,密码不是全暴露了”就能让这个项目降一档。正确做法是存加盐哈希,校验时把用户输入做同样的哈希再比对,敏感信息不落库。
import hashlib, os def hash_password(password: str) -> str: salt = os.urandom(16).hex() digest = hashlib.sha256((salt + password).encode("utf-8")).hexdigest() return f"{salt}${digest}" def verify_password(password: str, stored: str) -> bool: salt, digest = stored.split("$") real = hashlib.sha256((salt + password).encode("utf-8")).hexdigest() return real == digest为什么加盐而不直接对密码做SHA256?因为相同密码不加盐会产生相同哈希,攻击者用彩虹表就能反查出密码。加盐后即使八个管理员的密码都是123456,库里存的哈希也完全不同。
调用登录验证时,在service层按“用户名→取哈希和盐→校验→返回结果”的顺序执行,不要在主窗口里写一堆游离的SQL。这样后面改成任何界面,登录逻辑都能复用。
4.2 借书还书:一个事务绑住四条SQL,杜绝半截操作
借书这个动作至少涉及两步:扣减图书表的可借数量、往借阅表插入一条记录。如果这两步之间程序崩溃,就会出现“库存扣了但借阅记录没生成”的脏数据。还书更复杂,要更新归还日期、恢复库存,超期还要插入罚款记录。
解决思路就是把多步操作包进同一个事务。pyodbc默认autocommit是True,必须手动关掉才能控制事务边界:
def borrow_book(conn, reader_id, book_id, days=30): cursor = conn.cursor() try: conn.autocommit = False cursor.execute( "SELECT available_copies FROM book WHERE book_id = ?", (book_id,) ) row = cursor.fetchone() if row is None: raise ValueError("图书不存在") if row[0] <= 0: raise ValueError("当前无可借副本") cursor.execute( "UPDATE book SET available_copies = available_copies - 1 " "WHERE book_id = ? AND available_copies > 0", (book_id,) ) if cursor.rowcount != 1: raise ValueError("扣减库存失败") cursor.execute( "INSERT INTO borrow(book_id, reader_id, borrow_date, due_date) " "VALUES (?, ?, CAST(GETDATE() AS DATE), DATEADD(DAY, ?, CAST(GETDATE() AS DATE)))", (book_id, reader_id, days) ) conn.commit() except Exception: conn.rollback() raise finally: conn.autocommit = True注意一个细节:UPDATE语句里带了AND available_copies > 0条件,并且通过cursor.rowcount判断影响行数。两个连接同时借同一本书最后一本副本时,只有一个人能成功把库存从1改成0,另一个人影响行数为0,直接走异常回滚。这就是乐观锁思路,比先SELECT再UPDATE更安全。
参数全部用?占位符传给驱动,避免字符串拼接SQL。既能防SQL注入,也避免中文或特殊符号在拼接时出问题。
还书逻辑同样用事务:先查借阅记录是否存在且未归还,再更新return_date,然后恢复库存,最后判断是否超期,超期就按天数和每日罚金计算金额插入fine表。罚款金额计算放在Python里还是存储过程里都行,放在SQL里更方便在服务端统一规则,这一点第6章会展开。
4.3 组合查询与统计:WHERE 1=1加参数化,统计用GROUP BY
图书管理系统的查询往往是组合条件:按书名模糊查、按分类查、按出版社查,读者可能同时填两个条件。常见做法是动态拼接SQL,用WHERE 1=1起步,逐条追加条件:
def query_books(title=None, category=None, publisher=None): sql = "SELECT book_id, title, author, publisher, category, available_copies FROM book WHERE 1=1" params = [] if title: sql += " AND title LIKE ?" params.append(f"%{title}%") if category: sql += " AND category = ?" params.append(category) if publisher: sql += " AND publisher = ?" params.append(publisher) return sql, paramsWHERE 1=1的作用是让后面每一条AND都能直接拼接,不用在拼SQL前先判断“这条是不是第一个条件”。代码看着有点野,但在动态查询里确实实用。重点还是参数化:所有用户输入都通过?传值,绝不直接拼进SQL字符串。
统计数据则用GROUP BY配合聚合函数,例如按分类统计在库图书量、按月统计借阅次数。这类SQL在课设里属于加分展示项,说明你不只会增删改查,还会做数据分析维度。
4.4 PyQt界面与业务分离:主窗口只做事件转发
很多课设源码的问题是把SQL直接写在按钮的槽函数里,点击“借书”按钮后,函数里又是查库存又是UPDATE,写了上百行,界面一多就乱。更清晰的做法是界面只负责收集输入、调用service层、展示结果。
# ui/main_window.py 内点击借书按钮后的槽函数 def on_borrow_clicked(self): reader_id = self.reader_id_input.value() book_id = self.book_id_input.value() try: borrow_service.borrow_book(self.conn, reader_id, book_id) self.statusbar.showMessage("借书成功", 3000) self.refresh_borrow_table() except Exception as e: QMessageBox.critical(self, "借书失败", str(e))借书事务逻辑放在services/borrow_service.py里,主窗口只负责拿到结果后刷新表格和弹提示。这样答辩时老师问“借书失败怎么处理”,你可以直接指出service层的异常处理,比在一坨槽函数里扒逻辑清晰得多。PyQt用QTableWidget展示借阅列表时,注意按行读取数据再setItem,不要在大表格上逐单元格执行SQL,否则界面卡顿感会很明显。
5. 部署与演示路上的五个高频坑:连接、乱码、外键与界面卡顿
5.1 SQL Server连接失败:TCP/IP协议没启用
现象:SQL Server Management Studio能正常打开数据库,但Python连接时一直超时,报“操作超时”或“无法连接到服务器”。第一次遇到容易怀疑连接串写错,反复改端口和密码都没用。
原因:本地用SSMS连接走的是Shared Memory协议,而Python的ODBC驱动走TCP/IP。SQL Server安装时默认可能只启用了Shared Memory和Named Pipes,TCP/IP默认关闭,第三方程序自然连不进来。
解决:打开SQL Server配置管理器,找到“SQL Server网络配置→实例名协议”,把TCP/IP启用,然后重启SQL Server服务。改完必须重启,否则配置不生效。
如果确认TCP/IP已经启用仍然连不上,多半是SQL Server Browser服务没启动。这个服务负责为命名实例提供端口映射,本地开发一般建议把Browser服务设为启动状态。
5.2 sa登录被拒:登录名和数据库用户是两层概念
现象:连接串填了sa和密码,报错“用户'sa'登录失败(错误18456)”。有时用Windows身份验证进入SSMS查sa账户明明存在,密码也重置了,还是不行。
原因:两个层面没打通。第一层,SQL Server实例的“身份验证模式”可能仍是仅Windows身份验证,不允许SQL Server账号登录;第二层,即便允许登录,sa这个登录名还要被映射到目标数据库,并分配对应权限,否则登录后也进不了LibraryMS库。
解决:在SSMS里右键实例→属性→安全性,选择“SQL Server和Windows身份验证模式”;再到“安全性→登录名→sa”里启用该账户并重置密码;最后在LibraryMS库的“用户”里创建对应映射。一个快速补救语句是:
USE LibraryMS; GO CREATE USER sa_for_lib FOR LOGIN sa; GO ALTER ROLE db_owner ADD MEMBER sa_for_lib; GO这样sa登录后就直接拥有该库的全部操作权限,省去额外授权。
5.3 中文数据显示为问号:字符集从建表那一刻就要定
现象:从Python界面往里插入中文,库里看是正常,界面刷新又变成“???”;或者库里直接存的就是问号,怎么改连接串都不行。
原因:这一类问题大部分发生在建表阶段用了VARCHAR存中文,而数据库排序规则又不匹配。VARCHAR是非Unicode类型,中文能否正确存储取决于数据库代码页;NVARCHAR是Unicode类型,任何排序规则下都能存中文。
解决:建表阶段就把所有中文文本字段定义为NVARCHAR/NCHAR类型,这是最根本的解决方案。如果已经用VARCHAR建了表,需要ALTER TABLE修改列类型。pymssql连接时还要显式加上charset="utf8",否则即使表结构正确,应用层读取也会因驱动默认字符集而乱码。pyodbc则没有这个参数,它跟随ODBC驱动配置。
5.4 删除有借阅记录的图书被外键拦截
现象:界面上删一本已经被人借过的书,直接抛外键冲突异常,删除失败,回滚。有时候演示到这一步,老师会觉得系统“有bug”。
原因:外键约束在起作用,效果是数据完整性得到保护,但用户体验不好。原因不是设计错了,而是删除策略没有提前定好:有借阅历史的书到底能不能删?借阅表里可能还有未归还的记录,物理删除会导致历史记录悬空。
解决:在代码里先查“该图书是否存在未还借阅记录”,存在则提示先处理还书再删除。对于有历史记录的图书,更合理的设计是逻辑删除,给book表加一个is_deleted字段,删除时置1,查询时默认过滤掉。这样既保留历史借阅关系,又不在界面看到旧书。外键不是坑,没有删除策略才是坑。
5.5 PyQt界面卡死:数据库操作不要放在主线程直接跑
现象:点击“查询统计”按钮后窗口直接卡住,拖不动、关不掉,过十几秒又恢复,有时直接弹“未响应”。
原因:PyQt的主线程也是事件循环所在线程,如果在里面执行同步数据库查询,查询期间窗口无法处理重绘和鼠标事件,数据量稍大就表现为界面冻结。
解决:耗时操作放到QThread里执行,或者用QTimer分片处理。简单方案是继承QThread,重写run方法执行查询,通过自定义信号把结果传回主线程更新界面。注意一个铁律:在线程里查询数据可以,但不要在线程里直接操作控件。所有界面更新必须通过信号回到主线程,否则会随机崩溃,而且这种崩溃很难复现。
class QueryThread(QThread): result_ready = pyqtSignal(list) def __init__(self, conn, sql, params): super().__init__() self.conn = conn self.sql = sql self.params = params def run(self): cursor = self.conn.cursor() cursor.execute(self.sql, self.params) rows = cursor.fetchall() self.cursor.close() self.result_ready.emit([list(row) for row in rows])主线程连接这个信号,在槽函数里刷新QTableWidget。当然,演示数据只有几十行时同步查询也能接受,但把这个线程框架写上,答辩提分明显。
6. 高分的关键不在界面而在数据库:存储过程、视图索引与答辩演示技巧
6.1 把借书逻辑封装成存储过程,应用层一次调用完成事务
借书还书的存储过程是整篇课设最亮眼的加分点。它把第4章的Python事务搬进SQL Server,应用层不再拼多条语句,只需传入读者编号、图书编号,存储过程内部完成扣库存、插借阅记录、回滚异常。下面是一个参考实现:
CREATE PROCEDURE dbo.sp_borrow_book @reader_id INT, @book_id INT, @days INT = 30, @result NVARCHAR(50) OUTPUT AS BEGIN BEGIN TRY BEGIN TRANSACTION; DECLARE @available INT; SELECT @available = available_copies FROM book WITH (UPDLOCK, ROWLOCK) WHERE book_id = @book_id; IF @available IS NULL BEGIN SET @result = N'图书不存在'; ROLLBACK; RETURN; END IF @available <= 0 BEGIN SET @result = N'无可借副本'; ROLLBACK; RETURN; END UPDATE book SET available_copies = available_copies - 1 WHERE book_id = @book_id; INSERT INTO borrow(book_id, reader_id, borrow_date, due_date) VALUES (@book_id, @reader_id, CAST(GETDATE() AS DATE), DATEADD(DAY, @days, CAST(GETDATE() AS DATE))); COMMIT; SET @result = N'借书成功'; END TRY BEGIN CATCH ROLLBACK; SET @result = ERROR_MESSAGE(); END CATCH END GO注意SELECT用了WITH (UPDLOCK, ROWLOCK),这两个表提示配合事务,能避免两个连接同时读到可借数量为1而都进入后续操作,把并发超卖挡在数据库层。这在答辩中解释清楚,是“理解并发控制”的实证。应用层调用它则非常简单:
cursor.execute("EXEC dbo.sp_borrow_book ?, ?, ?, ?", (reader_id, book_id, 30, out_param))6.2 用视图和索引补全数据库设计拼图
单独建几张表不足以展示数据库能力,增加一个视图和一个索引就能把设计与“性能意识”串起来。视图适合把高频联查封装成一张虚拟表:
CREATE VIEW v_borrow_detail AS SELECT b.borrow_id, r.reader_name, bk.title, b.borrow_date, b.due_date, b.return_date FROM borrow b JOIN reader r ON b.reader_id = r.reader_id JOIN book bk ON b.book_id = bk.book_id; GO之后应用层查询直接SELECT * FROM v_borrow_detail WHERE reader_name LIKE ?,联查逻辑收敛到数据库端。索引则建在外键列和WHERE常用列上,比如borrow表的reader_id和book_id、book表的category。数据量小时索引看不出性能差距,但字段设计是否合理、有没有索引意识,老师一眼就能看出来。
6.3 答辩演示脚本与最后的数据保全
演示顺序比演示动作更重要。我习惯按“数据模型→约束演示→事务演示→查询统计”走:先打开SQL Server的表关系图,把五张表的主外键讲一遍;再故意借一本库存为0的书,让界面弹出“无可借副本”,证明约束在生效;然后删除一个有借阅记录的读者,展示外键拦截;最后跑一次统计查询,用GROUP BY结果收尾。
演示前一定做一次数据库完整备份,或者把MDF、LDF文件单独复制一份。课设中经常出现演示中途误删数据、外键连锁误改,没有备份就只能现场圆场。数据文件是这个项目的“后悔药”,提交前确认数据库能通过脚本重新生成,再交压缩包,否则数据库文件损坏就全白费了。
我还记得某次演示,A同学现场把图书表清空了,界面还开着,管理员账号也连带被删,整个系统直接瘫痪。后来靠备份还原才救回来。从那以后我对自己说:任何演示前先备份,宁可多花两分钟,不要在答辩台上赌运气。希望帮到你。
本文还有配套的精品资源,点击获取