简介:围绕图书管理系统的数据库设计,这份PDF完整给出实体联系图、数据流图与关系模式,并附有数据库字段定义说明,适合数据库课程设计、考试复习或软件工程文档撰写参考。内容覆盖管理员、读者、书籍、书架、阅览室等核心实体,以及读者借阅表、归还表和图书类型等字典表,逐项列出字段名称、数据类型、取值范围、主外键约束与是否为空;同时梳理读者办证、信息验证、借书、还书、缴纳欠费等环节的数据流向,帮助读者从概念模型到关系模式落地形成完整理解。整个资源包内含单个PDF文档,大小约332KB,无需解压即可打开查看。已有2718人浏览学习,适合正在准备数据库考试、完成课程设计或编写需求文档的学习者;字段定义说明还可直接作为数据字典编写依据。
1. 图书管理系统E-R图PDF:为什么课程设计和数据库考试都绕不开它
上周末帮一个学弟看数据库课程设计,他把图书管理系统的E-R图画成了“一张大网”:读者和书籍之间直接连线,中间没有借阅表,读者类型也当普通字段塞在读者表里。我翻出这份PDF对着改了一个小时,发现文档里其实早把答案写清楚了。它不是一张孤立的图,而是E-R图、数据流图、关系模式、字段定义表四部分组成的完整设计材料,覆盖了“概念模型→逻辑模型→物理表定义”的整条链路。对三类人最有用:正在赶数据库课程设计的学生,备考数据库期末考试的人,以及接了个“做一个简单图书管理系统”需求但建表没头绪的开发者。看到最后你会明白,这份PDF值钱的地方不只是图,而是那些连主键、默认值都标好的字段定义。
2. 拆解E-R图:实体、属性、联系,这三层决定你的表结构
拿到PDF,先别急着看关系模式,从E-R图这一层开始梳理。实体是名词,联系是动词,属性是名词的修饰。图书管理系统里这几样分得很清楚:管理员操作业务,读者产生借还行为,书籍和某类书籍提供被借阅的对象,书架和阅览室描述存放位置,读者类型规定权限,书籍类型负责分类。只要这一层不乱,后面建表就照抄,不会跑偏。
2.1 实体清单与主键候选:为什么“某类书籍”和“书籍”要拆开
文档开头列出的实体很有代表性:管理员、账号信息、读者、读者类型、某类书籍、书籍、书籍类型、书架、阅览室。这里最容易被初学者忽略的是“某类书籍”和“书籍”的拆分。某类书籍以ISBN为主键,对应“一本书”,比如《数据库系统概论》第五版;书籍以条形码为主键,对应“一册书”,比如图书馆里那本贴了条码的、可被借走的物理副本。同一个ISBN可以对应好几本副本,这就是“库存量”和“在馆数量”挂在某类书籍上的原因。
如果把书名、作者、出版社也塞进“书籍”表,每一册书都要重复存一遍书名,改版或下架时还要批量改所有副本,数据冗余非常难看。反过来,如果把“在馆数量”挂到单本上,一次借书就要同时改两个表的数量,逻辑更乱。文档把这两层拆开,是在用实体区分“书目信息”和“馆藏信息”。我在实际项目中见过很多翻车案例,都是因为嫌表多,把这两张表合并成一张,最后做统计时要么重复计算,要么丢了副本状态。
主键也要单独说一嘴:读者账号和账号信息的关系是1:1,文档把登录账号单独放一张“账号信息”表,读者表再通过外键引用它,这样做的好处是登录认证和业务资料解耦。考试时如果让你补全E-R图,记得把“账号信息”画成独立实体,或者至少标注成读者实体的弱实体,否则评卷老师会认为你没理解权限和业务分离。
2.2 联系与基数:借阅、归还、属于、位于、拥有怎么画才不丢外键
E-R图里最核心的其实是联系及基数。文档中出现的联系可以归纳成下面这张表,每条都对应一个外键或者一张中间表:
| 联系 | 实体A | 实体B | 基数 | 转换规则 |
|---|---|---|---|---|
| 管理 | 管理员 | 读者、书籍、读者类型、书籍类型 | 1:N | 在N端加外键 |
| 拥有 | 读者 | 读者类型 | N:1 | 在读者表加读者类型外键 |
| 属于 | 书籍 | 某类书籍 | N:1 | 在书籍表加ISBN外键 |
| 归类 | 某类书籍 | 书籍类型 | N:1 | 在某类书籍表加书籍类型编号外键 |
| 位于 | 书架 | 阅览室 | N:1 | 在书架表加阅览室编号外键 |
| 借阅 | 读者 | 书籍 | M:N | 拆成读者借阅表 |
| 归还 | 读者 | 书籍 | M:N | 拆成读者归还表 |
借阅和归还是两张独立的联系,不是一张表的两列。文档里分别定义了“读者借阅表”和“读者归还表”,原因很直接:借阅表记录当前还没有归还的借出动作,归还表则保存历史记录。如果只保留一张借阅表,把实际归还日期加进去,那查询“当前谁借了这本书”就要额外过滤归还日期是否为空;而拆成两张表后,借阅表天然就是“在借列表”,查询和统计都更顺手。考试时题目经常问“图书借阅是几对几联系”,标准答案就是读者和书籍之间的多对多,必须拆中间表。
我画E-R图时的习惯是,先把实体的主键属性标记好,再把联系菱形画在中间,连线上标1和N,最后才补其他属性。原因很简单:主键决定外键,外键决定表结构。如果你一开始就纠结“邮箱要不要是唯一键”“余额用什么类型”,反而会把基数关系漏掉。
2.3 画E-R图的落地顺序:先实体、再联系、最后补属性
画一张能直接转成关系模式的E-R图,我一般按四步走。第一步,画实体矩形:管理员、读者、某类书籍、书籍、书架、阅览室、书籍类型、读者类型,一共八个实体。第二步,画菱形联系:把借阅、归还、管理、拥有、属于、位于、归类七组联系都放上去,并在连线上标注基数。注意“管理员”和“读者类型”之间也有管理联系,考试题里容易漏。第三步,补属性椭圆:从文档的字段定义表往回抄,每个实体加主键和必要属性,联系上标出它们自身的属性,比如借阅日期、续借次数、实际归还日期。第四步,检查每个联系是否都能在关系模式阶段找到落脚点——1:N联系在N端加外键,M:N联系拆表,1:1联系任选一端加外键。
这里有个容易踩的细节:书架和阅览室之间的联系“位于”,在文档里是用书架表外键指向阅览室编号来表示的,这没问题。但书架和书籍之间还有一种物理摆放关系,文档里书架表还放了“条形码”字段,说明它试图记录“哪些书放在这个书架上”。这个设计有一定语义歧义,我在后面第5章会单独说,画图阶段先知道这两个方向都存在即可。画E-R图不需要一次性画得漂亮,很多同学习惯在电脑上直接用Visio或draw.io画,但我建议先手写在草稿纸上,把实体和联系的编号列出来,确认没有漏联系再上工具,否则改来改去效率很低。
3. 关系模式与字段字典:把图转成建表SQL,字段级细节全在这
E-R图是概念模型,关系模式是逻辑模型,字段定义表则是物理模型的原材料。这份PDF把关系模式和字段定义放在一起,省去了“自己推导主外键”的中间步骤。你需要做的是从这些文字里提取出可执行的建表语句,并且理解每一个字段为什么这样设计。这一章我一边解释文档给出的关系模式,一边给出对应的MySQL写法,你照着改就能用。
3.1 关系模式清单:主键、外键、唯一键逐条对应
文档给出的核心关系模式可以整理成下面这份清单:
- 管理员(管理员账号,姓名,性别,住址)
- 账号信息(账号,密码,账号类型)
- 读者(读者账号,读者类型,是否可用,姓名,性别,系别,班级,邮箱,余额)
- 读者类型(读者类型,借书上限,借书最大时间,最大续借次数)
- 某类书籍(ISBN,书名,作者,主题,出版社,页数,价格,书籍类型编号,出版日期,库存量,在馆数量)
- 书籍(条形码,ISBN,书架编号,书籍状态,损坏程度)
- 书籍类型(书籍类型编号,书籍类型名称)
- 书架(书架编号,条形码,阅览室编号)
- 阅览室(阅览室编号,阅览室名称,阅览室位置)
- 读者借阅表(读者账号,条形码,借出日期,续借次数)
- 读者归还表(读者账号,条形码,借出日期,实际归还日期,续借次数)
把主键、外键标出来以后再读这份清单,能看到几条隐藏规则。第一,读者表的主键“读者账号”同时是外键,指向账号信息表,说明读者必须先有登录账号,才能建立业务档案。第二,书籍表的主键“条形码”之外,ISBN是外键指向某类书籍,书架编号是外键指向书架,这相当于把“哪本书”“放在哪”“现在什么状态”三个维度都挂在单本记录上。第三,借阅表和归还表的主键都用读者账号加条形码,这是多对多关系拆出来的中间表。
有同学问我,为什么归还表不直接把“借出日期”和“实际归还日期”都放在借阅表里更新?这是典型的课程设计疑问。把归还信息单独拆表,除了能保留历史,还能避免借阅表主键语义被破坏。借阅表里一条记录代表“一次尚未结束的借阅”,如果归还了还留在表里,那么“查当前在借书籍”就必须加WHERE实际归还日期IS NULL,索引和查询条件都会变复杂。拆表之后,借阅表管状态,归还表管历史,各司其职。
3.2 字段定义表拆解:数据类型、取值范围、默认值背后的取舍
PDF后半部分的字段定义表才是精华所在。它把每个表的字段、类型、范围、默认值、是否为空都列了出来。我挑几个有代表性的字段说说背后的取舍。
先看读者信息表:“是否可用”字段定义为CHAR(1),取值范围“0”和“1”,默认值“0”,其中“0”为可用。很多初学者看到这里会问,直接用TINYINT或BOOL不好吗?在MySQL里确实可以用TINYINT,但文档的写法是典型的课程设计风格,为的是在评审时一眼能看出字段含义。如果你不打算用ORM框架,这个CHAR(1)的取值方式在Java或PHP里做判断也很直观:if ("0".equals(reader.getAvailable()))。再看“余额”字段用Smallint,这是文档里一个值得商榷的地方。如果余额按“元”存,Smallint最大32767,正常读者余额够用;但如果按“分”存,也能存327元。实际建表时我更建议改成DECIMAL(10,2),方便记录带小数的欠费金额,这个属于从文档迁移到实际数据库时可以做的小改进。
读者类型表里的“借书最大时间”字段,文档标注“单位是月”,默认值2,借书上限默认6,最大续借次数默认2。这三个Tinyint字段的取值组合,决定了后续借书业务里“是否有资格借书”“是否能续借”的判断逻辑。书籍表里“损坏程度”用Tinyint,取值范围0到4,0是无损坏,4是无法使用。这种“0-4分档”的设计比纯文字枚举更规范,但如果你在MySQL里想加约束,可以用CHECK或注释说明,后面我会给出SQL写法。
还有一个容易被忽略的细节:文档某些表名有复制粘贴错误,比如某类书籍的关系模式被写成“某类书籍(ReaderType)”,字段定义表也出现了“读者类型(ReaderType)”的串名。实际这里应该是“BookInfo”才符合语义。这不是什么大问题,但你在照着写文档时要能识别出来,否则建表时把表名抄错,后面所有外键都会跟着错。
3.3 生成MySQL建表语句:主外键、Check约束、默认值一次到位
把文档转成MySQL建表语句,我通常按依赖顺序分四组执行。第一组是最基础的公共表:管理员和账号信息。
CREATE TABLE Administrator ( AdminId CHAR(15) PRIMARY KEY, AName CHAR(8) NOT NULL, ASex CHAR(2) NOT NULL CHECK (ASex IN ('男', '女')), APhone CHAR(11) NOT NULL, AAddress TEXT NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE UserMessage ( UserName CHAR(15) PRIMARY KEY, PassWord CHAR(20) NOT NULL, UserType CHAR(6) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这里把“管理员”和“账号信息”拆成两张表,是因为管理员也需要登录账号,但管理员表存的是管理员自身资料,账号表存的是认证信息。CHAR类型比较适合定长字段,姓名和电话这种长度稳定的字段用CHAR比VARCHAR好,磁盘IO略优;AAddress用TEXT是考虑到住址长度不稳定。ENGINE=InnoDB是事务和行锁的基础,字符集用utf8mb4是为了中文和生僻字都能存。
第二组是读者相关表,注意外键的引用顺序,必须先建读者类型,再建读者。
CREATE TABLE ReaderType ( ReaderType CHAR(4) PRIMARY KEY, MaxBorNum TINYINT NOT NULL DEFAULT 6, MaxTime TINYINT NOT NULL DEFAULT 2, MaxCount TINYINT NOT NULL DEFAULT 1 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE Reader ( ReaderAccount CHAR(15) PRIMARY KEY, ReaderType CHAR(4) NOT NULL, Available CHAR(1) NOT NULL DEFAULT '0', ReaderName CHAR(8) NOT NULL, ReaderSex CHAR(2) NOT NULL, ReaderSdept CHAR(10) NOT NULL, ReaderClass CHAR(10) NOT NULL, ReaderEmail CHAR(20) NOT NULL, ReaderMon SMALLINT NOT NULL DEFAULT 0, FOREIGN KEY (ReaderType) REFERENCES ReaderType(ReaderType) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;读者表的外键“读者账号”按文档应该同时参考账号信息表的主键,所以还需要加一行FOREIGN KEY (ReaderAccount) REFERENCES UserMessage(UserName)。但要注意,ReaderAccount和ReaderType同时都是外键时,建表顺序必须是UserMessage和ReaderType先建好。Available字段默认“0”,含义是“可用”,这个默认值设对之后,新读者注册进来不需要额外置位,否则会漏掉初始化,导致新用户借不了书。
第三组是书籍相关表,这里要做一次“四连建”:书籍类型、某类书籍、阅览室、书架、书籍。
CREATE TABLE BookType ( BookType CHAR(20) PRIMARY KEY, TypeName TEXT NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE BookInfo ( ISBN CHAR(20) PRIMARY KEY, BookName TEXT NOT NULL, Author CHAR(20) NOT NULL, BookKey CHAR(10) NOT NULL, BookEdit TEXT NOT NULL, PageNum SMALLINT NOT NULL, Price DECIMAL(10,2) NOT NULL, BookType CHAR(20) NOT NULL, PublishTime DATETIME NOT NULL, BookNum INT NOT NULL DEFAULT 0, BookIN INT NOT NULL DEFAULT 0, FOREIGN KEY (BookType) REFERENCES BookType(BookType) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE Room ( RoomNum CHAR(20) PRIMARY KEY, RoomName TEXT NOT NULL, RoomLoca TEXT NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE Shelf ( ShelfNum CHAR(20) PRIMARY KEY, RoomNum CHAR(20) NOT NULL, FOREIGN KEY (RoomNum) REFERENCES Room(RoomNum) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE Book ( BookNum CHAR(20) PRIMARY KEY, ISBN CHAR(20) NOT NULL, ShelfNum CHAR(20) NOT NULL, BookState CHAR(6) NOT NULL DEFAULT '在馆', BookDamage TINYINT NOT NULL DEFAULT 0, FOREIGN KEY (ISBN) REFERENCES BookInfo(ISBN), FOREIGN KEY (ShelfNum) REFERENCES Shelf(ShelfNum) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;对比文档,我建书架表时去掉了“条形码”字段。原因是文档的书架表把条形码设成外键,会形成“书架↔书籍”互相引用,先建谁都报错。实际工程里,书架的物理位置应该挂在书籍表上,也就是Book.ShelfNum,书架表只负责维护“哪个阅览室有哪些书架”,否则一个书架只能放一本书,完全不符合真实场景。后面第5章我会把这个坑单独拿出来讲。
第四组是借阅和归还表,主键和外键要同时考虑,借阅表先建,归还表后建。
CREATE TABLE Borrow ( ReaderAccount CHAR(15) NOT NULL, BookNum CHAR(20) NOT NULL, BorTime DATETIME NOT NULL, BorCount CHAR(1) NOT NULL DEFAULT '0', PRIMARY KEY (ReaderAccount, BookNum, BorTime), FOREIGN KEY (ReaderAccount) REFERENCES Reader(ReaderAccount), FOREIGN KEY (BookNum) REFERENCES Book(BookNum) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE ReturnBook ( ReaderAccount CHAR(15) NOT NULL, BookNum CHAR(20) NOT NULL, BorTime DATETIME NOT NULL, ReturnTime DATETIME NOT NULL, BorCount CHAR(1) NOT NULL DEFAULT '0', PRIMARY KEY (ReaderAccount, BookNum, BorTime), FOREIGN KEY (ReaderAccount) REFERENCES Reader(ReaderAccount), FOREIGN KEY (BookNum) REFERENCES Book(BookNum) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;注意这里我把Borrow的主键设成了(ReaderAccount, BookNum, BorTime),而文档只写了ReaderAccount加BookNum。原因是同一读者在同一时间只能借同一本书一次,但不同时间可以多次借同一本书,如果不把借出日期加进主键,第二次借这本书时会直接主键冲突。改造后的主键既保证唯一性,又不会影响外键约束。BorCount用CHAR(1)存续借次数,取值范围0到1,这是文档规定的“最大续借次数2”含糊了,实际上续借次数记录的是已续借的次数,一般0或1就够。
4. 数据流图拆解:办证、借书、还书、缴费四条流程的存储变化
数据流图是这份PDF里最容易被跳过的一部分,因为比起E-R图,它看起来更像“业务流程示意”。但考试和答辩都喜欢从数据流图切入问问题。数据流图的重点不是“谁做了什么动作”,而是“数据从哪来、经过什么处理、存到哪个存储、流向哪个外部实体”。把这一点想清楚,后面写后端接口都会顺很多。
4.1 顶层图到0层图:外部实体与处理过程怎么划分
顶层数据流图只有两个外部实体:读者和管理员,再加上一个图书管理系统数据加工。读者向系统发出“办证”“借书”“还书”“缴费”请求,管理员在系统里做验证操作。外部实体之间不能直接传数据流,这是数据流图的铁律。到了0层图,系统加工会被拆成四个核心处理:办证处理、借书处理、还书处理、缴费处理。每个处理都要有输入数据流和输出数据流,并且连到数据存储。
文档里出现的“读者档案”“图书档案”“已借出档案”“还书档案”就是四个关键数据存储,对应到关系模式里分别是:读者表,书籍表加某类书籍表,读者借阅表,读者归还表。很多同学画数据流图时只画箭头和圆圈,不画存储,评卷老师一眼就看出没有理解“数据流”的含义。数据流图里的箭头代表数据包在流动,而不是控制信号。比如“验证信息失败”是从“验证信息”处理流回“读者检查办证”的数据流,表示验证不通过的信息反馈。
4.2 借书流程:验证读者资格、更新书籍状态、写入借阅表
借书流程在文档里体现为“读者验证资格→图书档案→已验证信息”这条链路。拆开来看,实际借书涉及四次数据变化。第一步,根据读者账号查读者表,检查是否可用、读者类型是否有借书名额;第二步,根据条形码查书籍表,看书籍状态是不是“在馆”;第三步,把书籍表的状态改成“不在馆”;第四步,向读者借阅表插入一条借出记录,同时扣减某类书籍表里的在馆数量。这四步必须作为一个事务来处理,否则会出现读者借书成功但书状态没改的脏数据。
我在做课程设计的时候,喜欢把这四步画成一张“存储变化表”:数据存储名称、操作类型、涉及字段。这样答案不仅表达了数据流,还能看出你是否理解表结构。比如对“图书档案”存储,借书时执行的是“读+改”,读的是BookState,改的也是BookState;对“某类书籍”存储,执行的是“读+改”,改的是BookIN字段;对“已借出档案”存储,执行的只有“插入”,不修改。写清楚这些,论文和答辩都很有说服力。
4.3 还书与欠费流程:归还表为什么必须保留“借出日期”
还书流程比借书多一个“缴纳欠费”分支。读者把书给管理员后,系统先在读者借阅表里找到借出记录,读取借出日期和续借次数;然后判断是否超过读者类型表的借书最大时间;如果超期,先计算欠费金额,再走缴纳欠费处理,更新读者表余额。最后把书的状态改回“在馆”,更新某类书籍表在馆数量加1,并向读者归还表插入一条归还记录。
归还表必须保留“借出日期”而不是只存归还日期,原因有两个。第一,判断超期需要借出日期,若归还表不复制这个字段,还书时还得回借阅表反查,一旦借阅表记录被清理,历史超期信息就断了。第二,归还表是审计轨迹,借书日期和实际归还日期都有,才能计算某本书的借阅周期,也才能应对“这本书是不是逾期归还”的争议。文档里归还表完整保留了借出日期和续借次数,这个设计不是冗余,是有意为之。考试问“归还表为什么要有借出日期”时,答案就是这里。
4.4 数据流图落到后端代码:PHP图书管理系统里的借书处理示例
数据流图最终要变成代码。如果你用PHP写后端,借书接口的核心逻辑和前面说的四步存储变化完全对应。我用PDO写了一个可运行的借书函数,能体现事务、行锁和数据流映射。
function borrowBook(PDO $pdo, string $readerAccount, string $bookNum): void { $pdo->beginTransaction(); try { // 1. 查读者是否可用 $stmt = $pdo->prepare( "SELECT ReaderAccount, Available, ReaderType FROM Reader WHERE ReaderAccount = ?" ); $stmt->execute([$readerAccount]); $reader = $stmt->fetch(PDO::FETCH_ASSOC); if (!$reader || $reader['Available'] !== '0') { throw new RuntimeException('读者不存在或不可用'); } // 2. 锁定书籍记录,防止并发借同一本 $stmt2 = $pdo->prepare( "SELECT BookNum, BookState, ISBN FROM Book WHERE BookNum = ? FOR UPDATE" ); $stmt2->execute([$bookNum]); $book = $stmt2->fetch(PDO::FETCH_ASSOC); if (!$book || $book['BookState'] !== '在馆') { throw new RuntimeException('书籍不在馆'); } // 3. 更新书籍状态 $pdo->prepare( "UPDATE Book SET BookState = '不在馆' WHERE BookNum = ?" )->execute([$bookNum]); // 4. 插入借阅记录 $pdo->prepare( "INSERT INTO Borrow (ReaderAccount, BookNum, BorTime, BorCount) VALUES (?, ?, NOW(), '0')" )->execute([$readerAccount, $bookNum]); // 5. 某类书籍的在馆数量减一 $pdo->prepare( "UPDATE BookInfo SET BookIN = BookIN - 1 WHERE ISBN = ?" )->execute([$book['ISBN']]); $pdo->commit(); } catch (Throwable $e) { $pdo->rollBack(); throw $e; } }这里的关键点是FOR UPDATE行锁。在并发场景下,两个管理员同时借同一本书,如果不锁行,两次查询都会看到“在馆”,然后都执行插入,书就被借出去两次。FOR UPDATE会锁住Book表里对应的那一行,第二个事务必须等第一个提交后才能执行。PDO prepare和execute绑定参数是为了避免SQL注入,课程设计里很多同学直接拼接字符串,虽然能跑,但现在连期末验收都会查这一项。事务包住五步操作后,任何一步失败都会回滚,保证数据流图里“图书档案更新”和“已借出档案插入”要么都成功,要么都不生效。
5. 避坑指南:从这份PDF到能跑的系统,五个高频翻车点
PDF本身是完整的设计蓝本,但照着它抄作业的时候,至少有五个地方几乎人人都会踩。每条我都按“现象、原因、解决”来讲,这也是我在帮人改课程设计时最常处理的几类问题。
5.1 翻车点一:分类与实例混为一谈,丢掉了“某类书籍”这张表
现象:建表时嫌表面太多,只做了一张“书籍”表,把书名、作者、出版社、库存量、在馆数量全都塞进去,条形码作为主键,结果同一本书有5个副本就存了5遍完全相同的书名和作者。统计“馆藏总量”时压根不知道应该算哪一行,只能COUNT(DISTINCT书名),数据一多就乱套。
原因:没有分清某类书籍和书籍两个实体。某类书籍是书目层的抽象概念,一条记录代表一种书;书籍是馆藏层的物理实体,一条记录代表一册可借的书。前者存静态书目信息和汇总数量,后者存动态状态和物理位置。
解决:严格按文档拆两张表。某类书籍表以ISBN为主键,存书名、作者、主题、出版社、库存量、在馆数量;书籍表以条形码为主键,存ISBN、书架编号、书籍状态、损坏程度。任何涉及“这本书还有几本可借”的统计都去某类书籍表查,涉及“这一册书在不在馆”的去书籍表查。
5.2 翻车点二:多对多关系只画一条线,忘记拆中间表
现象:E-R图里读者和书籍之间只画一条带“借阅”标签的连线,没有拆借阅表。到建表时发现读者表不知道该放哪个字段才能记录“借了哪几本书”,于是有人把BookNum拼成字符串存在读者表里,还有人建一张“借书表”只放读者账号和书名的TEXT字段,完全没法做日期查询。
原因:把多对多联系当成了简单属性。借阅联系本身有属性:借出日期、续借次数、实际归还日期。这些属性不能挂在读者或书籍任何一侧,必须挂在联系对应的中间表上。
解决:遇到“读者和书籍通过借阅产生联系”,先判定基数:一个读者可以借多本书,一本书可以被多个读者借过,所以是多对多。多对多必须拆成两张中间表,即文档里的读者借阅表和读者归还表。借阅表存当前在借,归还表存历史记录,主键建议用读者账号加条形码再加借出日期,避免同一读者重复借同一本书时主键冲突。
5.3 翻车点三:外键参考错了表,报表联查结果全是空
现象:查询借书列表时LEFT JOIN Reader和Book,发现结果全是NULL,或者结果重复增长。有些同学贴出来的报错是“Cannot add foreign key constraint”,后面跟了一串乱码。检查表结构,发现书籍表的ISBN外键指向了读者表的主键,或者书架表外键指向了某类书籍表。
原因:照抄PDF关系模式时没有核对字段的归属。文档里有些表名有笔误,比如某类书籍的关系模式写成ReaderType,如果直接把这个错误的名字当表名,外键自然建不上。另外,书架表“条形码”外键指向书籍表,书籍表“书架编号”外键又指向书架表,循环依赖也会导致建表失败。
解决:建表前先画一张外键依赖图。依赖是单向的:读者外键指向账号信息和读者类型,书籍外键指向某类书籍和书架,某类书籍外键指向书籍类型,书架外键指向阅览室,借阅表外键指向读者和书籍,归还表外键指向读者和书籍。按照依赖顺序从被依赖的表开始创建。遇到循环依赖时,按我第3章的处理方式,去掉书架表的条形码外键,把书架编号作为书籍表的外键即可,不要让书架和书籍互相引用。
5.4 翻车点四:数据流图把数据存储漏掉,画成了“流程图”
现象:交上去的数据流图看起来像泳道图,只有“读者”“管理员”两个框,中间一串箭头连到“验证”、“借出”、“归还”,没有任何“数据存储”的图标。答辩论证时老师问“借书后书的状态存在哪里”,答不上来。
原因:混淆了数据流图和流程图。流程图表达控制流,箭头是“下一步做什么”;数据流图表达数据流,箭头是“有哪些数据在移动”,并且必须经过数据存储。读者把书给管理员不是数据流,“读者的借书请求”才是数据流。
解决:画数据流图时,先列出外部实体、处理、数据存储三个维度。外部实体只放“读者”和“管理员”;处理至少放“办证处理”“借书处理”“还书处理”“缴费处理”;数据存储放“读者档案”“图书档案”“已借出档案”“还书档案”。每个处理必须至少有一条输入数据流和一条输出数据流连接到存储或外部实体,漏掉任何一个箭头都要被追问。箭头命名用名词短语,比如“读者档案信息”“已验证信息”,不要用“验证一下”“返回结果”这种动词短语。
5.5 翻车点五:默认值和取值范围不写,测试阶段就出现脏数据
现象:图书管理系统联调时,发现“在馆数量”出现负数,“书籍状态”里一半是“在馆”一半是“正常”“可借”“已借出”各种写法,统计时COUNT结果对不上。读者类型表里借书上限填了“无限”,后端解析时直接报类型错误。
原因:建表时没有把PDF字段定义表里的取值范围和默认值变成SQL约束。开发同学直接从接口接收前端传值,前端下拉框如果漏配选项,就会把脏数据写进数据库。没有默认值时,“是否可用”字段可能是空的,查出来是NULL而不是“0”。
解决:把文档里的默认值全部落到建表语句。书籍状态“在馆”设为默认值,可用状态“0”设为默认值,损坏程度和借阅上限用TINYINT并加CHECK约束。MySQL里可以这样写:CHECK(BookDamage BETWEEN 0 AND 4)、CHECK(MaxBorNum BETWEEN 0 AND 10)。注意不要指望MySQL老版本自动执行严格Check,开发阶段还要在应用层做参数校验,双重保险。考试阶段不要求写应用层,只要建表语句里带上DEFAULT和CHECK即可。
6. 用这份PDF组织课程设计交付:三件套自查与答辩话术
6.1 三件套自查清单
把E-R图、关系模式、数据流图对照检查,半小时内能查完。表单向下面这样用:
| 检查项 | 怎么查 | 对应文档位置 |
|---|---|---|
| 实体是否齐全 | 数一下E-R图里的实体矩形,是否覆盖读者、管理员、书籍、某类书籍、书架、阅览室、读者类型、书籍类型 | 文档E-R图段落 |
| 联系是否带基数 | 每个菱形两侧是否标了1或N,没有标的直接扣分 | 联系描述 |
| 外键是否闭合 | 选一个联系,看它对应的外键字段能不能连回主键表 | 关系模式清单 |
| 数据流是否指向存储 | 数据流图里每个处理是否至少连到一个数据存储矩形 | 数据流图段落 |
| 默认值是否落库 | 检查建表SQL里是否出现DEFAULT和CHECK | 字段定义表 |
这套自查表是我现在做课程设计辅导时每次必用的。它不检查功能,只检查设计完整性。设计完整性不过关,代码写得再好也难拿高分。
6.2 答辩现场怎么把这套设计讲清楚
答辩讲十分钟,我建议这样分配:前两分钟讲E-R图,重点讲某类书籍和书籍为什么拆开,以及读者和书籍之间是M:N关系。中间四分钟讲关系模式和建表SQL,现场投影出字段定义表,强调“是否可用默认0”“损坏程度0到4”这些细节,说明你是认真读过文档的。最后四分钟讲数据流图,把办证、借书、还书、缴费四条链路各讲一句话,配合借阅表和归还表的区别说清楚。
有个小技巧:答辩时把E-R图里的联系数量数出来,并逐一说明它对应到哪张表的外键。这比背诵概念更能体现理解程度。比如你指着“拥有”联系说“读者表里有ReaderType外键,指向读者类型表”,老师就知道你是真做过设计,而不是背的模板。
我从那以后每次做课程设计,都强制自己先走一遍“实体清单→联系判定→关系模式→字段定义→建表SQL”这五步。这份PDF的价值不在于让你直接复制答案,而在于它示范了一个标准的信息系统设计过程:先有图,后有模式,最后有字段。你把里面每一个字段默认值都问一次为什么,就已经超过大部分交作业的人。希望帮到你。
本文还有配套的精品资源,点击获取