简介这是一份南华大学《数据库原理》课程的实验报告合集面向数据库初学者和正在完成相关实验的大学生。报告以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) ) ENGINEInnoDB; CREATE TABLE course ( cno CHAR(6) PRIMARY KEY, cname VARCHAR(30) NOT NULL, credit DECIMAL(3,1) DEFAULT 2.0 ) ENGINEInnoDB; 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) ) ENGINEInnoDB;建库语句里最值得解释的是utf8mb4而不是utf8。MySQL的utf8实际最多只能存3字节像emoji和生僻字会直接报错或变成问号实验报告里出现“中文乱码”大概率就是这里选错了。unicode_ci排序规则让查询不区分大小写符合课程里的常规预期。三张表的字段设计要点学生表用CHAR(10)存学号因为学号固定长度用VARCHAR反而浪费存储和索引空间选课表把sno和cno做成联合主键天然防止同一学生重复选同一门课。外键不是摆设后边做“删除被引用学生”的实验时它能替你验证参照完整性约束是不是真在起作用。ENGINEInnoDB必须显式指定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(*) 1MySQL会直接报“无效使用组函数”这个报错本身就是实验报告里可以写的好素材。3. 约束、索引与视图把概念实验做成能验证的SQL3.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说明没走任何索引。第二次查询如果看到typeref、keyidx_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_userlocalhost IDENTIFIED BY Exp123456; GRANT SELECT ON db_exp_2024.v_stu_course_grade TO exp_userlocalhost; REVOKE SELECT ON db_exp_2024.student FROM exp_userlocalhost;逻辑说明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 sno2024000001 AND cnoCS103; -- 会话A能看到这条数据 -- 会话B SELECT * FROM sc WHERE sno2024000001 AND cnoCS103; -- 会话B看不到说明未提交数据对其他事务不可见 -- 会话A执行 ROLLBACK; SELECT * FROM sc WHERE sno2024000001 AND cnoCS103; -- 数据消失证明原子性隔离级别的切换也是必做实验。默认是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 sno2024000001 AND cnoCS101; -- 会话B START TRANSACTION; UPDATE sc SET grade 80.0 WHERE sno2024000003 AND cnoCS102; -- 此时两边各自持有一行锁 -- 会话A继续 UPDATE sc SET grade 70.0 WHERE sno2024000003 AND cnoCS102; -- 会话A在等会话B释放该行锁 -- 会话B继续 UPDATE sc SET grade 60.0 WHERE sno2024000001 AND cnoCS101; -- 死锁发生两个会话互相等待 -- 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语句在默认autocommit1下是逐条提交的报错行之前的已经落库之后的没执行。如果是手动用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连接而代码运行时可能走了远程IPMySQL用户表里只授权了userlocalhost。另一个常见原因是服务端bind-address限制了监听地址。解决新建一个允许任意主机访问的账号并单独授权CREATE USER exp_app% IDENTIFIED BY App123456; 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用下面语句转成InnoDBALTER TABLE student ENGINEInnoDB;同时记住一个原则事务里边不要混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 sno2024000001 AND cnoCS102;这个场景把数据库并发锁等待和死锁的原理串到了一起锁会等待等待可能超时超时后事务自动回滚。实验报告里把这条记录的完整链路写出来就是我反复说的“从翻车到排查”的完整素材。6. 实验报告写法把排查过程变成加分项的进阶技巧报告结构上我建议放弃“步骤-截图-结果”这种流水账换成“实验目的-环境基线-核心操作-破坏性验证-问题分析-结论”的框架。环境基线放在最前面把MySQL版本、字符集、端口、存储引擎写清楚后边所有操作都基于这个基线展开。排序上“问题分析”和“破坏性验证”放在“核心操作”之后因为这两部分才是实验报告里老师最想看的“思考痕迹”。验证手段上除了截图要主动贴三类证据一是EXPLAIN的输出文本二是SHOW ENGINE INNODB STATUS里的死锁片段三是报错信息的原文。报错原文比你自己写的“报错了”三个字有说服力得多。我自己的习惯是每次实验建一个文本文件把终端里所有报错连同执行时间一起追加进去报告写到最后直接从里面挑素材比做完了才回忆高效得多。报告里的代码块要保证“复制过去能跑通”尽量不要直接贴那种带...省略...的伪语句。如果实验环境的数据量太小导致索引不生效不要硬改数据直接把EXPLAIN结果贴出来写一句“当前数据量下优化器选择全表扫描”这个解释本身就是对优化器行为的一次合格验证。结尾处我想分享一个带了很多届学生以后形成的习惯我写实验报告时最后总要留一节叫“本次实验的教训”里面写满像是“DDL 不能和事务混在一起”“MyISAM 不支持外键”“连接字符集和库表字符集要同步确认”这类话。这些东西一开始都是从报错里抠出来的但存得多了就成了自己的知识库。你现在把每一步排查过程记录下来以后面试聊到数据库这些都是现成的真实案例。希望帮到你。本文还有配套的精品资源点击获取