简介《数据库原理第四版》课件面向数据库初学者、高校学生及需要系统梳理理论体系的从业人员围绕数据库系统开发全流程与核心数据模型组织内容。压缩包共1个PPT文件约289KB以幻灯片形式覆盖第一章至第十章便于课堂讲授、章节复习和知识点串联。课件从需求分析、概念结构设计、逻辑结构设计、物理结构设计到数据库实施逐步展开并讲解数据字典、数据独立性、概念模型、层次模型、网状模型和关系模型其中网状模型部分细分为数据结构、数据操纵、完整性约束、存储结构及优缺点配有学生、宿舍、教师、教研室和家庭关系等图示案例帮助理解一对多联系与复杂关系表达。目前已有168人学习适合用于课程预习、期末复习及数据库基础概念查漏补缺。1. 数据模型的三层抽象为什么“画个图”能决定数据库的生死翻开《数据库原理第四版课件》绪论里最容易被跳过的是层次模型和网状模型那几页。很多人觉得它们已是历史遗迹学关系模型和 SQL 就够了。但一个反直觉的结论是今天图数据库里“一个节点可以有多个父节点”的设计几乎就是网状模型换了种形式。数据库原理真正有价值的地方不是某一种模型的语法而是数据模型如何在“描述现实世界”和“能被机器高效存取”之间做取舍。这一章先把结论摆出来模型演化不是淘汰赛而是一条取舍链每一环都在回答同一个问题——把复杂性放在数据库里还是放在写程序的人身上。2. 网状模型一个结点为什么可以有两个双亲2.1 从“树”到“图”双亲数量限制是怎么被打破的层次模型的结构是一棵树有且仅有一个结点没有双亲这个结点叫根其余每个结点有且只有一个双亲。这个限制在描述组织架构、目录树时很自然但一碰到“一个学生属于一个系又住在一栋宿舍还选了多门课”就立刻不够用了。网状模型的定义正是冲着这条限制来的它要求满足两个条件的基本层次联系的集合允许一个以上的结点无双亲一个结点可以有多于一个双亲。前一条打破了“唯一根”后一条打破了“单亲”。把这两个条件翻译成结构语言层次模型里每个结点最多一条向上的边网状模型里向上边可以有多条也可以一条都没有。于是学生结点可以同时连向“系”和“宿舍”两个双亲教师结点可以同时连向“系”和“教研室”。这不是为了炫技而是现实世界里实体本来就处在多张关系网里单亲树装不下。注意网状模型仍然以“记录类型”为结点、以“一对多父子联系”为边它并不是任意图。任意图那种多对多直连网状模型并不直接支持这一点后面会专门拆。层次模型其实是网状模型的一个特例把网状模型里所有“多双亲”和“无双亲”的结点约束成“只有一个根、其余单亲”得到的就是层次模型。反过来不成立这个方向经常被记反后面避坑章节会展开。2.2 记录类型、字段与父子连线网状模型的表示法网状模型的表示方法和层次模型一致只是约束松了。实体型用记录类型描述每个结点表示一个记录类型属性用字段描述每个记录类型可以包含若干个字段联系用结点之间的连线表示记录类型之间的一对多父子联系。注意这里的措辞是“记录类型之间”不是“记录值之间”——模式层面画的是类型图运行时的具体记录值再按这张图挂到各自的系下面。课件里给了一组典型例子学生、宿舍、系、教师、教研室。学生到宿舍是多对一多个学生住一间宿舍学生到系是多对一教师到系是多对一教师到教研室是多对一。画成网状图系结点同时是学生和教师的双亲宿舍是学生的另一个双亲。这就是“一个结点多个双亲”的直观来源。每个记录类型还要定义一个排序字段也叫码字段。这个字段决定同一双亲下子女记录的排列顺序同时也是记录在系内的标识依据。它和关系模型里的主键不是一回事关系模型的主键是全局唯一标识网状模型的码字段更多是“在某个系内按什么顺序排、按什么找”。课件里那句话很关键——任何记录值只有按其路径查看时才能显出它的全部意义。也就是说一条学生记录脱离了“系-学生”这条路径你就不知道它属于哪个系意义是路径赋予的不是记录自己带的。用一张表把几个概念对齐概念网状模型里的载体作用实体型记录类型结点描述一类实体属性字段描述实体特征联系结点间连线系表示一对多父子关系排序依据码字段系内子女记录的排序与定位记录意义存取路径离开路径无法完整解释记录这张表也解释了网状模型为什么“结构复杂”模式图本身就是程序的一部分路径变了程序里的导航逻辑就得跟着变。2.3 多对多联系为什么要先拆成两个一对多网状模型只能直接处理一对多的实体联系。课件里明确写了多对多联系要用网状模型间接表示方法是将多对多联系直接分解成一对多联系。这句话听起来像绕口令落到具体设计上就是引入一个“连接记录类型”让它同时成为两个实体的子女。以学生选课为例学生和课程之间是多对多一个学生选多门课一门课被多个学生选。网状模型不画学生直连课程而是插入一个“选课”记录类型学生到选课是一对多课程到选课也是一对多。这样任意一条选课记录都有两个双亲路径清晰操纵也能落地。# 网状模型不直接支持多对多连线必须先拆成两个一对多 # 以学生选课为例引入“选课”作为连接记录类型 # 分解前的直觉结构网状模型画不出这种直连 naive { 学生: {学号: S01, 课程: [C01, C02]}, 课程: {课程号: C01, 学生: [S01, S02]}, } # 分解后选课记录同时是学生和课程的子女 student {学号: S01, 姓名: A同学} course {课程号: C01, 课程名: 数据库原理} enrollment { 选课号: E01, # 排序字段也是这条连接记录在系内的定位依据 学号: S01, # 指向学生双亲的引用 课程号: C01, # 指向课程双亲的引用 成绩: 92, }这段代码的重点不在数据结构本身而在“选课”这个记录类型的双重身份。参数上选课号是排序字段决定同一学生下选课记录的排列顺序学号和课程号是两个双亲引用分别挂到“学生-选课”系和“课程-选课”系下面。查询“某学生选了哪些课”先从学生记录沿“学生-选课”系找到全部选课记录再顺着每条选课记录里的课程号定位课程查询“某门课有哪些学生”方向反过来。两条路径都能走但走法不同这就是导航式访问的代价查询效率取决于你从哪头进。课件里还给了家庭关系的例子父母与子女、树种植与砍伐、养育与赡养。父母到子女是一对多但“养育”和“赡养”是两种不同性质的父子联系网状模型允许两个结点之间有多种联系这叫复合联系。层次模型做不到这一点因为它一条边只能表达一种父子语义。2.4 层次模型是网状模型的特例这句话反过来不成立课件在网状模型与层次模型的区别里列了五条网状允许多个结点没有双亲允许结点有多个双亲允许两个结点之间有多种联系可以更直接地描述现实世界层次模型实际上是网状模型的一个特例。这五条里前三条是结构差异后两条是结论。为什么“更直接”值得单独说因为层次模型在描述非树形关系时要么冗余存储要么引入虚拟结点要么把多对多硬拆成两条独立路径。网状模型把这些约束放开后现实世界里的“学生既属于系又住宿舍”可以原样画出来不需要为了迎合模型去扭曲业务。代价是模式图变复杂用户必须理解路径才能取数。这里有个容易被忽略的推论既然层次模型是网状模型的特例那么网状模型的所有操纵原语在层次模型里都能用只是层次模型额外保证“单亲、单根”。反过来说学网状模型不是学一个过时玩意儿而是在学一个更一般的结构关系模型后来用“表外键”把路径藏起来了但底层的父子语义并没有消失。3. 数据操纵与完整性约束DBTG 为什么给删除留了后悔药3.1 四种操纵的本质改的是链接指针不是行网状模型的数据操纵包括查询、插入、删除、更新四类。听起来和关系模型一样但动作对象完全不同。关系模型改的是表里的行网状模型改的是记录之间的链接指针。插入一条子女记录不只是往存储里写一条数据还要把它挂到对应的系上删除一条双亲记录不只是删一行还要决定它下面的子女记录怎么办。课件的完整性约束部分提到DBTG 规范对数据操纵加了一些限制提供了一定的完整性约束码、双亲结点与子女结点之间是一对多联系、属籍类别。这三条里码和一对多是结构约束属籍类别才是操纵约束的核心。理解网状模型的数据操纵关键是理解“一条记录在系里的进出规则”而不是背四条操作名。3.2 加入类别与移出类别DBTG 给记录和系定的“户口规则”属籍类别是 DBTG 里最容易被略过、但设计动机最强的一部分。它分两个维度加入类别和移出类别。加入类别管的是“子女记录怎么进系”有两种自动的和手工的。自动加入的意思是子女记录一插入就自动挂到系上不需要额外命令手工加入的意思是记录先作为游离记录存在应用显式调用加入操作才挂链。手工加入不是多此一举它给“先录入、后归属”的场景留了口子比如新生数据先入库、分院系之后再挂到“系-学生”系上。移出类别管的是“子女记录怎么出系”有三种固定的、必须的、随意的。固定的是说这条子女记录一旦入系就不能移出必须的是说移出之前必须先连到另一个双亲不能悬空随意的是说可以自由移出移出后成为游离记录。这三种规则直接决定了删除双亲时子女的命运。类别取值语义典型场景加入类别自动插入即挂链强归属关系如选课记录必须属于某学生加入类别手工显式挂链先录入后归属如新生先入库再分系移出类别固定不许移出关键从属记录不允许脱离双亲移出类别必须移出前必须改挂不允许悬空的强约束关系移出类别随意可自由移出弱归属关系如临时借调这套规则的本质是把“参照完整性”从一句口号变成了可执行的挂链、解链策略。关系模型用外键约束和级联动作表达类似语义网状模型则把它下沉到系一级由 DBMS 在操纵时强制执行。3.3 允许悬空双亲值批处理场景下的后悔药课件里有一条很反直觉的完整性约束允许插入尚未确定双亲结点值的子女结点值允许只删除双亲结点值。按关系模型的直觉这两条几乎是“破坏完整性”的同义词——外键怎么能为空双亲怎么能删了子女还在但放在网状模型的历史场景里这两条是刻意留出的回退空间。早期数据库大量用于批处理录入数据往往分阶段到位先把子女记录批量灌进去双亲记录稍后补或者先删双亲做逻辑下线子女记录留给后续迁移程序处理。如果 DBMS 强制度绝悬空这些批处理流程就得在应用层拆成更细的步骤反而更容易出错。允许悬空等于把“暂时不一致”的责任交给应用DBMS 只保证结构上的链接合法。class SetLink: 模拟一个 DBTG 风格的系一个双亲记录 若干子女记录 def __init__(self, owner, member_type, insert_mode, remove_mode): self.owner owner self.members [] self.member_type member_type self.insert_mode insert_mode # automatic 或 manual self.remove_mode remove_mode # fixed / mandatory / optional def insert_member(self, record): # 自动加入插入子女记录时直接挂到系上 if self.insert_mode automatic: self.members.append(record) record[owner] self.owner # 手工加入先作为游离记录存在等应用显式 attach return record def attach(self, record): # 手工加入的挂链入口 self.members.append(record) record[owner] self.owner def remove_owner(self): # 固定双亲记录不允许删除 if self.remove_mode fixed: raise RuntimeError(双亲记录被固定拒绝删除) # 必须子女必须先改挂到另一个双亲否则拒绝 if self.remove_mode mandatory: raise RuntimeError(子女记录必须先改挂到另一个双亲) # 随意子女可以脱离留下悬空的双亲引用 for member in self.members: member[owner] None self.members.clear()这段代码把属籍类别的三个取值落到了具体分支上。insert_mode控制插入时是否自动挂链remove_mode控制删双亲时子女的解链行为。remove_owner里“随意”分支把owner置空而不是删子女正是课件里“允许只删除双亲结点值”的实现方式双亲没了子女还在引用变成悬空后续由应用决定是补挂还是清理。参数上owner是双亲记录的引用members是子女记录列表member_type用来区分这个系挂的是哪类子女。提示悬空双亲值是“允许”不是“鼓励”。应用层如果没写补偿逻辑查出来的子女记录会带一个空引用导航到一半断链这类问题在批处理系统里很常见。3.4 码字段与一对多约束完整性从哪来网状模型的完整性约束有三根支柱。第一根是码每个记录类型定义排序字段保证记录在系内可定位、可排序。第二根是双亲与子女之间的一对多联系这是结构层面的硬约束一条子女记录只能有一个双亲在同一个系里不存在“一对多变成多对多”的模糊地带。第三根是属籍类别管的是记录进出系的规则。这三根支柱合起来构成了网状模型的完整性基础。它不像关系模型那样有统一的声明式约束语言而是把约束分散在模式定义和操纵语义里。好处是执行路径短DBMS 在挂链、解链时顺手就检查了坏处是约束和存取路径绑得太死改一条联系往往要动一片程序。这也是后面设计取舍章节要展开的核心矛盾。4. 存储结构四种链接方式到底在省什么4.1 单向链接只存一个方向遍历便宜反向贵网状模型存储结构的关键是实现记录之间的联系。课件列了四种常用方法单向链接、双向链接、环状链接、向首链接。这四种不是四种数据库产品而是同一套父子关系在指针层面的四种摆法。单向链接最省指针每个子女记录只存一个指向“下一个兄弟”的指针双亲记录存一个指向“第一个子女”的指针。遍历时从双亲出发沿第一个子女一路往后走走到空就结束。插入一条新子女记录改两个指针即可删除一条子女记录要找到它的前驱再改指针所以单向链接的删除代价取决于能不能快速定位前驱。class Record: def __init__(self, key): self.key key self.first_child None # 双亲记录指向第一个子女 self.next_sibling None # 子女记录指向下一个兄弟 self.prev_sibling None # 双向链接时才启用 self.owner None # 向首链接指回双亲 def walk_children(owner): 单向链接的正向遍历从双亲走到最后一个子女 cursor owner.first_child result [] while cursor is not None: result.append(cursor.key) cursor cursor.next_sibling return resultwalk_children只依赖first_child和next_sibling两个指针正向遍历是 O(n)但拿不到反向信息给定一个子女记录你不知道它的双亲是谁也不知道它的前一个兄弟是谁。反向查找要么从头再扫一遍要么在记录里额外存指针。这就是单向链接“省指针、费反向”的取舍。4.2 双向链接与环状链接用指针换回退能力双向链接在单向的基础上给每个子女记录加一个prev_sibling指向前一个兄弟。代价是每条子女记录多一个指针插入和删除时要同时维护前后两个方向。收益是反向遍历和前驱定位变成 O(1)删除一条子女记录不用再从头找前驱。环状链接把“最后一个子女的 next 指向第一个子女”或者“指向双亲”形成一个环。这样从任意一个子女出发都能转一圈回到起点适合需要循环处理兄弟集合的场景比如轮询分配、循环扫描。环状链接的坑在于边界判断遍历终止条件不再是“指针为空”而是“回到起点”代码里多一个哨兵判断写错就容易死循环。链接方式正向遍历反向定位插入代价删除代价额外指针单向链接O(n)需重扫低需找前驱1 个双向链接O(n)O(1)中O(1)2 个环状链接O(n)视实现中中12 个向首链接O(n)O(1) 到双亲低低1 个这张表不是让你背而是说明一个事实网状模型的“存取效率高”不是免费的它是靠模式设计阶段就把路径和指针摆法定死换来的。你选了单向链接就接受了反向查找要重扫你选了双向链接就接受了每条记录多一个指针、每次插入多维护一次。4.3 向首链接为什么子女要指回双亲向首链接是给子女记录加一个指向双亲首记录的指针。它解决的是“从子女反查双亲”的问题给定一条选课记录不用回到“学生-选课”系的入口直接顺着owner指针就能找到学生记录。课件里把向首链接和单向、双向、环状并列说明它是按需选配的不是所有系都默认加。向首链接的代价很直接每条子女记录多一个指针插入时要写删除时要清。收益是导航路径可以双向走应用不用记住“我是从哪个双亲下来的”。在多层嵌套的网状结构里这个收益会被放大——从叶子记录一路向首回到根比从根重新导航下来要快得多。注意向首链接只解决“回到双亲”不解决“回到兄弟”。如果需要兄弟间自由移动还是得叠双向或环状链接。四种方式可以组合不是四选一。4.4 四种链接的代价对比与选型把这四种方式放在一起看会发现它们优化的是不同方向的访问模式。单向链接优化“从双亲到子女”的正向扫描双向链接优化“前后移动”环状链接优化“循环遍历”向首链接优化“从子女回双亲”。选型不是选最先进的而是选最匹配查询模式的。课件里提到的典型网状数据库系统是上世纪七十年代依据 DBTG 规范推出的一批产品它们奠定了数据库系统的基本概念、方法和技术。这些系统在存储层大量使用上述链接方式把存取路径固化在模式里。今天看这种做法的血泪教训是路径一旦写死业务查询一变改造成本极高。但它的历史贡献不能抹掉——关系模型后来能普及恰恰是因为它把“路径选择”从程序员手里拿走交给了查询优化器。5. 避坑与排查网状模型学习中最容易踩空的地方5.1 把“能画出多对多”当成“能直接处理多对多”现象模式图画得很漂亮学生和课程之间直接连了一条线写操纵时却发现没有对应的系可以挂链查不出“某学生选了哪些课”。原因网状模型直接支持的是一对多父子联系多对多必须通过连接记录类型分解。模式图上画一条多对多直连只是概念模型的表达不是网状模型的实现结构。解决画完概念图后强制做一步分解——每一条多对多联系都问一句“中间记录类型是谁”。选课、订单明细、借阅记录这些都是典型的连接记录类型。分解完再检查每条连接记录是不是同时挂了两个双亲。5.2 排序字段与码字段混为一谈现象按码字段查记录查出来的顺序和预期不一致或者以为码字段全局唯一结果不同系下出现了重复值。原因网状模型的码字段是记录类型内定义的排序字段它的职责是“在系内按什么顺序排、按什么定位”不是关系模型意义上的全局主键。同一个码值在不同双亲下可能重复出现这在网状模型里是允许的。解决把“系内排序”和“全局唯一标识”分开设计。如果业务需要全局唯一额外加一个唯一标识字段不要指望码字段包打天下。查询时先定位系再按码字段在系内找不要跳过系直接全局扫。5.3 把层次模型说成网状模型的扩展现象复习时把“层次模型是网状模型的特例”记反了答题或设计时把网状模型当成层次模型加几条边。原因两个模型的名字和结构太像层次模型出现更早容易形成“先有层次、后有网状所以网状是扩展”的直觉。课件里的原话是反过来的层次模型实际上是网状模型的一个特例。解决用约束方向记——网状模型放开“单根、单亲”得到层次模型所以网状更一般。判断时问一句这个结构允许一个结点多个双亲吗允许就是网状不允许就是层次。5.4 多对多分解后没设移出类别删除连锁翻车现象删除一条学生记录结果把该学生的选课记录全带走了甚至连课程记录也受影响或者删除双亲后子女记录变成悬空后续查询断链。原因分解出连接记录类型后没有为“学生-选课”“课程-选课”这两个系设置合适的移出类别。默认行为可能是级联删除也可能是允许悬空具体取决于系统实现但设计时没显式声明就等于把命运交给默认值。解决每条连接记录的两个系都显式设置移出类别。选课记录对学生的归属通常是“必须”或“固定”对课程的归属也类似如果业务允许课程下线但保留历史选课就把课程侧的移出类别设为“随意”接受悬空并在应用层补课程快照。规则写在模式里不要靠口头约定。5.5 低估 DDL、DML 的复杂度改一个联系重建半张图现象原型阶段加一个联系很轻松上线后业务要调整一条父子关系改完 DDL 发现周边一片程序编译不过DML 里的导航路径全要重写。原因网状模型的 DDL 和 DML 把存取路径写进了模式路径即契约。加一条联系不只是加一个结点还要定义系、排序字段、属籍类别周边所有依赖这条路径的操纵代码都要跟着改。课件在优缺点里写得很直白结构复杂随着应用环境扩大越来越复杂DDL、DML 语言复杂用户不容易使用。解决模式设计前先做概念模型把实体和联系理清楚再决定哪些路径值得固化进网状结构。变化频繁的联系不要硬编码成系宁可留给应用层做关联查询。已经固化的路径改之前先盘点依赖别把“加一条边”当成改一行配置。6. 设计取舍效率与可用性之间的那条线6.1 导航式与声明式存取路径到底该由谁决定网状模型和关系模型最根本的分歧不是数据结构而是“存取路径由谁决定”。网状模型是导航式的程序员必须知道从哪个系进、沿哪条链走、走到哪停。查询效率高因为路径是设计阶段定好的运行时不用猜。代价是路径和程序耦合业务一变程序跟着变。关系模型是声明式的程序员只写“要什么”路径交给查询优化器。单条查询可能不如手写导航快但模式变了SQL 往往不用大改。它把复杂性从应用层挪到了数据库内核用优化器的计算换开发者的心智负担。图数据库这两年的回潮本质是在两者之间找折中它保留了“多跳遍历”的导航能力但用声明式查询语言把路径细节包了一层。写查询的人不用手工维护指针内核替你做邻接遍历。这恰好说明网状模型的思想没有死只是换了一种对开发者更友好的外壳。6.2 一条可验证的取舍判断线判断一个场景该不该用“网状式”的显式路径设计我一般看三条查询模式是否稳定、路径深度是否固定、变更频率是否足够低。三条都满足固化路径能换来实打实的效率有一条不满足就该把路径选择权交出去。判断维度适合固化路径适合交给优化器查询模式长期稳定、可枚举频繁变化、临时组合路径深度固定几跳、层次清晰深度不定、递归遍历变更频率低模式多年不动高迭代以周计团队能力熟悉导航式访问更熟悉声明式查询这张表不是标准答案它只是把“网状模型为什么赢在效率、输在可用性”拆成了可检查的项。课件在网状模型优缺点里写的那句“存取效率较高”和“用户不容易使用”其实是同一枚硬币的两面效率来自路径固化难用也来自路径固化。从那以后我每次看到“一个结点多个双亲”的需求都会先问一句这是稳定的网状结构还是只是临时画着好看的概念图。如果是前者老老实实按系、按属籍类别、按链接方式把路径设计清楚如果是后者宁可先用关系表加连接表扛着也不要把路径写死。希望帮到你。本文还有配套的精品资源点击获取