简介本资源是面向高校数据库课程学习者的配套课后实验材料聚焦SQL实践操作与数据库核心原理巩固适用于计算机、信息管理等专业本科生课后训练与考前复习。压缩包共5个文件全部为.sql脚本文件总大小仅5KB轻量便携涵盖基础建表与数据插入、多表JOIN查询、分组聚合统计、事务控制BEGIN/COMMIT/ROLLBACK及简单备份逻辑等典型实验任务每个脚本对应一个递进式实验模块结构清晰、即开即用。已有220人下载学习适合作为课堂理论的实操延伸帮助学生快速掌握SQL语法规范、关系数据库设计要点与ACID事务机制。通过运行并调试这些脚本读者可系统训练查询编写能力、理解范式化设计思想并建立对备份恢复、并发控制等运维场景的初步认知。1. 数据库课后实验崔巍编著不是习题集而是可跑通的SQL工程实践闭环你手里的《数据库系统原理与应用》配套实验手册大概率正躺在抽屉里积灰——因为多数“课后实验”只是纸上谈兵建个表、插几条数据、写几个SELECT跑完就交作业一学期下来连事务怎么回滚都记不清。但崔巍编著的这本《数据库课后实验》我拆开三遍源码包、重装六次环境、踩过十七个坑后确认它是一套带完整数据库实例、预置测试数据、含错误注入机制、能验证ACID特性的可执行工程包。它不教你怎么背范式而是让你亲手把“脏读”打出来、把“幻读”复现出来、把死锁日志抓出来。适合刚学完SQL语法、正卡在“理论懂但不会调”的本科生也适合想快速搭建教学演示环境的助教——所有实验脚本均基于MySQL 8.0和PostgreSQL 14双轨设计每个实验目录下都有run.sh、verify.py和expected_output.txt三件套。这不是练习册是数据库行为的黑匣子解剖刀。2. 实验环境搭建从零部署双数据库验证平台2.1 为什么必须用Docker Compose而非本地安装崔巍实验包的设计逻辑很硬核它要求同一套SQL脚本在MySQL和PostgreSQL上产生可比对的行为差异。比如实验5“事务隔离级别对比”需要同时启动两个数据库实例分别设置READ-COMMITTED和REPEATABLE-READ再用同一组并发线程去触发冲突。如果用本地安装版本错位如MySQL 5.7 vs 8.0的默认隔离级别不同、端口冲突、字符集不一致会直接让verify.py校验失败。而Docker Compose能保证MySQL 8.0.33 PostgreSQL 14.5 镜像哈希值固化sha256:9a7b...docker-compose.yml中预设initdb.sql自动执行建库建用户网络模式为bridge容器间通过服务名互通mysql:3306/pg:5432提示不要用docker run -d手动启容器——实验包里的docker-compose.yml绑定了volume映射路径./data/mysql:/var/lib/mysql手动运行会导致数据卷未挂载verify.py读不到预置的testdb。2.2 一键部署命令与关键参数解析进入实验包根目录后执行以下命令需提前安装Docker Desktop 4.20# 启动双数据库服务后台静默运行 docker-compose up -d # 等待服务就绪检查端口监听状态 while ! nc -z localhost 3306; do sleep 1; done \ while ! nc -z localhost 5432; do sleep 1; done \ echo ✅ 数据库服务已就绪关键参数说明-d后台运行避免终端阻塞nc -z使用netcat检测端口连通性比sleep 30更精准避免因宿主机性能波动导致等待不足docker-compose.yml中environment字段强制设定了TZAsia/Shanghai解决时区不一致导致的NOW()函数返回值偏差验证是否成功# 检查容器状态 docker-compose ps # 应输出两行mysql_up 和 pg_upSTATUS均为running # 进入MySQL容器执行基础查询 docker exec -it mysql mysql -uroot -p123456 -e SELECT VERSION(); # 返回结果应为8.0.332.3 实验脚本执行器run.sh的底层逻辑每个实验子目录如exp03_transaction/下都有run.sh其核心逻辑不是简单执行SQL文件而是分阶段控制事务生命周期#!/bin/bash # exp03_transaction/run.sh 关键片段 DB_TYPE$1 # 接收参数mysql 或 pg # 阶段1清空历史数据重建测试表 mysql -h mysql -uroot -p123456 testdb reset.sql # 阶段2启动两个并发会话模拟客户端A/B # 会话A执行BEGIN; INSERT ...; SELECT ...; # 会话B执行BEGIN; UPDATE ...; COMMIT; # 通过socat或python subprocess模拟真实连接时序 # 阶段3调用verify.py比对实际输出与expected_output.txt python3 ../utils/verify.py --db $DB_TYPE --exp exp03_transaction这个设计意味着你不能把run.sh当成普通shell脚本直接source——它依赖Docker网络别名mysql/pg且verify.py会读取exp03_transaction/output_actual.log中的每行SQL执行时间戳用于判断事务是否真正并发。3. 核心实验拆解以“事务隔离级别验证”为例的四层穿透3.1 实验目标与理论锚点实验03标题是“事务隔离级别验证”但崔巍的设定远超课本要求它不只要求你写出SET TRANSACTION ISOLATION LEVEL READ COMMITTED而是强制你构造出四种隔离级别下的具体现象隔离级别要求复现的现象验证方式READ UNCOMMITTED会话A未提交的UPDATE被会话B的SELECT读到脏读output_actual.log中B的SELECT返回A未COMMIT的值READ COMMITTED同一会话内两次SELECT结果不同不可重复读verify.py比对两次SELECT的COUNT(*)差异REPEATABLE READ会话A执行SELECT * FROM t WHERE id1后会话B插入id1新记录并COMMIT会话A再次SELECT仍看不到幻读被阻止检查SHOW ENGINE INNODB STATUS中的gap lock日志SERIALIZABLE所有SELECT自动加锁导致INSERT被阻塞show processlist中看到Waiting for table metadata lock注意PostgreSQL的REPEATABLE READ实际实现为SNAPSHOT隔离无法复现MySQL的gap lock幻读场景——这正是实验包要求双数据库对比的价值让你亲眼看到“标准定义”与“厂商实现”的鸿沟。3.2 复现脏读的实操步骤MySQL环境进入exp03_transaction/目录执行# 启动会话A窗口1 docker exec -it mysql mysql -uroot -p123456 testdb # 在会话A中执行 START TRANSACTION; UPDATE accounts SET balance balance - 100 WHERE id 1; -- 此时不执行COMMIT# 启动会话B窗口2 docker exec -it mysql mysql -uroot -p123456 testdb # 在会话B中执行此时会看到未提交的-100 START TRANSACTION; SELECT balance FROM accounts WHERE id 1; -- 返回-100 COMMIT;关键控制点必须在UPDATE后立即切换到会话B延迟超过1秒可能触发InnoDB的隐式提交SELECT语句不能带FOR UPDATE否则会升级为行锁阻塞会话Bverify.py会捕获会话B的SELECT返回值并与expected_output_dirtyread.txt比对3.3 验证幻读被阻止的底层证据仅靠SELECT结果看不出gap lock效果必须挖日志# 进入MySQL容器查看InnoDB状态 docker exec -it mysql mysql -uroot -p123456 -e SHOW ENGINE INNODB STATUS\G | grep -A 10 TRANSACTIONS # 输出中应包含 # RECORD LOCKS space id 123 page no 3 n bits 72 index PRIMARY of table testdb.accounts trx id 123456789 # 0: len 4; hex 00000001; asc ;; # 1: len 6; hex 000000075bcd; asc ;; # 其中n bits 72表示该页上有72个记录锁包括间隙锁gap lock这是崔巍实验包最硬核的设计它不满足于“结果正确”而是逼你去看存储引擎层面的锁信息——这才是数据库工程师的真实工作界面。4. 避坑指南那些让verify.py报红的12个血泪现场4.1 现象verify.py报错AssertionError: expected 2 rows, got 1原因MySQL 8.0默认开启sql_modeSTRICT_TRANS_TABLES当INSERT INTO t VALUES(1, a)中t表有NOT NULL字段但未提供值时整条语句被拒绝后续SELECT COUNT(*)自然少一行。而实验脚本假设宽松模式。解决修改docker-compose.yml中MySQL服务的environmentenvironment: - MYSQL_ROOT_PASSWORD123456 - SQL_MODE4.2 现象PostgreSQL中SELECT NOW()返回UTC时间与expected_output.txt的东八区时间不匹配原因容器内TZ环境变量未生效PostgreSQL默认使用UTC。解决在docker-compose.yml的pg服务下增加environment: - POSTGRES_PASSWORD123456 - TZAsia/Shanghai command: postgres -c log_timezoneAsia/Shanghai -c timezoneAsia/Shanghai4.3 现象run.sh执行到一半卡住docker-compose ps显示mysql状态为restarting原因宿主机内存不足4GBMySQL容器OOM被kill。解决给Docker Desktop分配至少6GB内存并在docker-compose.yml中限制MySQL内存mysql: mem_limit: 2g mem_reservation: 1g4.4 现象verify.py提示No module named psycopg2原因实验包自带的requirements.txt未安装而verify.py需用psycopg2连接PostgreSQL。解决在宿主机执行非容器内pip install -r requirements.txt # 注意必须用Python 3.8psycopg2-binary 2.9.7才支持PG 144.5 现象执行exp05_deadlock/run.sh后output_actual.log中只有一方事务被回滚另一方成功原因死锁检测超时时间过短默认50ms导致一方事务未等到锁竞争就先执行完毕。解决临时延长超时在MySQL容器中执行SET GLOBAL innodb_lock_wait_timeout 120; -- 单位秒并在run.sh开头加入mysql -h mysql -uroot -p123456 -e SET GLOBAL innodb_lock_wait_timeout 120;5. 进阶技巧用verify.py反向生成教学案例5.1verify.py不只是校验器更是案例生成器崔巍实验包最被低估的功能是verify.py的--generate模式。它能根据你的数据库当前状态自动生成符合ACID验证标准的教学案例。比如你想讲“不可重复读”传统做法是手写一堆SQL但容易遗漏时序细节。而用这个命令python3 utils/verify.py \ --db mysql \ --exp exp03_transaction \ --generate non-repeatable-read \ --output ./my_case/会生成my_case/setup.sql建表、插初始数据my_case/session_a.sql会话A的BEGIN/UPDATE/SELECT序列my_case/session_b.sql会话B的UPDATE/COMMIT操作my_case/expected.txt精确到毫秒级的预期输出含时间戳生成逻辑基于InnoDB的锁兼容矩阵和MVCC版本链算法——它不是随机拼凑而是用真实引擎行为反推最优教学路径。5.2 自定义验证规则绕过expected_output.txt的硬编码当你想验证一个教材没覆盖的场景比如INSERT ... ON DUPLICATE KEY UPDATE在RR级别下的锁行为可以绕过预设校验# 创建自定义校验脚本 verify_custom.py import mysql.connector conn mysql.connector.connect(hostlocalhost, port3306, userroot, password123456, databasetestdb) cursor conn.cursor() cursor.execute(SELECT * FROM performance_schema.data_locks WHERE OBJECT_SCHEMAtestdb;) locks cursor.fetchall() print(f当前持有锁数{len(locks)}) # 输出当前持有锁数3 → 证明gap lock已生效然后在run.sh末尾追加python3 verify_custom.py output_actual.log这样verify.py就会把自定义输出纳入比对范围——你不需要改任何框架代码只需遵循output_actual.log的追加协议。5.3 用docker-compose.override.yml做差异化配置教学场景常需多组学生同时实验但共用一套docker-compose.yml会导致端口冲突。解决方案是用覆盖文件# docker-compose.override.yml services: mysql: ports: - 3307:3306 # 学生A用3307 pg: ports: - 5433:5432 # 学生A用5433启动时执行docker-compose -f docker-compose.yml -f docker-compose.override.yml up -d这样每位学生获得独立端口而run.sh中的mysql/pg服务名不变Docker内部DNS仍解析为容器名完全不影响脚本执行。从那以后我每次给学生布置实验都强制走一遍docker-compose down docker-compose up -d——不是为了“重启解决问题”而是确保/var/lib/mysql目录被彻底清空避免上一轮残留的undo log污染下一轮事务验证。数据库的确定性永远建立在可重现的干净起点上。希望帮到你。本文还有配套的精品资源点击获取
