☰
悉尼大学DBMS课程实战指南:从SQL到事务恢复的底层认知
2026/9/25 6:28:56 网站建设 项目流程

简介:悉尼大学数据库管理系统课程资料,内容覆盖数据库核心理论与应用,适合正在学习数据库原理的高校学生、准备课程考试或希望系统复习数据库管理系统的技术人员。压缩包共65个文件,大小约17.04MB,以PDF课件和带答案的练习册为主体,同时包含多个SQL脚本、PPT讲义等辅助材料。资料按周组织,从概念建模、关系模型、关系代数与SQL查询,到事务处理、并发控制、完整性约束、存储索引、查询处理和数据库规范化,均有对应讲解与习题,且练习多附参考答案,便于对照练习与查漏补缺。其中还包含复习周材料和整体复习题,可帮助考前系统梳理。无论是理解ACID属性、事务隔离还是完成复杂SQL查询,都能借助这些材料得到有效训练。已有191人学习,是一份理论与实践结合较紧密的数据库学习资料。

1. 悉尼大学这门DBMS课程,为什么值得你把数据库地基重打一遍

对绝大多数写业务代码的人来说,数据库就是“会写SQL、会建索引、挂了会重启”,直到线上出现死锁、数据对不上、恢复不回来,才意识到自己缺的不是工具经验,而是数据库管理系统层面的底层认知。悉尼大学的这门Database Management System课程(通常对应INFO2120/INFO2820编号体系)恰好是把这个缺口补上的典型训练:它不是让你背语法,而是逼着你从关系模型、存储结构、事务并发一路走到恢复机制,在一学期内把DBMS的黑匣子拆开看一遍。内容密度和作业强度都高于普通“数据库应用课”,愿意照着它的脉络啃下来的人,无论是去做后端、做数据平台还是做数据库内核,底子都会扎实很多。这篇文章按课程主线拆出可执行的落地路径,结合我在本地环境复现课程练习时的经验,讲清楚每一步怎么走、参数怎么设、哪些地方容易翻车。

2. 课程主线拆解:关系模型、SQL 与 ER 建模到底在考什么

2.1 用 PostgreSQL 跑通课程 SQL 练习:最小环境与命令集

课程前半段会密集覆盖关系代数、SQL DDL/DML、约束与视图。作业里最常出现的场景是:给你一个业务描述,让你建表、写查询,再针对特定查询说明执行顺序。很多同学在 pgAdmin 或 Navicat 里点鼠标习惯了,到考试和作业里反而写不利索原生的 SQL。我的建议是:整个学期都用命令行 psql + 纯 SQL 脚本完成练习,训练强度完全不同。

# 创建课程练习专用库,owner 用自己的系统用户 createdb -h localhost -p 5432 -U $USER info2120_lab # 进入交互终端,实际上 psql 会读取 ~/.pgpass 里的密码配置 psql -h localhost -p 5432 -U $USER -d info2120_lab
-- 建一张符合课程常用场景的成绩表:学生选课记录 CREATE TABLE enrollments ( student_id INTEGER NOT NULL, course_code CHAR(8) NOT NULL, semester CHAR(6) NOT NULL, grade NUMERIC(3,1), PRIMARY KEY (student_id, course_code, semester), CHECK (grade IS NULL OR (grade >= 0 AND grade <= 100)) ); -- 插入验证约束 INSERT INTO enrollments VALUES (1001, 'INFO2120', '2024S2', 85.5); INSERT INTO enrollments VALUES (1001, 'INFO2120', '2024S2', 120); -- 违反 CHECK,会被拒绝

这里的核心不是建表语法,而是让你理解“约束是数据完整性的一部分”。PRIMARY KEY决定唯一性,CHECK把非法数据挡在入库之前,这些在课程后面的并发与恢复章节里会反复依赖。psql 终端里判断 SQL 是否执行成功,看返回的INSERT 0 1或错误码就可以,作业判题器也是这么判断的。

实际练习时,我习惯把每道作业题写成独立的.sql文件,再用psql -f批量执行。这样做的好处是:改错重跑成本低,而且提交作业时只需要打包文件,不需要截图证明。命令行执行还有一个容易被忽视的好处——你会更早意识到 SQL 的事务边界。默认情况下 psql 每条语句自动提交,如果你在一段脚本里先删表再插入,中间某条失败,前面已经提交的操作回不去。课程作业里经常要你对比“自动提交”和“手动BEGIN ... COMMIT”的行为差异,这恰恰是后面事务章节的前置实验。

2.2 ER 图转关系模式:三个必须检查的映射点

课程期中前后会进入数据库设计环节,作业典型形态是:给你一段校园场景描述(比如图书馆借阅、课程注册),让你画 ER 图,再转成关系模式。这里翻车最多的不是画图,而是从 ER 图到关系模式的映射不完整。我常用的检查清单是三条:实体必须有主键、关系两端都要有外键落入、多对多必须拆成交叉表。

-- 以“学生-课程-教师”场景为例,多对多关系 student_course 必须单独成表 CREATE TABLE student ( student_id INTEGER PRIMARY KEY, student_name VARCHAR(50) NOT NULL ); CREATE TABLE course ( course_id INTEGER PRIMARY KEY, course_name VARCHAR(100) NOT NULL, teacher_id INTEGER NOT NULL REFERENCES teacher(teacher_id) ); -- 交叉表:学生与课程的多对多关系落在这里 CREATE TABLE student_course ( student_id INTEGER REFERENCES student(student_id), course_id INTEGER REFERENCES course(course_id), enroll_date DATE NOT NULL DEFAULT CURRENT_DATE, PRIMARY KEY (student_id, course_id) );

参数说明上要注意三点:一是外键列的数据类型必须与主键完全一致,INTEGER和BIGINT混用会让 PostgreSQL 在运行时抛出类型不匹配错误;二是复合主键的列顺序要看查询模式,作业或多或少的评分规则是不看你的顺序,但(student_id, course_id)比反过来的写法更适合按学生查选课;三是命名要带明确语义,student_course比sc更容易在后面的连接查询里减少差错。

实际上,这门课并不会要求你用特定工具画 ER 图,draw.io 或者手画都可以,提交 PDF 即可。但我的经验是:每画一个关系,立刻写下对应的 SQL DDL,画图与建表同步推进,可以有效避免“图上有关系、表里没外键”的经典翻车。

2.3 规范化理论:从函数依赖到 3NF 的判定步骤

规范化是课程里理论性最强也最容易懵的部分。作业里经常直接甩给你一个R(A, B, C, D)加若干函数依赖,让你判断属于第几范式,并分解到 3NF 或 BCNF。很多同学靠背定义,遇到题目变形就垮。我按课程要求整理成一套固定流程:先找候选键,再看非主属性对候选键的依赖类型,最后判断是否存在传递依赖或部分依赖。

具体步骤是:第一步,根据函数依赖集求出闭包,确定候选键;第二步,列出所有非主属性;第三步,检查是否存在非主属性对候选键的部分依赖,存在则至少是 1NF,不存在再判断有没有传递依赖;第四步,如果所有非主属性都完全直接依赖候选键,则达到 3NF。注意,BCNF 要求“所有依赖左侧都是超键”,这个条件比 3NF 更严,课程考试里经常拿 BCNF 和 3NF 的区别做区分题。

-- 规范化过程不适合用 SQL 直接验证,但可以用约束来检验分解结果的关系模式 -- 比如将 R(A, B, C, D) 分解为 R1(A, B, C) 与 R2(A, D) CREATE TABLE r1 ( a INTEGER PRIMARY KEY, b INTEGER NOT NULL, c INTEGER NOT NULL ); CREATE TABLE r2 ( a INTEGER PRIMARY KEY, d INTEGER NOT NULL, FOREIGN KEY (a) REFERENCES r1(a) );

分解成两个表后,连接查询仍能恢复到原始数据,这就是无损连接。课程作业里会要求你给出分解过程,并验证是否满足无损连接与依赖保持。用 SQL 建表来“模拟”分解结果,能帮你直观感受连接代价。一个值得留意的坑是:分解到 3NF 时依赖保持容易满足,分解到 BCNF 时则可能丢失函数依赖,考试和作业都偏好让你论证这个取舍。

3. 复刻课程数据库设计项目:从需求分析到可运行原型

3.1 课程设计作业的常见形态与选题逻辑

课程后半段通常有一个占比较高的团队项目:从零设计一个数据库系统并用 SQL 实现。历年常见选题不外乎书店库存、健身房会员、医院预约这类经典业务。这类项目的核心不是功能多炫,而是考察你是否完整走了一遍“需求分析 → ER 建模 → 关系模式 → 建表约束 → 查询视图”的流程。我见过太多组把时间花在写花哨的 Java 界面上,结果 ER 图里实体关系都理不清楚,分数照样不高。

我的观点是:项目选题越“土”越好。选择自己熟悉业务场景,能把精力留给约束设计、索引策略和事务边界,而不是花大量时间去猜业务规则。作业文档里通常只给需求概述,具体规则需要你们自己补充,比如“预约不能冲突”“库存不能为负”这类业务约束,最终都要落到 CHECK 约束或事务逻辑上。

-- 以“健身房会员与课程预约”为例,核心业务规则落地成约束 CREATE TABLE member ( member_id INTEGER PRIMARY KEY, member_name VARCHAR(50) NOT NULL, level VARCHAR(10) DEFAULT 'normal' ); CREATE TABLE class_schedule ( schedule_id INTEGER PRIMARY KEY, class_name VARCHAR(50) NOT NULL, capacity INTEGER NOT NULL CHECK (capacity > 0), current_count INTEGER NOT NULL DEFAULT 0 CHECK (current_count >= 0) ); CREATE TABLE booking ( booking_id INTEGER PRIMARY KEY, member_id INTEGER NOT NULL REFERENCES member(member_id), schedule_id INTEGER NOT NULL REFERENCES class_schedule(schedule_id), booking_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE (member_id, schedule_id) );

这里的UNIQUE (member_id, schedule_id)解决“同一会员不能重复预约同一节课”的问题,capacity与current_count两列组合实现“满员不可预约”的业务规则。实际在项目里,减少可用名额和插入预约记录必须放在一个事务里完成,否则并发下会出现超卖——这块内容课程后半段的并发控制章节会专门讲。

3.2 用 Python + SQLite 快速搭一个可运行原型

项目交付通常要求“数据库设计文档 + SQL 脚本 + 可运行程序”。不少学生会选 Java + MySQL 的组合,这本身没错,但如果你只是想快速验证设计是否合理,Python + SQLite 是我体验下来最顺的组合:SQLite 支持完整的 DDL/DML 语法,Python 内置驱动,不需要额外装服务端,作业提交时附带一个.db文件即可。

import sqlite3 # 连接数据库;文件不存在时会自动创建 conn = sqlite3.connect("gym.db") conn.execute("PRAGMA foreign_keys = ON") # 关键:SQLite 默认不检查外键,必须手动开启 cur = conn.cursor() # 创建核心表 cur.execute(""" CREATE TABLE IF NOT EXISTS class_schedule ( schedule_id INTEGER PRIMARY KEY, class_name VARCHAR(50) NOT NULL, capacity INTEGER NOT NULL CHECK (capacity > 0), current_count INTEGER NOT NULL DEFAULT 0 CHECK (current_count >= 0) ) """) # 插入数据并提交 cur.execute("INSERT INTO class_schedule (class_name, capacity) VALUES (?, ?)", ("Yoga", 10)) conn.commit() conn.close()

Python 代码里必须注意两点。第一,PRAGMA foreign_keys = ON必须放在每次连接的开始执行,因为 SQLite 的外键检查默认关闭,不开的话你在 Python 层删除一个被引用的会员,booking 表里的记录不会被拦截,完整性约束形同虚设。第二,使用?占位符传参而不是字符串拼接,这既是防注入的基本功,也是课程强调的“参数化查询”实践。提交项目时数据库文件会带着你测试过的数据,如果不想让老师看到你的脏测试数据,提交前写个清理脚本把测试行删掉。

3.3 从本地原型到课程验收:索引与视图的加分配置

课程项目验收时,老师常会现场跑几个高频查询,并问你“这个查询为什么快/慢”。因此,提前给外键列和查询条件列建索引是必须做的功课。我的做法是根据作业文档里的查询需求反推索引,而不是给每列都加索引。

-- 高频查询:按会员查预约记录,外键列 member_id 需要索引 CREATE INDEX idx_booking_member ON booking(member_id); -- 高频查询:按课程查预约量统计 CREATE INDEX idx_booking_schedule ON booking(schedule_id);

参数选择上,索引列的顺序也很讲究。如果查询条件是WHERE member_id = ? AND schedule_id = ?,那么单个复合索引(member_id, schedule_id)通常优于两个单列索引。但如果你还要统计某节课的预约人数,schedule_id单列索引反而是更直接的选择。课程不会要求你做基准测试,但你要能讲清楚“为什么建这个索引”背后的选择逻辑。

视图是另一个容易被忽略的加分项。把复杂的多表连接封装成视图,既能让程序代码更简洁,也是关系模型“逻辑独立性”的直接体现。我一般会在项目文档里单独写一节“视图设计理由”,把每个视图对应的业务需求列清楚。

4. 事务、并发与恢复:DBMS 深水区为什么是拿分关键

4.1 ACID 在课程作业里怎么被检验

事务章节是这门课的分水岭,也是作业难度跳变最大的地方。前面 SQL 练习靠熟练能拿满分,事务题目则必须真正理解 ACID 每个字母的含义,并且能在具体场景里指认:哪条语句破坏了原子性,哪个隔离级别下会出现脏读。

课程作业常见的考察方式是给你一个转账场景,让你分析不同隔离级别下的执行结果。用BEGIN TRANSACTION包裹多条 SQL,这是实现原子性的基本手段,但要问一句:如果中途COMMIT失败,数据库保证回滚吗?答案是,只要语句在执行过程中发生错误,事务会进入 aborted 状态,必须显式ROLLBACK,否则后续语句全部被拒绝。

-- 转账事务:从 1001 账户扣款,向 1002 账户入账 BEGIN; UPDATE account SET balance = balance - 500 WHERE account_id = 1001; -- 如果执行到这里时 1001 余额不足,CHECK 约束触发,整个事务必须回滚 UPDATE account SET balance = balance + 500 WHERE account_id = 1002; COMMIT;

这个例子里的关键点不是 SQL 本身,而是约束与事务的联动。如果balance >= 0的 CHECK 约束在扣款语句上失败,事务会进入 aborted 状态,你需要手动ROLLBACK,否则连接会被锁住,后续所有语句都返回错误。实际做课程实验时,不少同学在这里卡了很久,以为是数据库坏了,其实是没回滚。

4.2 隔离级别与锁:用两个并发查询验证读现象

并发章节里,课程会重点讲读已提交与可重复读之间的差异。PostgreSQL 默认隔离级别是读已提交,这也是我们做验证实验时最常配置的环境。

-- 会话 A:开启事务并修改数据但不提交 BEGIN; UPDATE account SET balance = balance + 100 WHERE account_id = 1001; -- 此时不 COMMIT,保持事务打开 -- 会话 B:在另一连接执行查询 SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT balance FROM account WHERE account_id = 1001;

在默认的读已提交级别下,会话 B 看到的是修改前的数据,因为 A 还没提交,但 A 持有行锁;此时会话 B 如果试图修改同一行,会被阻塞直到 A 提交或回滚。这就是课程作业里最常见的锁等待场景。要验证可重复读与读已提交的区别,需要把 B 也放进一个事务里连续查询两次,期间 A 提交修改,观察 B 两次查询结果是否一致。PostgreSQL 在可重复读级别下使用快照隔离,两次查询结果一致,而在读已提交下两次结果可能不同。

这里的参数调整集中在SET TRANSACTION ISOLATION LEVEL语句,修改只在当前事务内生效。对课程实验来说,关键是理解“不同隔离级别解决不同异常”,不要求把四种级别全背下来,但脏读、不可重复读、幻读三个现象要能用实验复现。

4.3 WAL 日志与恢复:从崩溃里把数据找回来

恢复机制是课程里最“黑匣子”的部分,但恰恰也是很多线上事故的关键。课程通常会用 PostgreSQL 的 WAL 机制来讲日志先行与重做/撤销过程。做实验时最有价值的是观察pg_wal目录下的日志文件变化,以及对比不同fsync配置下的崩溃恢复表现。

PostgreSQL 中控制崩溃恢复行为的核心参数是fsync与synchronous_commit,这些参数写在postgresql.conf里。默认配置下fsync=on,每次提交都会把 WAL 刷到磁盘,确保崩溃后不丢数据;如果把fsync=off,性能会变快但断电后可能丢最近提交的事务。课程实验里我一般不建议关掉 fsync 去“测性能”,因为丢数据的结果不可控,这个开关在生产环境更是不能随便动。

实际做恢复验证有一个更安全的办法:用pg_ctl stop -m immediate模拟崩溃,再重启数据库后检查是否丢失已提交的数据。但要注意,-m immediate会跳过正常关闭流程,相当于拔电源,重启后实例会进入恢复模式,这时候不要急着连接数据库,等日志里的 “database system is ready” 出现再操作。

5. 悉尼大学 DBMS 课程的高频踩坑点与排查路径

5.1 本地连接串写错:psql 连不上课程服务器

现象:psql 输入密码后报connection refused或password authentication failed,但确认密码没记错。原因:一是服务器监听地址没包含你连的那个 IP,二是pg_hba.conf里的认证方式与客户端请求不匹配。解决:连接前先检查服务器端listen_addresses是否为*,再确认pg_hba.conf中对应条目把md5或scram-sha-256写上;作业一般要求用学校提供的虚拟机镜像,本地自建 PostgreSQL 时这些参数最容易漏。

5.2 索引没生效:查询计划器不用你的索引

现象:建了索引后跑EXPLAIN ANALYZE,发现还是Seq Scan,作业问“为什么索引没生效”。原因:表数据量太小,优化器认为顺序扫描比索引扫描更快,或者查询条件里对索引列做了函数计算。解决:数据量只有几百行时不用纠结索引是否生效,课程报告里你只需解释“数据规模小时优化器选择顺序扫描更优”即可;如果确实想看到索引扫描,可以临时调低enable_seqscan:SET enable_seqscan = off;,但记得这只是实验手段,不是生产调优做法。

5.3 函数依赖求错候选键:整个范式判断全歪

现象:给定函数依赖后,闭包求出来的候选键和别人不一样,导致后面 2NF/3NF 判断全部算错。原因:闭包运算漏掉了依赖的传递性,比如 A→B、B→C 时,闭包里少了 C。解决:按课程教的算法一步步求F+闭合集合,不要跳步。我习惯写一个小脚本列出依赖集,逐个求闭包再对照候选键定义,其实手工慢慢推一两次之后,后面就顺畅了。

5.4 外键约束在 SQLite 里失效

现象:Python + SQLite 项目里删除父表记录,子表数据还在,约束像没写一样。原因:SQLite 默认不启用外键检查。解决:每次连接都执行PRAGMA foreign_keys = ON;如果用的是 SQLAlchemy,则要在连接字符串里配置事件监听器,确保每次连接都自动开启。这也是我前面特别强调这条参数的原因。

5.5 元组锁定导致死锁

现象:两个事务各更新一行,然后交叉更新对方的行,数据库抛出deadlock detected。原因:课程实验里为了教死锁概念,特意让你制造这种场景。解决:看到死锁错误不要慌,回滚其中一个事务再以固定顺序获取锁。课程报告里把它写成“通过约定更新顺序来避免死锁”属于标准答案,实际生产中这也是常见做法。

6. 自学本课程的正确姿势:用一份作业驱动整条知识链

如果你不在悉尼大学,但想按这门课的体系自学,最有效的方式不是从头读教材,而是给自己布置一个“数据库设计作业”:选一个小区停车位预约场景,从 ER 图开始,一路做到事务与恢复实验,把这门课的知识点全部串起来。我当年就是用“健身房预约”做完了这一整套,收获比刷两遍教材都大。

做完之后,找三件事来验证自己是否真的掌握:第一,把 ER 图给一个不懂数据库的朋友看,他能看懂业务规则,说明建模成功;第二,用EXPLAIN ANALYZE分析自己的高频查询,至少能给每个查询讲出执行计划里的一个开销来源;第三,模拟一次崩溃恢复并复盘,看数据是否完整,日志里能否找到恢复路径。

我的一个习惯是每个章节结束后,用 markdown 写一份两百字的“我在这里犯了什么错”,比笔记更值钱。全部做完你会发现,后续再写业务代码时,关于事务边界、约束设计、索引选择的判断会快很多,遇到线上数据问题也不会只会上网搜sqlmap fingerprint这类报错信息盲目试错,而是能从数据库运行机制去排查。希望这份梳理能帮你少踩一些没必要踩的坑,也让你在这个方向上投入的时间真正有回报。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询