简介:本资源是《数据库原理(第四版)》配套教学课件,面向高校计算机、软件工程及信息管理等专业本科生,以及数据库初学者与从业技术人员,系统解决数据库基础理论与核心模型理解难题。课件以PPT格式呈现,共1个文件,大小289KB,内容精炼、图文并茂,涵盖从第一章绪论到第十章的完整知识脉络,重点深入讲解数据模型体系,尤其对网状模型的数据结构(多父节点、记录类型与字段定义)、数据操纵(查询/插入/删除/更新)、完整性约束(DBTG规范)、存储结构及优缺点进行详实剖析,并辅以学生-宿舍-教师、家庭关系等典型实例图示。目前已有168人学习下载,适合课堂预习复习、自学夯实基础或作为数据库设计与优化进阶学习的理论支点。
1. 网状模型:为什么一个节点能有多个“父亲”,却反而让70年代的数据库跑得比关系型更快?
你可能刚学完关系模型,正为外键、连接、范式化拍手叫好——突然课件里跳出一张图:一个叫“学生”的节点,同时连着“教研室”“宿舍”“教师”三个父节点;更离谱的是,“父母”节点底下还挂着“子女”,而“子女”又反过来连着“树”“种植”“砍伐”……这不是ER图,也不是UML,这是网状模型(Network Model)的真实结构。它不靠SQL,不靠约束传播,甚至没有“事务”概念,却在1970年代被IDMS、DMS1100等商用系统扛起银行核心账务、航空订票这类高吞吐场景。为什么?不是因为它“先进”,恰恰相反——是因为它足够原始、足够贴近硬件、足够放弃通用性换性能。它把数据组织成内存里可直接跳转的指针网络,把“一对多”关系固化为物理链接,把完整性检查压到DML语句级而非运行时引擎层。今天重看这份《数据库原理(第四版)》课件里的网状模型章节,不是为了复古,而是为了看清一个本质:所有现代数据库的优化器、索引结构、甚至NoSQL的图遍历逻辑,都藏着网状模型当年用指针和链表暴力解题的影子。搞懂它,你就拿到了理解B+树分裂、LSM-tree compaction、甚至Neo4j底层游标跳转机制的“原始密钥”。
2. 网状模型的数据结构:不是图论抽象,而是内存里可寻址的指针拓扑
网状模型常被误读为“带环的树”,但课件第1.2.5节反复强调:它是一组满足两个条件的基本层次联系的集合。这句话是钥匙——它意味着网状模型并非从零构建图结构,而是对层次模型(Hierarchical Model)做最小扩展:允许一个节点无双亲(打破根唯一性),允许一个节点有多个双亲(打破父子单向性)。这种“松绑”不是数学自由,而是为映射现实世界中天然存在的多归属关系:一个学生属于班级、也属于课题组、还属于宿舍楼;一个零件既是发动机子件,又是库存商品。课件用“学生-宿舍-教师-教研室”四节点图直观展示,但真正决定其效率的,是背后存储结构如何将这种逻辑关系翻译成CPU可直接操作的物理地址链。
2.1 记录类型与字段:静态内存布局的契约
课件明确指出:“每个结点表示一个记录类型”,“每个记录类型可包含若干个字段”。这不是面向对象的class定义,而是C语言struct级别的内存契约。以IDMS典型实现为例,一个STUDENT记录类型在磁盘上固定占128字节,前4字节存STU_ID(码字段),后12字节存指向DORMITORY记录的物理地址(即“下一页号+槽位偏移”),再后12字节存指向TEACHER记录的地址……这些地址不是逻辑ID,而是直接可被I/O子系统加载到内存并解引用的机器地址。课件P29提到的“单向链接”“双向链接”,本质就是struct里嵌入的指针字段方向:单向链接只存子节点地址(节省空间),双向链接则额外存父节点地址(支持反向遍历)。这种设计让一次磁盘读取就能拿到完整关联路径,无需像关系模型那样多次JOIN查索引页。
// IDMS风格的STUDENT记录内存布局(简化示意) typedef struct { uint32_t stu_id; // 码字段:唯一标识,也是排序依据 uint32_t dorm_ptr; // 单向链接:指向DORMITORY记录的物理地址 uint32_t teacher_ptr; // 单向链接:指向TEACHER记录的物理地址 uint32_t dept_ptr; // 单向链接:指向DEPARTMENT记录的物理地址 char name[32]; // 普通属性字段 uint16_t gpa; // 普通属性字段 // ... 其他字段 } STUDENT_RECORD;提示:这里的
dorm_ptr不是数据库主键,而是类似0x0000A1F2这样的物理地址。当DBTG系统执行FIND STUDENT WITH STU_ID = 1001后,直接从该记录的dorm_ptr字段取出地址,发起第二次I/O读取宿舍记录——整个过程在汇编层就是几条mov和call指令,没有解析SQL、没有生成执行计划、没有锁管理开销。
2.2 联系的物理化:连线即指针,遍历即地址跳转
课件强调:“联系用结点之间的连线表示”,但这“连线”绝非ER图里的虚线。在网状模型中,联系是记录类型间的硬编码指针字段。例如STUDENT记录里必须显式声明BELONGS_TO_DORMITORY联系,对应字段dorm_ptr;而DORMITORY记录里若要支持反查,则需声明HAS_STUDENTS联系,对应字段student_list_head(指向第一个学生的地址)。这种设计导致两个后果:
- 插入强耦合:向
STUDENT插入新记录时,必须同时更新DORMITORY记录的student_list_head字段(或其链表中的next_ptr),否则联系断裂; - 遍历零成本:查询“某宿舍所有学生”时,DBTG系统只需从
DORMITORY记录的student_list_head开始,按next_ptr字段一路解引用跳转,直到遇到空地址——整个过程是纯指针运算,不涉及任何哈希查找或B树遍历。
课件图示中“R1→L1→L2→R2”链条,实际对应内存中连续的四个记录实例,R1的child_ptr指向L1地址,L1的next_ptr指向L2地址,L2的parent_ptr回指R1……这种结构让“从教研室查所有教师,再查每位教师所带学生”变成三次内存跳转,而非关系模型中需要三张表JOIN、两次索引扫描、一次哈希匹配的复杂流程。
2.3 码字段:排序锚点与路径依赖的根源
课件反复提及“每个记录类型定义一个排序字段,也称为码字段”,并强调“任何记录值只有按其路径查看时,才能显出它的全部意义”。这揭示了网状模型最反直觉的设计哲学:数据没有全局唯一标识,只有路径上下文中的局部意义。STU_ID=1001这个值本身不携带语义,只有当它作为DORMITORY→STUDENT路径的终点时,才表示“某宿舍的1001号学生”;若作为DEPARTMENT→TEACHER→STUDENT路径的终点,则可能表示“某系某教师指导的1001号研究生”。这种路径依赖性源于码字段的双重角色:
- 物理排序依据:DBTG系统要求所有
STUDENT记录按stu_id升序存放在磁盘页中,便于范围查询(如FIND STUDENT WITH STU_ID > 1000 AND < 1010); - 逻辑路径锚点:
stu_id是STUDENT记录在DORMITORY子树中的排序键,但DORMITORY自身又有自己的码字段(如dorm_no),形成多级索引树。
这种设计牺牲了数据独立性——修改stu_id不仅需更新本记录,还需修正所有指向它的父节点指针(如DORMITORY.student_list中的地址),但换来的是极致的局部查询性能:给定dorm_no=301和stu_id=1001,系统可在O(log n)时间内定位到精确物理页,再O(1)解引用拿到记录,全程无JOIN、无约束检查。
3. 网状模型的数据操纵:DML即指针操作,没有“原子性”只有“程序员责任”
课件将数据操纵分为查询、插入、删除、更新四类,但其本质远非CRUD这么简单。在DBTG规范下,每条DML语句都对应一组不可分割的指针操作序列,且所有完整性约束都由程序员在调用DML前手动保证。这与现代数据库的ACID理念截然相反——网状模型的“事务”概念不存在,它的可靠性建立在开发者对数据结构的绝对掌控之上。
3.1 查询:路径导航式遍历,无优化器介入
网状模型的查询语法(如CODASYL DML)形如:
FIND FIRST STUDENT WITHIN DORMITORY WHERE DORM_NO = '301' OBTAIN NEXT STUDENT WITHIN DORMITORY这段代码不经过任何查询优化器,它被DBTG预处理器直接翻译为:
- 定位
DORMITORY记录(通过dorm_no索引); - 读取该记录的
student_list_head字段; - 解引用得到第一个
STUDENT记录地址; - 加载该记录到工作区;
- 执行
OBTAIN NEXT时,读取该STUDENT记录的next_in_dorm字段,解引用跳转。
整个过程是确定性的地址跳转,时间复杂度恒为O(1) per record,与数据总量无关。课件强调“只能直接处理一对多的实体联系”,正是因为这种遍历模式天然适配树形路径(父→子),但无法高效表达多对多(需拆解为两个一对多)。例如“学生选课”多对多关系,课件指出必须引入中间记录类型ENROLLMENT,STUDENT指向ENROLLMENT,COURSE也指向ENROLLMENT——这本质上是用两次一对多模拟多对多,增加存储冗余但保持遍历效率。
3.2 插入/删除:双链表级的外科手术
插入一个STUDENT到DORMITORY,DBTG要求执行严格序列:
CREATE STUDENT:分配新记录空间,填充stu_id等字段;CONNECT STUDENT TO DORMITORY:修改STUDENT记录的dorm_ptr字段,指向目标DORMITORY;- 修改
DORMITORY记录的student_list_head或链表中某节点的next_ptr,将新STUDENT挂入链表。
注意:步骤2和3必须成对出现,否则数据结构损坏。课件P29“双向链接”示例中,若只执行步骤2未执行步骤3,则
DORMITORY无法反查该学生;若只执行步骤3未执行步骤2,则学生记录的dorm_ptr为空,路径断裂。这种强耦合正是网状模型“高风险高回报”的体现——开发者获得极致控制权,但也承担全部一致性责任。
删除操作同理:
DISCONNECT STUDENT FROM DORMITORY // 清空STUDENT.dorm_ptr DELETE STUDENT // 物理释放记录空间但若遗漏DISCONNECT,DORMITORY.student_list中仍存指向已释放内存的野指针,下次遍历时必然崩溃。课件“完整性约束”部分提到“允许只删除双亲结点值”,指的就是DORMITORY记录可被删除,而其子STUDENT记录因dorm_ptr未清空仍存在——这在DBTG中是合法但危险的操作,需程序员自行保证业务逻辑容错。
3.3 更新:字段级原子性,无行锁概念
网状模型的MODIFY语句仅更新单个记录的字段,且不提供行级锁。例如:
MODIFY STUDENT WITH STU_ID = 1001 SET GPA = 3.8DBTG系统直接定位到该记录物理地址,用新值覆盖旧字段内存。若并发执行,后写者覆盖先写者——这就是课件所谓“无事务”的真实含义。为规避冲突,DBTG规范要求应用层实现锁协议:常用OBTAIN EXCLUSIVE语句锁定记录(本质是设置内存标志位),其他会话尝试OBTAIN时阻塞。但这种锁是应用级的,不与I/O或日志系统集成,崩溃后锁状态丢失,需人工修复。这也解释了为何课件强调“DDL、DML语言复杂”——开发者需同时编写业务逻辑、指针维护逻辑、锁管理逻辑,三者交织,稍有不慎即数据错乱。
4. 网状模型的完整性约束:DBTG的“手工护栏”,不是引擎自动守卫
课件将完整性约束列为独立小节,并特别注明“DBTG系统对数据操纵加了一些限制”,这一定位极为精准。网状模型的约束不是内建于存储引擎的强制规则,而是DBTG规范定义的一套程序员必须遵守的手工协议。它不阻止错误操作,只提供一套检查工具,能否生效全赖开发者是否调用。
4.1 属籍类别(Set Membership):联系的生命周期契约
DBTG用“属籍类别”(Set)管理记录间的隶属关系。一个SET由三部分构成:
- Owner Record Type(如
DORMITORY) - Member Record Type(如
STUDENT) - Set Name(如
DORM_STUDENTS)
课件提到“加入类别(自动的,手工的)”、“移出类别(固定的,必须的,随意的)”,这实则是定义SET的维护策略:
- 自动加入:当
STUDENT记录创建时,若指定WITHIN DORM_STUDENTS,DBTG自动填充dorm_ptr并挂入DORMITORY.student_list; - 手工加入:需显式执行
CONNECT STUDENT TO DORMITORY; - 固定移出:删除
DORMITORY时,DBTG自动断开所有STUDENT的dorm_ptr(但不清空STUDENT记录); - 必须移出:删除
STUDENT前,必须先执行DISCONNECT STUDENT FROM DORMITORY,否则报错。
这种设计将约束检查下放到DML调用点,而非运行时引擎。例如DISCONNECT语句执行时,DBTG仅检查STUDENT当前是否确实在DORM_STUDENTS集中——它不验证DORMITORY是否存在,也不检查其他联系(如STUDENT是否还属于DEPARTMENT)。约束的粒度是“联系”,而非“数据值”。
4.2 码字段约束:排序唯一性与路径锚定
课件指出“码字段”需满足唯一性,但DBTG不自动维护。例如STU_ID作为STUDENT的码字段,其唯一性由应用层保证:插入前需FIND确认不存在,否则CREATE会覆盖旧记录。更关键的是,码字段的排序性直接影响查询性能。若STUDENT记录未按stu_id物理排序,FIND WITHIN RANGE将退化为全表扫描。DBTG提供REORGANIZE命令重建排序,但需停机执行——这解释了课件强调“结构复杂,不利于用户掌握”的根源:性能优化与数据维护深度耦合,无法像关系模型那样通过索引在线重建解耦。
4.3 常见问题排查:那些年踩过的指针坑
网状模型的调试是真正的“黑匣子”体验,错误往往表现为内存越界、野指针或链表断裂。以下是基于课件内容和DBTG实战经验总结的典型问题:
现象1:FIND NEXT返回随机垃圾数据
原因:STUDENT记录的next_in_dorm字段未初始化或被意外覆盖,解引用指向非法内存地址。常见于CREATE后未执行CONNECT,或并发写入时未加锁。
解决:在CREATE后立即执行CONNECT,并在所有修改next_ptr的代码路径添加OBTAIN EXCLUSIVE;启用DBTG的VALIDATE SET命令定期检查链表完整性。
现象2:删除DORMITORY后,STUDENT记录仍显示dorm_ptr有效
原因:DBTG的“固定移出”仅清空DORMITORY侧的链表指针,STUDENT.dorm_ptr仍保留原值(悬垂指针)。后续OBTAIN该学生时,解引用已释放内存。
解决:删除DORMITORY前,先遍历其student_list,对每个STUDENT执行DISCONNECT;或在应用层为dorm_ptr添加有效性标记字段。
现象3:MODIFY更新stu_id后,FIND WITHIN RANGE失效
原因:stu_id是物理排序键,修改后记录未重排,导致B树索引(DBTG用B树索引码字段)指向错误位置。
解决:禁止直接MODIFY码字段;如需变更,先DELETE旧记录,再CREATE新记录并CONNECT;或使用REORGANIZE重建索引(停机代价高)。
现象4:多对多联系ENROLLMENT中,STUDENT与COURSE指针指向同一地址
原因:ENROLLMENT记录的student_ptr和course_ptr字段被同一内存地址赋值(如复制粘贴错误),导致逻辑关系错乱。
解决:为ENROLLMENT定义独立码字段(如enroll_id),所有指针操作基于此字段校验;在CREATE ENROLLMENT后,强制执行两次CONNECT并验证指针值不同。
现象5:OBTAIN EXCLUSIVE长时间阻塞
原因:某会话持有锁后崩溃,DBTG未自动释放锁(因无事务日志),导致SET处于永久锁定状态。
解决:启用DBTG的LOCK TABLE监控,定期DUMP LOCK STATUS;生产环境必须部署看门狗进程,超时强制RELEASE LOCK。
5. 网状模型的存储结构:从单向链接到环状链表,物理布局决定性能天花板
课件P29简略提及“单向链接、双向链接、环状链接、向首链接”,但未展开其工程取舍。这些链接方式不是理论选项,而是DBTG系统针对不同访问模式的物理优化方案,直接决定I/O次数和CPU缓存命中率。理解它们,才能读懂课件中“性能较高”的真实含义。
5.1 单向链接:空间最优,但遍历受限
单向链接(Unidirectional Link)仅在子记录中存储指向父记录的指针(如STUDENT.dorm_ptr),父记录不存子列表。这是最省空间的方案:每个联系仅需4-8字节指针。但代价是反向遍历不可能——无法从DORMITORY查所有STUDENT。课件中“学生宿舍”图若采用单向链接,则DORMITORY记录里没有student_list字段,所有查询必须从STUDENT出发,按dorm_ptr向上追溯。这种模式适用于“以子为主”的场景(如审计日志),但违背课件强调的“多对多联系需分解”原则,故DBTG主流系统极少单独使用。
5.2 双向链接:平衡之选,支持双向遍历
双向链接(Bidirectional Link)在子记录存父指针(dorm_ptr),在父记录存子链表头(student_list_head)。这是IDMS等系统的默认方案。课件图示的“R1→L1→L2→R2”链条,实际是双向链接的简化表现:R1有first_child指向L1,L1有parent_ptr回指R1和next_sibling指向L2,L2有parent_ptr回指R1。这种结构支持:
- 向下遍历:
R1 → L1 → L2(first_child+next_sibling) - 向上遍历:
L2 → R1(parent_ptr) - 反查:
R1 → L1, L2(first_child+ 链表遍历)
但空间开销翻倍:每个联系需两组指针。课件未明说,但DBTG规范要求next_sibling和parent_ptr必须同页存储(避免跨页I/O),这限制了单页子节点数量,是性能与空间的经典权衡。
5.3 环状链接:支持循环引用,但易成死循环陷阱
环状链接(Circular Link)将子链表构造成循环链表,last_child.next_sibling指向first_child。课件“父母-子女-树-种植-砍伐”图暗示了循环关系:子女指向父母,父母又指向子女。环状链接允许FIND NEXT无限循环(需应用层计数终止),但带来严重风险:若next_sibling指针被破坏,遍历将永远卡在环中。DBTG系统通常禁用纯环状,而采用“带哨兵的环状”——在链表头插入虚拟节点,first_child指向它,last_child.next_sibling也指向它,遍历时检测哨兵退出。课件未提此细节,但这是70年代工程师应对指针错误的血泪经验。
5.4 向首链接:为范围查询优化的B树变体
“向首链接”(Head-Referenced Link)是课件P29的隐藏重点。它不将子节点链成线性表,而是构建多级索引:DORMITORY记录的student_index字段指向一个B树根页,该B树按键(stu_id)索引所有STUDENT记录地址。这实质是网状模型对关系模型索引思想的吸收。当执行FIND STUDENT WITHIN DORMITORY WHERE STU_ID BETWEEN 1000 AND 1010时,DBTG直接遍历B树,而非线性扫描链表。课件称其“性能较高”,正是因B树将O(n)遍历降为O(log n)。但B树维护成本高:每次INSERT/DELETE需分裂/合并页,DBTG要求应用层调用UPDATE INDEX显式触发——这再次印证,网状模型的“高性能”是以牺牲自动化为代价的。
6. 从网状模型到现代数据库:为什么放弃指针,却继承了它的灵魂?
重读这份《数据库原理(第四版)》课件的网状模型章节,我越来越确信:我们今天用的每一个数据库优化技巧,都是在用更高阶的抽象,重新发明网状模型当年用指针暴力解决的问题。关系模型用B+树索引模拟网状的“向首链接”,NoSQL图数据库用邻接表存储复刻“双向链接”,甚至Redis的ZSET按score排序遍历,都暗合“码字段排序”的思想。但网状模型真正的遗产,不是这些技术细节,而是它暴露的一个残酷真相:所有性能优化,最终都归结为对内存地址的掌控力。
你看现代数据库的Buffer Pool,本质是网状模型“记录缓存”的升级版——它把磁盘地址映射为内存地址,用LRU算法管理,但核心仍是“减少I/O,加速地址跳转”。你看MySQL的InnoDB聚簇索引,PRIMARY KEY即网状的“码字段”,ROW_ID隐式指针即dorm_ptr,二级索引的PK回表就是parent_ptr解引用。就连PostgreSQL的TOAST机制,把大字段外存并用指针引用,不正是网状模型“大字段分离存储”的翻版?课件强调“结构复杂,不利于用户掌握”,今天看何尝不是如此?我们用ORM屏蔽SQL,用连接池管理连接,用分布式事务协调分片——每一层抽象都在远离那个最原始的指针,却也在不断重蹈覆辙:ORM N+1查询是忘了CONNECT,连接泄漏是没DISCONNECT,分布式事务超时是OBTAIN EXCLUSIVE没释放。
从那以后我每次设计数据库Schema,都强制走一遍网状模型的思维实验:如果这张表是STUDENT,它的“双亲”是谁?哪些字段是“码字段”?遍历路径需要几次I/O?如果去掉所有抽象,只留内存地址,我的查询会怎么跳?这习惯让我避开过太多坑——比如曾有个订单系统,用UUID作主键,结果写入时B+树频繁分裂,TPS骤降。换成自增ID后,就像网状模型恢复了stu_id的物理排序,性能立刻回归。还有次微服务间用HTTP同步状态,接口超时雪崩,改成Kafka事件驱动,本质就是把“同步指针调用”换成“异步消息队列”,和DBTG当年用QUEUE替代CALL如出一辙。
希望帮到你。
本文还有配套的精品资源,点击获取