简介:这是一份南华大学《数据库原理》课程的实验报告合集,面向数据库初学者和正在完成相关实验的大学生。报告以SQL Server Management Studio为工具,完整呈现认识DBMS、创建数据库和数据表、定义主外码与默认值、构建表间关系,以及执行单表查询、多表连接查询、数据更新等实验内容,每个实验均包含题目、要求、SQL代码与运行结果。压缩包内仅有1个doc文档,大小约5.38MB,结构按实验一至四及总结排列,覆盖学生选课数据库的完整操作,例如查询姓李的学生、计算间接先行课、按平均成绩降序排列等典型SQL场景。目前已有375人学习下载,对需要参考数据库原理实验报告写法、巩固SQL查询基础或准备课程考核的同学具有实用价值。
1. 数据库原理实验报告,真正的分水岭不在SQL,而在排查过程
很多人在接到“南华大学--数据库原理实验报告”这个题目时,第一反应是找一套可运行的SQL,把建表、插入、查询跑一遍,再截几张图贴上去。结果报告交上去,自己心里都发虚——老师随口问一句“这个查询为什么走全表扫描”“这个死锁是怎么复现的”,当场就卡住。这类实验报告真正想考察的,不是你会不会敲CREATE TABLE,而是你面对一个会报错、会锁表、会乱码、会丢数据的数据库时,有没有一套能定位问题、记录问题、讲清问题的流程。
能拿高分、也值得写进简历的报告,往往不是一路绿灯的报告。相反,它记录的是你怎么翻车、怎么用SHOW ENGINE INNODB STATUS找死锁、怎么把“数据没了”“连不上了”“中文乱码了”这类现场还原成文字。这套能力才是数据库原理实验最有价值的部分,也是今天这篇笔记要帮你完整走一遍的东西:从环境选型到建表约束,从索引验证到并发死锁,最后收在实验报告的写法上。适合正在做数据库原理实验或课程设计的人,也适合想把自己踩过的坑整理成作品集的新手。
2. 实验环境与建表:把增删改查跑通,才算迈过第一道坎
2.1 数据库选型与安装验证:先定版本,再定字符集
数据库原理实验的环境选择,常见做法是跟着课程指定走,没指定就用MySQL。原因很简单:资料多、出错好搜、自带INFORMATION_SCHEMA和EXPLAIN,能把原理课上的概念直接做成可见的输出。SQL Server和Oracle也能做,但对新手不友好;SQLite虽然轻量,却没法完整体验事务隔离级别和锁冲突这些重头戏。如果学校用了达梦或人大金仓这类国产数据库,底层思路和MySQL一致,SQL语法也高度兼容,本文的步骤照搬即可。
安装这一步最关键的坑在字符集和端口。很多实验室机器上已经装了MySQL 8.x,版本不同默认配置差异很大,建议先验证环境是否健康。安装完成或拿到现成环境后,第一步永远是确认版本、服务状态和客户端是否能连上:
mysql --version systemctl status mysql # 或 service mysql status mysql -u root -p登录后立刻执行下面两条,把基础信息记录下来,后边排查全靠它们:
SHOW VARIABLES LIKE 'character_set_server'; SHOW VARIABLES LIKE 'port';这里的逻辑是:mysql --version确认客户端版本,systemctl status确认服务进程活着,最后一条SQL确认服务监听端口。很多“Navicat连不上”的问题,就出在端口不是默认3306,或者服务只监听了127.0.0.1。先把基线记好,后边出问题才有对照。
2.2 建库建表:把三张经典表一次建对
数据库原理实验最稳妥的载体是“学生-课程-选课”三张表。它覆盖了主键、外键、唯一约束、默认值、CHECK约束这些课程考点,也方便后边做视图、索引和事务实验。建库时我一般会把库名带上学号或实验序号,避免多台机器共用同一个MySQL实例时互相踩表。
CREATE DATABASE IF NOT EXISTS db_exp_2024 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE db_exp_2024; CREATE TABLE student ( sno CHAR(10) PRIMARY KEY, sname VARCHAR(20) NOT NULL, sex CHAR(1) DEFAULT 'M', age TINYINT CHECK (age BETWEEN 14 AND 40), dept VARCHAR(30) ) ENGINE=InnoDB; CREATE TABLE course ( cno CHAR(6) PRIMARY KEY, cname VARCHAR(30) NOT NULL, credit DECIMAL(3,1) DEFAULT 2.0 ) ENGINE=InnoDB; CREATE TABLE sc ( sno CHAR(10), cno CHAR(6), grade DECIMAL(4,1), PRIMARY KEY (sno, cno), FOREIGN KEY (sno) REFERENCES student(sno), FOREIGN KEY (cno) REFERENCES course(cno) ) ENGINE=InnoDB;建库语句里最值得解释的是utf8mb4而不是utf8。MySQL的utf8实际最多只能存3字节,像emoji和生僻字会直接报错或变成问号,实验报告里出现“中文乱码”大概率就是这里选错了。unicode_ci排序规则让查询不区分大小写,符合课程里的常规预期。
三张表的字段设计要点:学生表用CHAR(10)存学号,因为学号固定长度,用VARCHAR反而浪费存储和索引空间;选课表把sno和cno做成联合主键,天然防止同一学生重复选同一门课。外键不是摆设,后边做“删除被引用学生”的实验时,它能替你验证参照完整性约束是不是真在起作用。ENGINE=InnoDB必须显式指定,MyISAM不支持事务和外键,很多“回滚无效”的翻车现场就是默认引擎不是InnoDB导致的。
2.3 增删改查的标准动作与统计查询写法
建完表就得灌数据。实验报告里最基础的一块就是增删改查,热搜词里也常年挂着“数据库增删改查”和“mysql数据库常用命令”。插入数据时要注意顺序:先插学生和课程,再插选课,否则外键检查不通过。这里给一组能直接用的示例数据:
INSERT INTO student (sno, sname, sex, age, dept) VALUES ('2024000001', '张三', 'M', 20, '计算机学院'), ('2024000002', '李四', 'F', 21, '软件学院'), ('2024000003', '王五', 'M', 19, '计算机学院'); INSERT INTO course (cno, cname, credit) VALUES ('CS101', '数据库原理', 3.0), ('CS102', '操作系统', 3.5), ('CS103', '数据结构', 4.0); INSERT INTO sc (sno, cno, grade) VALUES ('2024000001', 'CS101', 88.5), ('2024000002', 'CS101', 91.0), ('2024000003', 'CS102', 76.0);插入后的常规查询实验,我建议每组SQL都带着注释和结果影响分析写进实验报告,例如统计每个学院的平均年龄、查看选课人数超过2人的课程。这类SQL能展示你掌握了分组和聚合,比单纯SELECT *有说服力得多:
SELECT dept, AVG(age) AS avg_age FROM student GROUP BY dept HAVING AVG(age) > 0; SELECT cno, COUNT(*) AS cnt FROM sc GROUP BY cno HAVING cnt >= 1;HAVING的写法是新手最容易踩坑的地方:WHERE在分组前过滤,HAVING在分组后过滤,两者不能互换。如果改成WHERE COUNT(*) >= 1,MySQL会直接报“无效使用组函数”,这个报错本身就是实验报告里可以写的好素材。
3. 约束、索引与视图:把概念实验做成能验证的SQL
3.1 完整性约束:主键、外键、CHECK要在“破坏”中证明它有效
完整性约束这部分,很多实验报告只写了“建表语句里有PRIMARY KEY”,这不够。约束的价值要在违反它的时候才体现出来,实验报告应该记录“我尝试插入一条重复主键,结果报错,这说明主键约束生效了”。这种破坏性验证比空谈概念更有说服力,也更容易让老师相信你真的跑过实验。
-- 故意违反主键约束:重复学号 INSERT INTO student (sno, sname, sex, age, dept) VALUES ('2024000001', '赵六', 'M', 22, '计算机学院'); -- 预期报错:Duplicate entry '2024000001' for key 'PRIMARY' -- 故意违反外键约束:插入不存在的课程到选课表 INSERT INTO sc (sno, cno, grade) VALUES ('2024000001', 'XX999', 80.0); -- 预期报错:Cannot add or update a child row: a foreign key constraint fails参数说明:外键约束默认是RESTRICT,也就是子表插入或更新时,父表没有对应主键就直接拒绝。如果想换行为,可以在建表时指定ON DELETE CASCADE或ON DELETE SET NULL,但做实验时就用默认值,能清楚看到约束在工作。CHECK约束在MySQL 8.0.16之前只是“语法上存在但不验证”,如果实验环境是旧版本,CHECK会悄无声息被忽略,这一点要在报告里写明你的版本,否则老师一问你“CHECK到底生效没”就露馅了。
3.2 索引与查询优化:用EXPLAIN确认索引真的被用上
索引实验的正确写法不是“我建了索引所以查询变快了”,而是“我建索引前后分别用EXPLAIN对比了访问类型,从ALL变成了ref”。这才是能把“数据库原理”和“实际操作”挂钩的证据。下面演示一个典型的索引验证流程:
-- 1. 无索引时,查看查询计划 EXPLAIN SELECT * FROM sc WHERE sno = '2024000001'; -- 2. 给选课表的 sno 列建普通索引 CREATE INDEX idx_sc_sno ON sc(sno); -- 3. 再跑一次 EXPLAIN,对比 type 和 key EXPLAIN SELECT * FROM sc WHERE sno = '2024000001';第一次EXPLAIN的输出里,type字段通常是ALL,表示全表扫描;key列为NULL,说明没走任何索引。第二次查询如果看到type=ref、key=idx_sc_sno、rows明显变小,就说明索引生效了。这里有一个很多新手看不懂的点:选课表已经有联合主键(sno, cno),为什么还要给sno单独建索引?因为联合主键的索引最左前缀是sno,理论上单独查sno也能走这个联合索引。但实验里单独建索引的目的,是演示“创建索引”这个操作本身对查询计划的影响,所以保留两套索引反而让对比更清楚。
常见的错误是加完索引后用SELECT *查全表,数据量只有几十行时MySQL优化器会认为全表扫描更快,干脆不理索引。做索引实验时数据量最好在万行以上,达不到就用EXPLAIN配合FORCE INDEX观察,或者干脆说明“数据量太小,优化器选择全表扫描”这个现象本身。
3.3 视图与权限:为什么视图能当安全层用
视图实验的本质不在于“视图是一条命名的SELECT语句”,而在于它屏蔽了底层表结构。来看一个具体场景:把“学生姓名、课程名、成绩”做成视图,只把这个视图授权给低权限用户,不让它直接碰student或course表。
CREATE VIEW v_stu_course_grade AS SELECT s.sname, c.cname, sc.grade FROM student s JOIN sc ON s.sno = sc.sno JOIN course c ON c.cno = sc.cno;建完视图后接着做权限实验:
CREATE USER 'exp_user'@'localhost' IDENTIFIED BY 'Exp@123456'; GRANT SELECT ON db_exp_2024.v_stu_course_grade TO 'exp_user'@'localhost'; REVOKE SELECT ON db_exp_2024.student FROM 'exp_user'@'localhost';逻辑说明:GRANT SELECT ON 视图表示只给这个用户查视图的权限,不给底层表的权限。这样当它登录后,虽然能看到视图里的数据,但直接SELECT * FROM student会被拒绝。这一步把“视图是安全层”从概念变成了可验证的行为,是实验报告里很值钱的一段记录。
4. 事务、并发锁与死锁:实验里的“高压”
4.1 ACID验证动作:提交、回滚与隔离级别查看
事务实验要做出“能看见”的效果。最经典的演示是开启事务后插入一条数据,在当前会话能查到,另一个会话查不到,然后ROLLBACK,当前会话也查不到了。这同时覆盖了隔离性和原子性两个概念。我在做实验时会专门开两个MySQL客户端窗口,一个写、一个读,这种对比截图比任何解释都直观。
-- 会话A START TRANSACTION; INSERT INTO sc(sno, cno, grade) VALUES('2024000001', 'CS103', 85.0); SELECT * FROM sc WHERE sno='2024000001' AND cno='CS103'; -- 会话A能看到这条数据 -- 会话B SELECT * FROM sc WHERE sno='2024000001' AND cno='CS103'; -- 会话B看不到,说明未提交数据对其他事务不可见 -- 会话A执行 ROLLBACK; SELECT * FROM sc WHERE sno='2024000001' AND cno='CS103'; -- 数据消失,证明原子性隔离级别的切换也是必做实验。默认是REPEATABLE READ,改成READ COMMITTED后再重复上面的步骤,会发现会话B能看到未提交的数据——这就是脏读的边界场景。修改隔离级别的SQL如下:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT @@transaction_isolation;@@transaction_isolation是MySQL 8.0里的系统变量名,老版本是@@tx_isolation。写实验报告时要把当前版本对应的变量名写对,这是个很容易被忽视的细节。
4.2 复现死锁:两个会话互相等锁的写法
数据库并发锁和死锁是实验报告最容易写薄的部分。很多同学只写“死锁是什么”,没写“怎么复现”。实际上死锁复现非常简单:两个事务各自锁住一行,然后互相申请对方那行的排他锁。下面这个流程我用过很多次,稳定能触发死锁。
-- 会话A START TRANSACTION; UPDATE sc SET grade = 90.0 WHERE sno='2024000001' AND cno='CS101'; -- 会话B START TRANSACTION; UPDATE sc SET grade = 80.0 WHERE sno='2024000003' AND cno='CS102'; -- 此时两边各自持有一行锁 -- 会话A继续 UPDATE sc SET grade = 70.0 WHERE sno='2024000003' AND cno='CS102'; -- 会话A在等会话B释放该行锁 -- 会话B继续 UPDATE sc SET grade = 60.0 WHERE sno='2024000001' AND cno='CS101'; -- 死锁发生:两个会话互相等待 -- MySQL会很快检测到并回滚其中一个事务,报错: -- Deadlock found when trying to get lock; try restarting transaction参数说明:MySQL的死锁检测依赖innodb_lock_wait_timeout和innodb_deadlock_detect两个参数。后者默认ON,所以死锁会在毫秒级被检测到并回滚其中一个事务。如果你把死锁检测关了,两个事务会一直等到锁超时,那就是另一种“卡死”现象,也值得记录。
4.3 死锁后的排查手段:SHOW ENGINE INNODB STATUS
死锁复现出来只是第一步,“会排查”才是实验报告的加分点。MySQL提供了专门的死锁信息查看命令,执行以下SQL可以看到最近一次死锁的详细事务和锁等待信息:
SHOW ENGINE INNODB STATUS\G在这段输出里,重点找LATEST DETECTED DEADLOCK段落,它会列出两个事务各自的WAITING FOR THIS LOCK TO BE GRANTED和HOLDS THE LOCK信息。把这个输出原样贴进实验报告,再配两行解释,比任何教材定义都直观。
另外一个实用小技巧是调小锁等待超时时间后主动触发超时错误。实际业务里死锁的最佳处理不是“避免”,而是“快速失败+重试”,因为并发场景下死锁无法完全避免。实验报告里如果能写出这句判断,说明你对数据库并发锁的理解已经超出了动手层面,达到了原理层面。
5. 排查实验问题:现象、原因、解决的5条记录
5.1 中文乱码:数据变成了“??”或“????”
现象:INSERT执行成功,SELECT一看全成了问号,或者英文正常中文全乱。可能是SSMS、命令行还是第三方客户端都有可能出现,MySQL里最常见。
原因:数据库、表、连接三个环节字符集不一致。最常见的是库表用了utf8,而客户端连接用latin1,或者建库时没指定字符集,继承了服务器端的latin1。
解决:先看当前连接字符集用SHOW VARIABLES LIKE 'character_set_connection';,然后在连接后立刻执行SET NAMES utf8mb4;。如果建库建表时已经错了,修改表字符集用:
ALTER DATABASE db_exp_2024 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE student CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;注意语法是CONVERT TO而不是MODIFY,前者会转换列的数据类型附带字符集,后者只改表默认值,已存在的字段可能没变。这个“改了还是乱”的坑,我踩过不止一次。我一般建议落地时先在建库阶段就指定好,别依赖侥幸。
5.2 批量导入失败:唯一约束报错导致事务整体回滚
现象:用Navicat或用脚本一次性导入几千条数据,跑到第几百条时报Duplicate entry错误,然后前面的数据也没了,或者后面的一直失败。
原因:多条INSERT语句在默认autocommit=1下是逐条提交的,报错行之前的已经落库,之后的没执行。如果是手动用START TRANSACTION包起来的,报错后整批回滚。
解决:先清理重复数据再导入,或者利用MySQL的“存在即更新”语义,用如下SQL合并重复:
INSERT INTO student (sno, sname, sex, age, dept) VALUES ('2024000001', '张三', 'M', 20, '计算机学院') ON DUPLICATE KEY UPDATE sname = VALUES(sname), age = VALUES(age);参数说明:ON DUPLICATE KEY UPDATE根据主键或唯一索引判断是否冲突。这个写法在实验报告里写清楚适用场景就好——它只解决“重复时怎么处理”,不解决“为什么会有重复”,后者要回到数据清洗去查。批量导入时我更建议用事务包裹并先做去重查询,而不是盲目依赖这个语法。
5.3 Navicat能连、代码连不上:权限host和端口没对上
现象:同一个MySQL,图形客户端连得好好的,用Java/Python连就报Access denied或Communications link failure。
原因:图形客户端通常在本地、用root连接,而代码运行时可能走了远程IP,MySQL用户表里只授权了'user'@'localhost'。另一个常见原因是服务端bind-address限制了监听地址。
解决:新建一个允许任意主机访问的账号,并单独授权:
CREATE USER 'exp_app'@'%' IDENTIFIED BY 'App@123456'; GRANT ALL PRIVILEGES ON db_exp_2024.* TO 'exp_app'@'%'; FLUSH PRIVILEGES;'%'表示不限制来源IP。这命令在本地实验没问题,但在生产环境千万别这么干,会造成任何人都可以尝试密码。实验环境里建议至少限制成'192.168.%'这种网段格式,这个细节写进报告会让老师觉得你考虑过安全问题。
5.4 ROLLBACK不回滚:DDL隐式提交和MyISAM的陷阱
现象:事务里先UPDATE再DELETE,执行ROLLBACK后发现数据还是变了,或者报错“表不支持事务”。
原因:一是ALTER TABLE、CREATE INDEX、DROP TABLE这些DDL语句会隐式提交当前事务,事务边界被提前截断;二是表引擎是MyISAM,根本不支持事务。
解决:写实验报告前先用这条SQL确认引擎:
SELECT table_name, engine FROM information_schema.tables WHERE table_schema = 'db_exp_2024';如果发现不是InnoDB,用下面语句转成InnoDB:
ALTER TABLE student ENGINE=InnoDB;同时记住一个原则:事务里边不要混DDL操作。MySQL的隐式提交是很多“事务失效”实验翻车的源头,这个问题在实验报告里写透,比单纯记录“我回滚成功了”有价值得多。
5.5 UPDATE卡住:锁等待与全表扫描
现象:执行一条UPDATE sc SET grade = 70 WHERE sno = '2024...',一直卡住不动,等几十秒后报Lock wait timeout exceeded。
原因:目标行被另一个事务锁住,最典型的是另一个会话没COMMIT也没ROLLBACK就关了窗口,锁一直没释放。另一个原因是WHERE条件没走索引,导致锁了多行甚至全表。
解决:在UPDATE前先EXPLAIN查询计划,确认sno能走索引;再用SHOW PROCESSLIST;看有没有长时间挂起的事务,找到后用KILL <id>把它杀掉。再改小锁等待时间方便实验室快速复现:
SET SESSION innodb_lock_wait_timeout = 2; UPDATE sc SET grade = 70 WHERE sno='2024000001' AND cno='CS102';这个场景把数据库并发锁等待和死锁的原理串到了一起:锁会等待,等待可能超时,超时后事务自动回滚。实验报告里把这条记录的完整链路写出来,就是我反复说的“从翻车到排查”的完整素材。
6. 实验报告写法:把排查过程变成加分项的进阶技巧
报告结构上我建议放弃“步骤-截图-结果”这种流水账,换成“实验目的-环境基线-核心操作-破坏性验证-问题分析-结论”的框架。环境基线放在最前面,把MySQL版本、字符集、端口、存储引擎写清楚,后边所有操作都基于这个基线展开。排序上“问题分析”和“破坏性验证”放在“核心操作”之后,因为这两部分才是实验报告里老师最想看的“思考痕迹”。
验证手段上,除了截图,要主动贴三类证据:一是EXPLAIN的输出文本,二是SHOW ENGINE INNODB STATUS里的死锁片段,三是报错信息的原文。报错原文比你自己写的“报错了”三个字有说服力得多。我自己的习惯是每次实验建一个文本文件,把终端里所有报错连同执行时间一起追加进去,报告写到最后直接从里面挑素材,比做完了才回忆高效得多。
报告里的代码块要保证“复制过去能跑通”,尽量不要直接贴那种带...省略...的伪语句。如果实验环境的数据量太小导致索引不生效,不要硬改数据,直接把EXPLAIN结果贴出来,写一句“当前数据量下优化器选择全表扫描”,这个解释本身就是对优化器行为的一次合格验证。
结尾处我想分享一个带了很多届学生以后形成的习惯:我写实验报告时最后总要留一节叫“本次实验的教训”,里面写满像是“DDL 不能和事务混在一起”“MyISAM 不支持外键”“连接字符集和库表字符集要同步确认”这类话。这些东西一开始都是从报错里抠出来的,但存得多了,就成了自己的知识库。你现在把每一步排查过程记录下来,以后面试聊到数据库,这些都是现成的真实案例。希望帮到你。
本文还有配套的精品资源,点击获取