简介:华中科技大学数据库系统原理实践课程(以MySQL为例)的配套实验资源包,适合正在学习数据库原理的本科生、研究生以及需要系统梳理MySQL实践的开发者。资源围绕课程全部核心实验组织,既有数据库/表定义、完整性约束、数据查询、增删改操作等基础内容,也覆盖视图、存储过程、触发器、并发控制与隔离级别、备份恢复、安全性控制及数据库应用开发等进阶专题。
压缩包共92个文件,整体仅1.42MB,以62个SQL脚本为主,辅以Java、C++源码、Shell备份脚本、ER图设计文件(drawio/mwb/png)、Word报告模板及PDF任务书,便于按需查阅和对照学习。目前已有67人学习使用,适合同步跟做课程实验或考前完整复盘。
资源包目录按实验关卡的序号命名,定位清晰;包含B+树索引实现、金融场景SQL查询等具有一定挑战的案例,以及完整任务书和报告模板,可帮助理解数据库底层机制和设计方法,也能直接用于实验报告的撰写与提交。总之是一份针对性强、结构完整的课程实践参考资料。
1. 数据库系统原理实践:以MySQL为例的课程包,值得照着做一遍吗
拿到一份名为“数据库系统原理实践 - 以MySQL为例”的课程资料压缩包,第一反应往往是先解压看目录。这类实践包和网上零散的 SQL 教程最大的区别在于:它是按数据库原理课的知识点组织的,从 ER 模型到范式、事务、索引,每一项都对应可运行的任务,逼着你在 MySQL 里把原理课上学过的概念重新验证一遍。对正在做课程设计的学生来说,它能直接告诉你实验报告里每个小节该输出什么;对想补数据库实操的开发者,它也是一条现成的练习路径。别指望靠背 SQL 语句过关——实践课的评分点几乎都在“能不能把原理讲清楚”和“能不能让数据库按你的设计跑起来”这两件事上,而这恰好是这类以 MySQL 为例的实践资料最花力气的地方。
2. 拿到课程实践包先做什么:环境选型与最小化跑通
2.1 为什么课程实践普遍选 MySQL:选型理由与边界
数据库原理课程的教材和实验指导书,绝大多数把默认数据库定在 MySQL 上。这不是偶然,而是因为 MySQL 的语法和 SQL 标准贴合度好,课程里讲的关系代数、嵌套查询、分组聚合都能直接对应到具体语句上。用 Oracle 或 PostgreSQL 不是不行,但实验指导书里的验证脚本往往没有为它们做适配,学生在环境差异上消耗的时间会远超做实验本身。
MySQL 的 InnoDB 存储引擎默认支持事务和外键,这刚好覆盖了数据库原理课的核心实验点:完整性约束、事务 ACID、并发控制。MyISAM 引擎虽然读快,但既不支持事务也不支持外键,课程实践里基本不会选它。需要说明的是边界:MySQL 在复杂窗口函数、部分高级优化能力上弱于 PostgreSQL,但本科阶段的课程实践深度,MySQL 的能力已经绰绰有余。
2.2 最小化安装与初始化:两条命令和两个必调参数
不同系统的安装命令不同,但思路一致:装好服务端,确认服务启动,然后做安全初始化。这里给 Debian/Ubuntu 和 RHEL/CentOS 两个常见分支:
# Debian / Ubuntu 系 sudo apt update sudo apt install -y mysql-server # RHEL / CentOS 系 sudo yum install -y mysql-server sudo systemctl start mysqld sudo systemctl enable mysqld装完后执行安全初始化向导,注意两个参数。第一个是密码强度校验插件,课程环境可以关掉,否则设一个像123456这样的弱密码会被直接拒绝,平白给自己添堵。第二个是 root 的认证插件:Ubuntu 上默认走auth_socket,表现为sudo mysql能进、mysql -u root -p却报 Access denied;CentOS 上安装时会生成临时密码,需要先看日志再改密码。如果登录方式不对,后续所有实践步骤都推不下去。
# 检查服务状态 systemctl status mysql # 用 root 进入 MySQL 交互终端 sudo mysql -u root进入后立即确认版本和当前认证方式,这一步相当于给整个实践过程定基线:
SELECT VERSION(); SELECT user, host, plugin FROM mysql.user WHERE user='root';2.3 导入样例库:命令行导入与两个失败分支
课程实践包的压缩包里,SQL 脚本是核心资源,一般会按章节组织:建库脚本、建表脚本、插入数据脚本、查询练习脚本。常见做法是先用命令行把整个库建起来,再逐步执行单独的练习脚本。最小化导入只需要两步:
# 创建数据库并指定默认字符集 mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS school DEFAULT CHARSET utf8mb4;" # 把课程包里的 init.sql 导入 school 库 mysql -u root -p school < /path/to/course_package/init.sql-u指定用户,-p表示需要密码,<把文件内容重定向给 mysql 客户端执行。如果 init.sql 里已经包含CREATE DATABASE,第一步可以跳过,但显式创建能让你自己控制库名和字符集,避免脚本里的库名和你的预期不一致。
导入失败时先看两个地方:错误码是1049说明库不存在,1146说明表不存在;如果是1366或1265,说明某一列的数据在严格模式下被拒绝,最常见的原因就是字符集或日期格式不兼容。另一个常见做法是在 mysql 客户端里执行source /path/to/init.sql,效果和重定向一样,好处是能实时看到每条语句的报错。
导入完成后做一次快速验证,确认表和数据都进来了:
mysql -u root -p -e "USE school; SHOW TABLES; SELECT COUNT(*) FROM student;"SHOW TABLES列出所有表,COUNT(*)是最廉价的数据完整性检查。如果显示 0 条或直接报错,回 2.3 的导入步骤排查,不要急着往下走。
2.4 验证连接:用一条 SQL 和一个 Python 脚本确认环境可用
环境验证分两层:mysql 客户端能连只是第一层,后面课程实践如果要写小型应用,还会用到编程语言连接 MySQL。这里给一个典型的 Python 连接验证脚本,用 pymysql:
import pymysql conn = pymysql.connect( host="127.0.0.1", user="root", passwd="your_password", db="school", charset="utf8mb4" ) cursor = conn.cursor() cursor.execute("SHOW TABLES") for row in cursor.fetchall(): print(row) conn.close()host写 127.0.0.1 而不是 localhost,能绕开部分系统上 socket 连接的权限差异;charset必须显式指定 utf8mb4,否则查询中文结果可能出现乱码。这一步跑通后,后面无论是做课程报告里的应用演示,还是做期末的小型系统,都不会在连接层卡住。
3. 核心实验一:从 ER 模型到物理表——建库建表与完整性约束
3.1 把需求翻译成表结构:ER 图到关系模式的三个步骤
课程实践通常从一个小型业务场景开始,比如学生选课管理系统。这类场景的 ER 图高度相似,核心是三个实体和两个联系:学生、课程、选课,学生和课程之间是 M:N 联系。从 ER 图到关系模式,我一般按三步走。
第一步,每个实体转成一张表,实体的属性就是字段。第二步,根据联系的基数决定外键怎么放:1:N 的联系把“一”方的主键放到“多”方做外键,M:N 联系则必须单独拆出一张中间表,这张表的主键通常是两个外键的联合。第三步,把每张表过一遍范式,看有没有部分依赖和传递依赖,比如选课表的成绩只依赖联合主键,不存在只依赖学号或只依赖课程号的字段,这就是 2NF。
这里最容易翻车的点是:把 M:N 联系省略成在课程表里加一个学号字段。这样做会大量冗余,插入、修改、删除的异常在实验报告里根本圆不回来。课程答辩时导师最常追问的就是“你这个外键为什么放在这张表里,设计依据是什么”,如果答不出基数分析和范式推导,表建的再漂亮也要扣分。
3.2 建库建表 SQL:主键、外键、唯一约束的落地写法
以学生选课为例,标准的建表语句如下,注意约束和存储引擎的写法:
CREATE DATABASE IF NOT EXISTS school DEFAULT CHARSET utf8mb4; USE school; CREATE TABLE student ( sno CHAR(9) NOT NULL COMMENT '学号', sname VARCHAR(20) NOT NULL COMMENT '姓名', sdept VARCHAR(20) DEFAULT '计算机系' COMMENT '系别', PRIMARY KEY (sno) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生表'; CREATE TABLE course ( cno CHAR(4) NOT NULL COMMENT '课程号', cname VARCHAR(40) NOT NULL COMMENT '课程名', credit TINYINT UNSIGNED DEFAULT 2 COMMENT '学分', PRIMARY KEY (cno) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程表'; CREATE TABLE sc ( sno CHAR(9) NOT NULL COMMENT '学号', cno CHAR(4) NOT NULL COMMENT '课程号', grade DECIMAL(5,2) DEFAULT NULL COMMENT '成绩', PRIMARY KEY (sno, cno), CONSTRAINT fk_sc_sno FOREIGN KEY (sno) REFERENCES student(sno) ON DELETE CASCADE, CONSTRAINT fk_sc_cno FOREIGN KEY (cno) REFERENCES course(cno) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='选课表';参数说明是课程报告里必须写清楚的部分。CHAR(9)是定长字符串,9 表示 9 个字符,学号长度固定用 CHAR;姓名长度不固定,用VARCHAR(20)更省空间。credit TINYINT UNSIGNED表示学分是非负小整数,范围 0 到 255,够用且省空间。DECIMAL(5,2)表示成绩最多 5 位,小数 2 位,可以精确存 0 到 999.99,不会出现浮点误差。
ENGINE=InnoDB是必须写的,只有 InnoDB 支持事务和外键。ON DELETE CASCADE表示删除学生时自动删除该学生的选课记录,避免出现悬空外键。PRIMARY KEY (sno, cno)是联合主键,这也是中间表的标准写法,保证同一学生对同一门课程只能有一条选课记录。
3.3 表结构设计错了怎么办:ALTER TABLE 补齐约束的三种场景
建表时漏了外键是很常见的事,特别是先建表后导入数据的情况。不要删表重建,用 ALTER TABLE 补约束:
-- 场景一:给已有选课表补外键 ALTER TABLE sc ADD CONSTRAINT fk_sc_sno FOREIGN KEY (sno) REFERENCES student(sno); -- 场景二:给课程名加唯一约束 ALTER TABLE course ADD UNIQUE KEY uk_cname (cname); -- 场景三:修改字段长度,注意会造成全表重建 ALTER TABLE student MODIFY sname VARCHAR(30) NOT NULL;ADD CONSTRAINT用来加约束并命名,命名规范通常是fk_表名_字段名,方便后面根据约束名删除。UNIQUE KEY创建唯一索引,同时起到唯一约束的效果,课程里讲“唯一性约束”时对应的就是这条语句。MODIFY修改字段定义时不保留原 COMMENT,所以改写时要连同COMMENT一起写全,否则注释会丢。
需要强调的是,MODIFY在表数据量大时会锁表重建,课程实践里表就几十行无所谓,但如果是线上大表,这种操作要放到低峰期执行。实验报告里可以主动写一句“修改字段类型需评估对现有数据的影响”,这句话能让报告显得更专业。
3.4 数据填充与 CHECK 约束:MySQL 版本差异是个玄学
往表里插数据时,CHECK 约束是个容易踩坑的点。MySQL 在 8.0.16 之前并不会真正执行 CHECK 约束,语句能解析但不会拦截非法数据。8.0.16 之后才正式生效:
-- 低版本:CHECK 会被解析但不会真正拦截 CREATE TABLE sc_test ( sno CHAR(9) NOT NULL, grade DECIMAL(5,2) CHECK (grade BETWEEN 0 AND 100) ); -- 插一条明显非法的数据,8.0.16 之前的版本能插进去 INSERT INTO sc_test (sno, grade) VALUES ('202300001', 120); -- 8.0.16 之后的版本,这条 INSERT 会直接被拒绝检查约束是否生效,最直接的办法是用SHOW CREATE TABLE sc_test看表定义。如果输出里没有 CHECK 子句,说明约束根本没被识别。低版本环境下,更可靠的方案是在应用层做校验,或者用触发器实现等价逻辑。课程报告里如果写了 CHECK 约束,一定要注明当前 MySQL 版本是否真正支持,否则答辩时被问到“低版本怎么保证数据合法性”会很难收场。
4. 核心实验二:事务、隔离级别与索引——原理课考点的实操映射
4.1 事务 ACID 在 MySQL 里怎么观察
原理课上讲事务 ACID 特性,讲得再清楚也不如在终端里亲眼看到一次回滚。事务实验的标准动作是在一个会话里开事务、改数据、查询验证,再决定提交还是回滚:
START TRANSACTION; UPDATE sc SET grade = grade + 5 WHERE sno = '202300001'; SELECT * FROM sc WHERE sno = '202300001'; -- 此时数据只在当前会话可见 ROLLBACK;START TRANSACTION显式开启一个事务,之后执行的 DML 操作不会立即生效,直到COMMIT或ROLLBACK。ROLLBACK会把数据恢复到事务开始前的状态。这里的核心观察点是:执行 UPDATE 后不提交,立即打开另一个终端查询同一条数据,看不到修改。这个现象就是对事务隔离性最直观的验证,课程报告里要记录的就是这类“眼见为实”的证据。
另一个实用的观察命令是SHOW ENGINE INNODB STATUS,它会输出当前 InnoDB 引擎的运行状态,包括活跃事务列表、锁等待信息。事务卡死或锁冲突时,可以用它定位是哪一条事务占了锁。输出内容很长,实验报告里截取事务相关的段落即可,不要整段贴上去。
4.2 四个隔离级别:用两个终端演示脏读与不可重复读
隔离级别是原理课的重点,也是实践中最容易演示出效果的知识点。标准做法是用两个终端连接同一个库,一个终端改数据不提交,另一个终端在指定隔离级别下查询。先看脏读:
-- 终端 A:把隔离级别调到最低 SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT * FROM sc WHERE sno = '202300001';-- 终端 B:开启事务修改成绩但不提交 START TRANSACTION; UPDATE sc SET grade = 90 WHERE sno = '202300001'; -- 切回终端 A 查询终端 A 在READ UNCOMMITTED下能查到终端 B 未提交的 90 分,这就是脏读。把隔离级别换成READ COMMITTED再重复一遍,终端 A 查到的还是旧值,直到终端 B 提交后才能看到 90。READ COMMITTED解决了脏读,但会出现不可重复读:同一个事务里两次相同查询结果不一致。演示方法是终端 A 开启事务后查询一次,终端 B 提交修改,终端 A 再查询一次,两次结果不同。
这就是课程实践里最常用的“双终端对照法”,实验报告里把四个隔离级别各跑一遍,记录每个级别下脏读、不可重复读、幻读是否出现,对照原理课的理论表格,完整性比单纯背定义强得多。需要注意,MySQL 默认隔离级别是REPEATABLE READ,演示之前先执行SELECT @@transaction_isolation确认当前位置,避免结果和预期对不上。
4.3 EXPLAIN 看执行计划:索引失效的三种典型场景
索引实验的核心工具是EXPLAIN。它不会真正执行查询,而是让优化器把执行计划输出给你,是判断一条 SQL 有没有走索引的黄金手段:
EXPLAIN SELECT * FROM student WHERE sno = '202300001';重点关注type和key两列:type为const或ref说明走了索引,为ALL说明全表扫描;key为NULL说明这条 SQL 没用上任何索引。课程实践里最常见的三个索引失效场景:
-- 场景一:隐式类型转换,字符串列和数字比较 EXPLAIN SELECT * FROM student WHERE sno = 202300001; -- 场景二:前导通配符 EXPLAIN SELECT * FROM student WHERE sname LIKE '%张%'; -- 场景三:在索引列上做函数运算 EXPLAIN SELECT * FROM student WHERE YEAR(create_time) = 2024;三条语句的type大概率都是ALL,key为NULL。sno建了主键索引,但sno = 202300001把字符串列和整型常量比较,优化器会做类型转换导致索引失效;LIKE '%张%'的前导百分号让 B+ 树无法定位起点;YEAR(create_time)对索引列套了函数,索引值被破坏。这三个场景在实验报告里值一个独立的对比表格,每条 SQL 的执行计划都贴出来,再用一句话解释失效原理,是很好的加分项。
4.4 课程报告里索引与事务部分怎么组织
这两个实验点在答辩时是高频提问区,报告组织建议用“现象证据 + 原理解释”的格式。每个实验点给出证据表格,比如索引实验用如下结构:
| 查询写法 | type 列 | key 列 | 是否走索引 | 原因 |
|---|---|---|---|---|
| sno = '202300001' | const | PRIMARY | 是 | 主键等值匹配 |
| sno = 202300001 | ALL | NULL | 否 | 隐式类型转换 |
| sname LIKE '%张%' | ALL | NULL | 否 | 前导通配符 |
| YEAR(create_time) = 2024 | ALL | NULL | 否 | 索引列函数运算 |
表格能让评分老师一眼看清你做了对比实验,而不是只写了“索引很重要”这类空话。事务部分的报告同理:四个隔离级别各跑一遍,记录脏读和不可重复读的观察结果,再贴关键会话截图,比抄一段教材定义有用得多。
5. 避坑与排查:数据库实践包最常见的五个翻车现场
5.1 现象:插入中文变成乱码
中文乱码是课程实践里出现频率最高的问题。插入的中文查出来变成问号或乱码,几乎可以肯定是字符集链路不一致。原因通常是数据库、表、连接三者的字符集没有全部统一,比如建库用了latin1,但客户端用utf8mb4连接。
解决方法是把整条链路统一到 utf8mb4。先看现状再改:
SHOW VARIABLES LIKE 'character_set_%'; ALTER DATABASE school DEFAULT CHARSET utf8mb4; ALTER TABLE student CONVERT TO CHARACTER SET utf8mb4;已经插进去的乱码数据改不回来,只能清掉重插。这是一个血泪经验:建库建表时就把DEFAULT CHARSET utf8mb4写上,连接字符串里也显式传charset="utf8mb4",不要依赖默认值。
5.2 现象:外键建不上
执行ALTER TABLE sc ADD FOREIGN KEY ...时报错,常见错误码 1215 或 1005。原因通常是三个:字段类型或长度不一致,比如 student 表的sno是CHAR(9),sc 表里却是VARCHAR(20);两个表的存储引擎不一致,一个是 InnoDB 一个是 MyISAM;或者被参照表里已有违反外键约束的数据,比如 sc 表里有一个学号在 student 表中不存在。
解决方法是先比对两边的字段定义,统一类型和字符集;再确认两张表都是 InnoDB,用SHOW TABLE STATUS WHERE Name='sc'查看 Engine 列;最后清理掉孤儿数据,再重新加约束。实践课里最常见的是第二种和第三种,因为建表时漏写 ENGINE 参数,MySQL 会走默认引擎,很容易造出 MyISAM 表加外键失败。
5.3 现象:分组查询直接报错
执行SELECT sdept, AVG(grade) FROM sc GROUP BY sdept时报错,提示this is incompatible with sql_mode=only_full_group_by。原因很明确:MySQL 8.0 默认开启ONLY_FULL_GROUP_BY模式,SELECT 里出现的非聚合列必须出现在 GROUP BY 子句中,这是对 SQL 语义的严格约束。
解决方法是改 SQL,而不是关 sql_mode。比如只查系别和平均分,写法是SELECT sdept, AVG(grade) FROM student JOIN sc ON student.sno = sc.sno GROUP BY student.sdept。如果想临时关闭严格模式做验证,可以执行SET sql_mode='',但这只是当前会话生效,而且不推荐在报告里这么写,因为关掉严格模式会让很多 SQL 标准检查失效,反而掩盖了问题。
5.4 现象:普通用户执行导入时报权限不足
用非 root 账号执行mysql -u normal_user -p school < init.sql时,报Access denied for user。原因是 init.sql 里可能包含CREATE DATABASE或CREATE TABLE,而普通用户对这些库的权限是空的。课程实践环境里,最简单的做法是先用 root 把样例库建好,再给普通用户授权:
GRANT ALL PRIVILEGES ON school.* TO 'normal_user'@'localhost'; FLUSH PRIVILEGES;school.*表示 school 库的全部对象,FLUSH PRIVILEGES刷新权限表。还有一种场景是 root 用sudo mysql能进但远程连不上,这是因为 root 的 host 是 localhost,需要在另一台机器连库时创建'root'@'%'账号或改用普通用户连接。课程实践做完后,把本机权限最小化也是一项加分项,说明你有安全意识。
5.5 现象:事务执行后数据没写进去
UPDATE 或 DELETE 执行后 SELECT 能看到数据,但重开终端发现数据没变。原因十有八九是忘记 COMMIT,程序连接里的事务一直挂着。这个问题的隐蔽之处在于:当前会话里所有查询都能看到自己事务内的修改,看起来一切正常,只有事务外的连接能看到真实状态。
解决方法是养成“改完就提交”的习惯,尤其是通过编程语言连接数据库时,确认事务边界。排查时用SELECT * FROM information_schema.innodb_trx查看活跃事务,如果有长时间未提交的事务,先 COMMIT 或 ROLLBACK 再追查代码逻辑。在课程实践里,这个问题经常出现在最后演示环节,现场翻车很影响心态,早点把自动提交开着或显式提交,能省掉很多尴尬。
6. 收尾前必做:从能跑到能答辩的验证清单
6.1 对照实践要求自查:六个检查项
提交实践报告前,我一般按下面这个清单过一遍,每项对不上就回去补:
- 建库建表脚本能在干净环境下一次执行成功,不依赖人工干预
- 每张表的主键、外键、唯一约束、CHECK 约束用
SHOW CREATE TABLE验证 - 四个隔离级别各跑了一遍,脏读和不可重复读的观察结果有记录
- 至少三条 SQL 的
EXPLAIN执行计划贴进报告,并标明各自是否走索引 - 中文数据从插入到查询全部无乱码
- 每个事务都明确提交或回滚,没有遗留的未提交事务
这六项覆盖了数据库原理实践的核心评分点:完整性约束、事务、并发、索引、字符集。任何一项缺失,答辩时被问到对应知识点都会很被动。
6.2 把普通实验做成亮点的两个方向
第一个方向是给索引实验加一张真实数据量的对比表。把样例数据用存储过程扩到十万行,对比同一查询在有索引和没有索引时的耗时差异,用SHOW PROFILE或客户端计时截图当证据。这个操作不复杂,但能直观展示索引对查询性能的影响,比单纯贴 EXPLAIN 输出更有说服力。
第二个方向是做锁等待演示。两个终端同时对同一行做 UPDATE,第一个事务不提交,第二个事务会进入锁等待状态,用SHOW ENGINE INNODB STATUS抓一段锁信息放进报告。这能让事务章节从“背概念”升级成“演示并发控制”,也是答辩时最容易被认可的实验深度。
我自己做这类实践课的习惯是:宁可少做一个实验点,也要把已做的实验点做到“能讲清楚原理 + 有证据截图 + 能回答追问”三层。一套实践做下来,收获最大的不是那几个 SQL 语句,而是数据一致性和约束意识,这在之后写业务代码时价值很高。希望帮到你。
本文还有配套的精品资源,点击获取