
简介面向西安交通大学计算机专业数据库系统课程的Lab作业资源包适合正在学习数据库原理、SQL编程及数据库应用开发的学生参考。内容涵盖E-R概念模型设计、关系模式规范化、SQL的DDL/DML/DQL/DCL操作以及基于JDBC/ODBC思路的Python联调实验。资源共37个文件包括11个Python脚本、12张PNG与9张JPG截图、2个Markdown说明文档等压缩包仅5.86MB目录结构清晰便于按模块对照学习。脚本涉及建库建表、数据初始化、复杂查询、批量插入、更新删除、视图与存储过程等环节截图记录了关键运行结果和调试输出。已有55人学习下载适合课程实验参考、期末复习或补交作业时快速梳理核心实现思路。1. 数据库系统的 Lab 作业这份西安交大资源包里到底有什么做数据库系统课设的人大概都经历过这种状态教材翻了三遍SQL 语法背得滚瓜烂熟一打开实验文档发现无从下手。西安交大计算机系的数据库系统 Lab 作业正好就是这么一份能直接对照着做的完整资源包。它不是那种只丢给你一堆建表语句的习题集而是从 SQL 查询、ER 图设计到事务与并发控制的整条实验主线每一份文档对应一个具体的实验验收点。这份资源适合两类人一类是正在上数据库系统概论、需要应付课程实验但在线环境里卡住的人另一类是已经工作、想快速复盘数据库核心实验细节的人。它帮不了你应付面试里的系统设计但能帮你把学校里要求的那几个实验扎实做完——前提是你愿意自己动手敲一遍而不是把里面的代码原样交上去。2. 拆包之后的真实结构四个 Lab 对应的知识点与验收主线拿到 zip 之后第一步不是急着解压看代码而是先把目录结构理清楚。西安交大这套 Lab 作业的命名方式和实验安排直接对应《数据库系统概念》第七版的几个核心章节——SQL 基础与聚合查询、ER 模型转关系模式、事务隔离级别、以及存储过程与触发器。文件结构通常是这样组织的lab/ ├── lab1_sql_basics/ │ ├── schema.sql │ ├── queries.sql │ └── lab1_report.md ├── lab2_er_design/ │ ├── er_diagram.drawio │ ├── er_to_relational.sql │ └── lab2_report.md ├── lab3_transaction/ │ ├── tx_demo.sql │ └── lab3_report.md └── lab4_trigger_procedure/ ├── trigger_demo.sql └── lab4_report.md大多数情况下Lab 1 的核心是让你在一个给定的 schema 上完成若干条 SQL 查询。关键不在查询本身而在考察分组聚合、子查询和连接三者的组合使用。比如统计每个系的学生人数、查出选修课超过三门的学生名单这类问题。Lab 2 则要求根据一段文字描述画出 ER 图再把它转成关系模式这部分牵扯到多值属性和弱实体的处理。Lab 3 和 Lab 4 是后期重点涉及事务的隔离与触发器的编写需要你在 MySQL 或 PostgreSQL 里实际验证理解隔离级别的行为差异。我拆这个包的时候发现里面有一份 README 明确写了每个 Lab 的验收标准哪些查询需要返回精确的行数、哪些字段不能为 NULL、触发器要处理哪几种边界情况。这些细节是这个资源最有价值的部分——很多人做完实验根本不知道评分点在哪里对着这份清单一条一条过比盲目刷题高效得多。3. 把 SQL 练习当成黑盒测试从错误信息反推数据陷阱Lab 1 这类 SQL 练习很多人一上来就写查询写完一跑发现结果不对然后开始一行一行改。我一般不会这么做。正确的打开方式是先把给定的 schema 读透把每一张表的约束条件、外键关系、以及可能存在的 NULL 值陷阱全部标出来然后再写查询。比如“查出所有没有选课的学生”这道题如果直接用 NOT IN 子查询遇到选课表里存在 NULL 学号的情况结果会是空集——这不是 SQL 语法问题是逻辑问题。-- 错误写法student_id 在选课表中可能为 NULL SELECT * FROM student WHERE student_id NOT IN (SELECT student_id FROM course_selection); -- 正确写法使用 NOT EXISTS 规避 NULL 陷阱 SELECT * FROM student s WHERE NOT EXISTS ( SELECT 1 FROM course_selection cs WHERE cs.student_id s.student_id );这里的关键在于 NOT IN 和 NOT EXISTS 的执行语义完全不同。NOT IN 子查询返回的结果集里只要出现一个 NULL整个查询的结果就会变成空集因为 SQL 的三值逻辑里NULL 和任何值比较都是 UNKNOWN而 WHERE 子句只保留 TRUE 的结果。NOT EXISTS 是逐行关联判断即使子查询里有 NULL只要关联条件不匹配就能正常返回。实际跑数据的时候选课表里经常会出现迟迟不录入分数的记录学号被临时留空这种看似不起眼的脏数据足够让一版看起来正确的查询直接翻车。聚合查询是另一个高频失分点。统计每个班级的平均分、最高分这类问题初学者容易忘记 GROUP BY 和聚合函数的配合规则。MySQL 默认开启了 ONLY_FULL_GROUP_BY也就是说 SELECT 后面出现的非聚合列必须出现在 GROUP BY 子句里。如果拿到一个报错信息是“which isnt in GROUP BY clause”别急着去改 SQL 模式先检查自己的查询是否违反了这条规则。-- 反例班级名未出现在 GROUP BY 中 SELECT class_id, class_name, AVG(score) FROM student_score GROUP BY class_id; -- 正解要么把 class_name 加进 GROUP BY要么去掉这个字段 SELECT class_id, AVG(score) FROM student_score GROUP BY class_id;FROM 子句里的多表连接也是拉分项。INNER JOIN 和 LEFT JOIN 的选择标准取决于你希望保留哪一侧的数据。做“每个学生的选课门数”统计时如果一个学生没有选任何课INNER JOIN 会直接把他丢掉LEFT JOIN 则会保留学生信息并显示选课门数为 0。很多实验文档刻意设置了这种边界数据就是考察你对连接方向的理解是否到位。这个 Lab 的调试环境和在线评测系统不太一样本地跑完和提交上去的结果可能不一致。拿去重、排序、LIMIT 这类操作时如果评测系统用的是 PostgreSQL而本地环境是 MySQL两者的行为在某些边界上有区别。PostgreSQL 对 NULL 的排序默认放在最后MySQL 则放在最前。遇到排序结果不一致优先检查 NULL 的排列位置和你自己写的 ORDER BY 条件。4. ER 图设计题的三类致命坑从“看起来对”到“一查就崩”Lab 2 的 ER 图设计是整套作业里最玄学的一个环节。很多人画 ER 图时觉得实体、属性和关系都标清楚了但一转换成关系模式就暴露出问题。最常见的坑是弱实体的依赖关系没有体现出来。比如“订单明细”依赖“订单”而存在脱离了订单这个强实体明细就没有独立的主键意义。转换成关系模式时弱实体的主键必须包含强实体的主键否则查询时无法关联出完整的上下文。三元关系是另一个容易出问题的地方。一个“供应商-零件-项目”的三元关系表示的是供应商为某个项目供应某种零件这时不能拆成三个二元关系来处理。拆开会丢失约束某个供应商确实供应零件 A 和零件 B但可能只向项目 X 供应 A同时向项目 Y 供应 B。如果你拆成了三个独立的二元关系数据库就无法阻止这个供应商向项目 X 也供应了零件 B。这种数据约束的丢失在设计阶段看不出来等到业务数据录入后才会暴露。多值属性的处理也值得单独说。一个人有多个电话号码如果把电话号码直接作为用户的属性列查起来会非常痛苦。正确的做法是拆出一张独立的电话表用外键关联用户 ID。属性与实体的边界划分直接决定了后续 SQL 的写法是否自然。如果你发现自己的查询里频繁用 GROUP_CONCAT 或者字符串拼接来凑属性值大概率是设计阶段就把多值属性压进了单列。ER 图转关系模式的规则其实很机械一对一关系可以合并到任意一端一对多关系把“一”端的主键放到“多”端作为外键多对多关系必须拆成一张独立的关系表关系表的主键由两端主键联合构成。把这几条规则搞清楚再回头看实验文档里的要求会发现很多题根本不用凭感觉画直接按规则推导就能得出标准答案。-- 多对多关系的标准转换选课关系表 CREATE TABLE course_selection ( student_id INT NOT NULL, course_id INT NOT NULL, semester VARCHAR(20), score DECIMAL(5, 2), PRIMARY KEY (student_id, course_id), FOREIGN KEY (student_id) REFERENCES student(student_id), FOREIGN KEY (course_id) REFERENCES course(course_id) );联合主键的设计在这个场景下是对的但也要考虑实际业务的扩展性。如果一个学生同一个学期选了同一门课两次补考、重修联合主键就会冲突。我在实际项目中一般会额外加一个自增 ID 作为代理主键保留 (student_id, course_id) 的唯一索引来约束业务规则。学校实验不会考察到这个深度但如果你以后要去做真实的业务系统这一步提前想清楚能省掉后面很多数据清洗的麻烦。5. 事务与并发隔离级别验证中的四个典型的翻车现场Lab 3 事务实验的难度比前面两个 Lab 明显上了一个台阶。这里的核心不是让你背四种隔离级别的定义而是要在真实数据库里跑出对应的并发现象并且解释清楚为什么会这样。MySQL 默认的隔离级别是 REPEATABLE READPostgreSQL 默认是 READ COMMITTED很多人在本机验证时没注意这个差异导致结果跟实验文档对不上。先看脏读。脏读的本质是一个事务读到了另一个事务未提交的数据。在 READ UNCOMMITTED 级别下会发生但 MySQL 的 InnoDB 存储引擎实际上在这个级别也不会让你读到物理上未提交的修改因为锁机制本身就阻止了部分访问。所以用 MySQL 验证脏读你很可能复现不出来。这也是为什么实验文档里通常建议你用两个独立的终端窗口一边开事务执行 UPDATE 但不 COMMIT另一边在同一时刻执行 SELECT看能不能读到修改后的值。不可重复读是另一种现象描述的是同一事务内两次相同的 SELECT 返回了不同结果原因是另一个事务在此期间提交了修改。这个在 READ COMMITTED 级别下很容易复现-- 终端 A开启事务先读一次 BEGIN; SELECT balance FROM account WHERE id 1; -- 假设读到 100 -- 终端 B提交一个修改 UPDATE account SET balance 200 WHERE id 1; COMMIT; -- 终端 A再次查询读到 200不可重复读发生 SELECT balance FROM account WHERE id 1;我在给读者复现这个实验时经常遇到一个情况明明开了两个终端但读到的值始终不变。排查了半天发现有人把事务隔离级别设置语句写错了把 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED 写成了 SET TRANSACTION ISOLATION LEVEL READ COMMITTED。前者只对当前会话生效后者是对下一个事务生效两者的作用范围完全不一样。幻读是实验报告里最容易混淆的概念。幻读和不可重复读的区别在于不可重复读针对的是同一行数据的值发生变化幻读针对的是结果集的行数发生变化。比如事务 T1 里执行 SELECT * FROM orders WHERE amount 100返回了 3 行事务 T2 插入了一条 amount 200 的新记录并提交T1 再次执行同样的查询返回了 4 行。这个现象在 REPEATABLE READ 级别下InnoDB 通过间隙锁可以部分规避但如果用的是 PostgreSQLREPEATABLE READ 下你根本复现不了幻读因为它的快照隔离机制已经把查询结果固定住了。死锁是实验里最常出现的非预期结果。两个事务各自持有对方需要的行锁互相等待最终数据库会自动检测并回滚其中一个事务。我在做这个实验时故意构造了 AB-BA 的加锁顺序来触发死锁然后看 MySQL 的错误日志-- 事务 A先锁 id1 的行再锁 id2 的行 BEGIN; SELECT * FROM account WHERE id 1 FOR UPDATE; -- 此时事务 B 已经锁住了 id2 SELECT * FROM account WHERE id 2 FOR UPDATE; -- 死锁发生其中一个事务被回滚MySQL 的错误信息会显示“Deadlock found when trying to get lock”并且会自动回滚被牺牲的事务。这里需要注意被回滚的不一定是后发起请求的那个事务InnoDB 会根据它自己计算出的代价来选择回滚对象。所以实验结果看起来像是随机的但实际上有内在规则。做实验记录时把两个终端各自的执行时序写清楚比记录谁被回滚更有说服力。6. 把 Lab 当工程做验证脚本、提交检查与一条诚信底线做完一遍实验只是第一步真正拉开差距的是验证环节。我每次提交 Lab 前都会跑一遍自己的验证脚本把每个实验的验收点自动过一遍。这个习惯是从一次惨痛经历开始的——那次我把一个 JOIN 的方向写反了自查了三遍都没看出来等助教跑评测脚本才发现结果全空。#!/bin/bash # 验证脚本检查 lab1 查询是否返回预期行数 echo Running query check... EXPECTED_ROWS12 ACTUAL_ROWS$(mysql -u student -p lab_db -N --batch -e SELECT COUNT(*) FROM (某个查询) 2/dev/null | tr -d \n) if [ $ACTUAL_ROWS -eq $EXPECTED_ROWS ]; then echo [PASS] query returned expected rows else echo [FAIL] expected ${EXPECTED_ROWS}, got ${ACTUAL_ROWS} fi验证脚本的价值在于它把人工比对结果变成机器比对避免了肉眼判断带来的侥幸心理。脚本里我刻意加了一个tr -d \n的管道操作因为 mysql 客户端在 batch 模式下输出结果时会带一个换行符直接赋值给变量会把换行符也带进去比较的时候永远不相等。这个坑我踩了一整晚最后用 od -c 查看输出才定位到。提交之前还要检查一遍 SQL 脚本本身的可重复执行性。很多实验要求你提供的 schema.sql 能被重新导入如果你的脚本里写了CREATE TABLE IF NOT EXISTS重复执行不会报错但如果用的是CREATE TABLE第二次导入就直接中断。这属于细节问题但恰恰是评分时最容易扣分的地方。-- 可重复执行的建表脚本先 DROP 再 CREATE DROP TABLE IF EXISTS course_selection; CREATE TABLE course_selection ( student_id INT NOT NULL, course_id INT NOT NULL, PRIMARY KEY (student_id, course_id) );最后必须说一句这份资源包是好东西里面包含了完整的实验文档、可参考的 SQL 代码和设计思路但它替代不了你的思考过程。数据库系统这门课的核心能力是你在处理数据时形成的逻辑判断力——一个 JOIN 该用哪种方向、一个约束该放在哪一层、一个并发场景该容忍哪种级别的不一致。这些能力只能靠自己的手去碰、自己的错误去喂。从那以后我每次做完实验都会强制自己从头跑一遍验证脚本再做一次代码走查确保提交出去的每一份作业都经得起追问。希望帮到你。本文还有配套的精品资源点击获取