☰
MySQL数据库实验全攻略:从环境搭建到事务并发避坑
2026/10/12 18:58:32 网站建设 项目流程

简介:崔巍编著的《数据库课后实验》是一套面向数据库初学者的实践教学资源,适合配合《数据库原理》课程进行课后巩固与上机练习。压缩包体积仅5KB,共包含5个sql脚本文件,对应多个递进式实验任务,可直接在SQL Server等环境中运行调试。实验内容全面覆盖SQL语言基础与高级查询、表创建与数据操作、JOIN多表连接、GROUP BY分组统计、数据库范式设计与ER模型转换、事务ACID属性及并发控制、备份与恢复策略,以及索引优化等关键知识点。每个脚本都针对具体问题设计,便于读者对照课堂理论逐步拆解。目前已有220人学习,适合需要借助现成实验代码快速理解数据库原理、提升SQL编写与排错能力的高校学生。

1. 数据库课后实验(崔巍编著)是什么:不是背 SQL,是练边界

数据库实验课最常见的翻车现场,不是 SQL 语法没背熟,而是环境没起来、UPDATE 少了 WHERE 条件把整张表改废、两个窗口同时改同一行数据直接卡死。崔巍老师编著的这套课后实验,正好把数据库课程里最容易被背题掩盖的环节全拉到了命令行面前,从建库建表、增删改查,到视图、索引、存储过程和事务并发,一题一题逼你动手。它适合三类人:上课听懂了但一敲命令就出错的在校生,准备数据库课程设计和复试前想补手感的同学,以及刚接手实验课、想快速搭出可验收课题的助教。说白了,实验报告交上去不是终点,重点是你敢不敢把数据库当工具用。

2. 先让环境跑起来:MySQL 安装、配置与建库脚本

2.1 为什么用 MySQL 8.x 档住这套实验

崔巍老师这套实验册里的题目,语法主体是标准 SQL,所以在选数据库引擎时,没必要在 Oracle、SQL Server、MySQL 之间反复纠结。我一般建议直接走 MySQL 8.x,理由有三个。第一,安装包和命令行工具都透明,实验报告里每一步操作都能复现,不像 Oracle 那样要处理一堆服务账号;第二,MySQL 8 对标准 SQL 的兼容度比 5.7 时代好很多,窗口函数、CTE、CHECK 约束都能跑,后面做数据库课程设计或者转去接触人大金仓、达梦这些国产数据库,语法迁移成本都很低;第三,实习面试里被问得最多的也是 MySQL。

版本选择上,我建议走 LTS 线,比如 8.4 LTS,而不是追最新大版本。实验册里的 SQL 用不到新版本的功能,LTS 的默认行为更稳定的意思是,你在网上搜到的排错经验基本都能对上,不太会遇到"同样的 SQL 在别人机器能跑、在你机器报错"的玄学问题。还有一个关键决定:用 msi 安装包还是 zip 压缩包。我自己偏好 zip,因为它本质就是解压出一个目录和几个可执行文件,不注册一堆 Windows 服务,也不会在卸载时留一堆残留。想在一台实验机上反复折腾多套环境,zip 方式还能让不同版本共存,数据目录互相独立。

2.2 下载解压到初始化:最小命令序列

从官网下载 zip 包之后,解压到 D:\mysql-8.4 这种纯英文路径。不要放在带中文、带空格的目录里,否则后面 mysqld 启动时会出现路径解析问题。解压完成后,先不要急着一键启动,建议在解压目录根目录(和 bin 同级)建一个 my.ini:

[mysqld] basedir=D:/mysql-8.4 datadir=D:/mysql-8.4/data port=3306 character-set-server=utf8mb4

路径里的斜杠建议用正向斜杠,避免 Windows 下反斜杠被当成转义字符。datadir 是数据目录位置,提前指定可以防止 MySQL 把数据塞到系统盘默认路径,后面做备份和清理都麻烦。然后进入 bin 目录执行初始化:

# 解压 zip 包后进入 bin 目录;--initialize 生成 data 目录和临时 root 密码 mysqld --initialize --console # 前台启动,方便看启动日志;确认没问题再 Ctrl+C 停掉 mysqld --console

--initialize是 MySQL 8 首次运行前必须执行的步骤,它会创建系统库、初始化权限表、生成一个随机的 root 临时密码打印在控制台。如果当时没截屏导致密码丢了,把 data 目录整个删掉,重新初始化一次就行,反正还没有业务数据。mysqld --console前台启动能直接看到日志里的错误,常见三类:my.ini 路径不对、data 目录权限不对、3306 端口被占用。这些错误在前台日志里看得清清楚楚,一旦注册成服务再排查,就得翻半天事件日志。

确认启动无报错之后,再注册成服务。管理员终端执行mysqld --install,然后net start mysql启动。不想用了就net stop mysql加mysqld --remove。实验机不比生产环境,服务数量越少,出幺蛾子的概率越低。

2.3 改密码、字符集和专用实验账号

初始化完成后的 root 账号只能本机登录,密码是随机生成的。第一件事登录并改掉它:

mysql -uroot -p

登录后执行:

ALTER USER 'root'@'localhost' IDENTIFIED BY '换成你自己的密码';

不要设空密码,也别让 root 远程登录。实验课和课程设计共用机器时,root 权限太大,误操作一下整个实例都受影响。我一般建一个专用实验账号,权限只给实验库:

CREATE USER 'lab'@'localhost' IDENTIFIED BY 'lab123456'; GRANT ALL PRIVILEGES ON db_lab.* TO 'lab'@'localhost'; FLUSH PRIVILEGES;

平时用 Navicat 连接就用这个账号。Navicat 连不上 MySQL 8 常是两种情况:账号绑定的来源不是你能访问的主机,或者 MySQL 8 默认的 caching_sha2_password 认证插件跟旧版客户端不兼容。报错信息里直接出现caching_sha2_password字样的,优先升级客户端;不建议为了迁就插件把安全特性关掉。

字符集在 my.ini 里已经写了 utf8mb4,启动后可以确认一下:

SHOW VARIABLES LIKE 'character_set_server';

如果显示不是 utf8mb4,说明刚才的 my.ini 没被读到,回头检查文件位置和编码,不要只在会话里 SET GLOBAL,那玩意重启就失效。utf8 和 utf8mb4 的差别也值得在报告里提一句:utf8 在 MySQL 里最多三字节,emoji 和部分生僻字存不进去,utf8mb4 才是完整的四字节编码。

2.4 交作业前先跑通的建库脚本

实验册翻开第一题,几乎都是建库建表。学生、课程、选课是三张必出场的关系表。最小可交作业的脚本长这样:

CREATE DATABASE IF NOT EXISTS db_lab CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE db_lab; CREATE TABLE student ( sno VARCHAR(10) PRIMARY KEY, sname VARCHAR(20) NOT NULL, ssex VARCHAR(4), sage INT, sdept VARCHAR(20) ) ENGINE=InnoDB; CREATE TABLE course ( cno VARCHAR(10) PRIMARY KEY, cname VARCHAR(30) NOT NULL, cpno VARCHAR(10), ccredit INT ) ENGINE=InnoDB; CREATE TABLE sc ( sno VARCHAR(10), cno VARCHAR(10), grade INT, PRIMARY KEY (sno, cno), FOREIGN KEY (sno) REFERENCES student(sno), FOREIGN KEY (cno) REFERENCES course(cno) ) ENGINE=InnoDB;

学号我习惯用 VARCHAR,而不是自增 INT。原因是实验册里的数据是纸面直接给的,用字符串学号录入时不用先查自增值;而且学号不参与算术运算,字符串主键完全够用。三张表全部写ENGINE=InnoDB不是可选项,第 5 章做事务、行锁和死锁复现全依赖这个存储引擎;如果建表时写成 MyISAM,并发实验根本复现不出来。

把整段脚本存成 init_lab.sql,每次实验开始前执行一次:

mysql -u lab -p db_lab < init_lab.sql

这算是我做实验的固定习惯:动手前先恢复干净基线,改坏了重跑一遍就回到原点,比手工一条条改回去高效得多。到了数据库课程设计阶段,这个习惯能救命的频率远超你想象。

3. 实验一:把增删改查和表结构修改练到不假思索

3.1 INSERT / UPDATE / DELETE 的边界条件

增删改查是数据库实验的地基,但真正能区分"会写"和"敢写"的,是边界条件。INSERT 的边界是主键冲突、NOT NULL 约束、长度超限;UPDATE 的边界是 WHERE 条件有没有写全;DELETE 的边界是它能不能被后悔。实验里最常见的翻车现场,是兴致勃勃地执行了一句UPDATE sc SET grade = grade + 5;,然后发现整张表所有成绩都被改了。想避免这种事故,最稳妥的做法是把所有高风险操作包在事务里:

START TRANSACTION; UPDATE sc SET grade = grade + 5 WHERE sno = '001'; -- 执行完之后先查一下影响范围 SELECT * FROM sc WHERE sno = '001'; -- 确认没问题再提交,有异常直接回滚 COMMIT; -- ROLLBACK;

事务的作用是给操作留一道后悔药。实验报告里经常要求展示"修改前、修改后",用事务把两步截图拼在一起,还能顺带说明 COMMIT 和 ROLLBACK 的作用。UPDATE 和 DELETE 还有一个通用习惯:先用 SELECT 顶着同一个 WHERE 条件看影响行数,再执行写操作。比如要删sdept='历史系'的学生,先SELECT COUNT(*) FROM student WHERE sdept='历史系',看到数字心里有底了再删。

INSERT 的边界则在约束上。向已有数据的表插入重复主键,MySQL 会报Duplicate entry;插入 NOT NULL 列时漏掉值,会报Field 'sname' doesn't have a default value。这些报错不是坏事,实验报告里写清楚"为什么报错、怎么解决",比单纯贴成功结果得分高。注意一张表如果有自增列,删除数据后自增值不会回退,实验里想清理数据重建现场,用 TRUNCATE TABLE 而不是 DELETE,后者不会重置自增计数;有外键时 TRUNCATE 会失败,要先处理子表。

3.2 用 ALTER TABLE 修改结构:四个高频操作

数据库增删改查里最容易让人不设防的,是修改表结构。语法本身不难,难的是搞清楚 MODIFY、CHANGE、ADD 之间的差别。实验里常要求的四个操作是:加列、改列类型、改列名、删列。

-- 1. 增加列 ALTER TABLE student ADD COLUMN sphone VARCHAR(15); -- 2. 修改列类型/约束 ALTER TABLE student MODIFY COLUMN sage TINYINT UNSIGNED; -- 3. 修改列名(注意这里必须用 CHANGE) ALTER TABLE student CHANGE COLUMN sphone phone VARCHAR(15); -- 4. 删除列 ALTER TABLE student DROP COLUMN phone;

我的记忆方法是:只改类型和约束用 MODIFY,要改名必须用 CHANGE,原名和新名都要写一遍。如果实验册要求"将 age 列改名为 new_age",写成MODIFY COLUMN age new_age会直接报错。另外加约束也是 ALTER TABLE 的高频考点:

ALTER TABLE student ADD CONSTRAINT uq_sname UNIQUE (sname); ALTER TABLE sc ADD CONSTRAINT fk_sc_sno FOREIGN KEY (sno) REFERENCES student(sno);

MySQL 8 里修改表结构时会重建表,数据量小的时候看不出影响,到了课程设计阶段,一张十万行的表加列可能要卡几十秒。实验里养成先备份再 ALTER 的习惯,后面做大作业少踩很多坑。修改完结构后用DESC student;或SHOW CREATE TABLE student;确认结果,比肉眼盯着报错信息猜要快。

3.3 连接查询、聚合与分组:交作业前反复核对

查询实验是课后实验里题量最大的一块,老师最喜欢在连接和分组上挖坑。三张表的连接查询建议先写 FROM,再写 JOIN 条件,最后 SELECT 列,不容易漏关联条件:

SELECT s.sdept, COUNT(DISTINCT s.sno) AS stu_cnt, AVG(sc.grade) AS avg_grade FROM student s LEFT JOIN sc ON s.sno = sc.sno GROUP BY s.sdept HAVING stu_cnt >= 2;

这里 LEFT JOIN 和 INNER JOIN 的区别是实验报告里必写的:INNER JOIN 只保留两表都有匹配的行,LEFT JOIN 会保留左表的所有行,右表没匹配上时字段为 NULL。聚合查询里还要注意 COUNT(DISTINCT) 和 COUNT() 的差别,如果学号在 student 表里唯一,问题不大;但一旦 JOIN 出重复行,COUNT() 会把重复算进去。AVG 函数会忽略 NULL,没有成绩的学生不会拉低平均分,这一点结论可以直接写进报告。

MySQL 8 默认的 sql_mode 里包含 ONLY_FULL_GROUP_BY,所以 SELECT 里的非聚合列必须出现在 GROUP BY 中。比如只 GROUP BY sdept,却 SELECT sname,会直接报错。这不是 MySQL 难为你,还是分组本身的要求。另一个高频坑是"查没有选课的学生",很多同学写WHERE sno NOT IN (SELECT sno FROM sc),当子查询结果里出现 NULL 时,NOT IN 会返回空结果。更稳的写法是:

SELECT * FROM student s LEFT JOIN sc ON s.sno = sc.sno WHERE sc.sno IS NULL;

这个"IS NULL 判断"是实验课上的经典翻车点,写进报告的排查记录里很加分。

3.4 验证实验结果的三个习惯

做完查询实验,别急着截图关终端。我一般用三个习惯收尾。第一,危险操作前先用 SELECT 顶着同一个 WHERE 看一眼,影响行数和预估值对不上就先别动手。第二,修改之后立刻用一个聚合查询核对总数,比如 UPDATE 后SELECT COUNT(*), SUM(grade),数字和预期一致才算成功。第三,每次实验结束把 SQL 脚本和结果截图按日期归档。做数据库课程设计时,这套归档习惯能让你在两周后还能定位到"哪一步把数据改坏了"。这三个习惯都不复杂,但能把实验从"玄学能跑"变成"可复现、可解释"。

4. 视图、索引和存储过程:三个高频实验题一次过

4.1 视图:创建、查询和更新限制

视图是实验册里第一个"看起来像表,但不是表"的东西。它的本质是一段保存好的 SELECT,查询视图时 MySQL 才去执行这段 SELECT。所以视图字段的语义取决于基表,基表数据变了,视图结果也跟着变。创建视图的语法和查询基本一样:

CREATE VIEW v_stu_grade AS SELECT s.sno, s.sname, c.cname, sc.grade FROM student s JOIN sc ON s.sno = sc.sno JOIN course c ON c.cno = sc.cno; SELECT * FROM v_stu_grade WHERE grade < 60;

这个视图把三张表连接的结果封装起来,后续要查挂科名单就不用重复写 JOIN。视图的最大坑在更新上:如果 SELECT 里包含 JOIN、GROUP BY、聚合函数或 DISTINCT,这个视图就不可更新。实验里很多同学对视图执行UPDATE v_stu_grade SET grade = 90然后收到not updatable报错,原因就在这。想演示"可更新的视图",用单表视图:

CREATE VIEW v_student_basic AS SELECT sno, sname, sdept FROM student; UPDATE v_student_basic SET sdept = '计算机系' WHERE sno = '001';

视图更新本质上更新的是基表,所以还要满足基表本身的约束。实验报告里建议把"可更新/不可更新"两种情况各放一个例子,对比着写,比只贴视图查询成功截图扎实得多。

4.2 索引:EXPLAIN 和实际耗时怎么判断

索引实验的重点不是会写 CREATE INDEX,而是会证明索引真的生效了。最直接的验证工具是 EXPLAIN。在 sc 表上加一个 cno 索引前后的对比:

-- 建索引 CREATE INDEX idx_sc_cno ON sc(cno); -- 查看是否走索引 EXPLAIN SELECT * FROM sc WHERE cno = 'C1'; -- 删除索引,再做一次对比 DROP INDEX idx_sc_cno ON sc;

EXPLAIN 输出的关键列是 type 和 key。type=ALL 是全表扫描,建索引后变成了 ref 或 const,key 列出现索引名,这才算索引生效。实验报告里如果只贴一张SHOW INDEX FROM sc的表,等于什么都没证明。我一般会把实验数据先灌到几千行,否则在几百行的表上全表扫描和索引查找的耗时差异肉眼看不出来,EXPLAIN 的扫描行数差异也不明显。

另一个值得写进报告的观察是:低区分度字段建索引效果差。比如 ssex 只有两个值,索引扫描还是得回表读大量行,优化器很可能直接选择全表扫描。把这些观察写进去,老师能看出你是真跑过而不是背结论。索引不是越多越好,每多一个索引,INSERT 和 UPDATE 就要多维护一棵 B+ 树,实验里可以故意建三个索引再对比写入耗时,结论很有说服力。

4.3 存储过程:循环、游标和异常处理

存储过程是很多同学第一次接触 SQL 里的控制结构。语法不难,难在环境配合。直接在 mysql 客户端里写过程体,会遇到"分号被截断"的困惑,所以要先改分隔符:

DELIMITER // CREATE PROCEDURE sp_mark_good_students() BEGIN DECLARE done INT DEFAULT 0; DECLARE v_sno VARCHAR(10); DECLARE v_total DECIMAL(8,2); DECLARE cur CURSOR FOR SELECT sno, SUM(grade) FROM sc GROUP BY sno; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; OPEN cur; loop_label: LOOP FETCH cur INTO v_sno, v_total; IF done = 1 THEN LEAVE loop_label; END IF; -- 业务处理:把总成绩达标的学生插到荣誉表 INSERT INTO honor_student(sno, total_grade) VALUES (v_sno, v_total); END LOOP; CLOSE cur; END // DELIMITER ;

DELIMITER 是必需步骤,它告诉 mysql 客户端别在过程体内部的分号处提前结束;做完再恢复成;,否则后面普通 SQL 全部不能正常执行。游标循环里最容易犯的错是忘记 FETCH 之后立刻判断 done,导致循环多跑一次或者取到空值。还有一个常见报错是变量名和列名重名,比如变量名叫 sname 同时表里也有 sname 列,会造成歧义,我习惯在变量名加 v_ 前缀避开。

调用存储过程用 CALL,带参数版本也一样。如果过程里出错,MySQL 默认把错误抛给客户端,实验里可以加 DECLARE EXIT HANDLER 做异常处理,但大多数课后实验只要能把游标循环跑通就够。项目结束前记得DROP PROCEDURE IF EXISTS清理重跑。

4.4 把三个实验打包成可复现的验收脚本

视图、索引、存储过程这三个实验分开做没问题,但交作业时被人逐个执行一遍很容易翻车,因为环境状态对不上。我习惯把三者打包进一个 sql 文件:先建视图,再建索引,再建存储过程,最后写一段验证 SELECT,执行顺序固定。执行方式:

mysql -u lab -p db_lab < lab_view_index_proc.sql

文件开头加一句DROP VIEW IF EXISTS v_stu_grade;和DROP PROCEDURE IF EXISTS sp_mark_good_students;保证脚本可重复执行。索引和存储过程不同,MySQL 对DROP INDEX不支持 IF EXISTS,所以重建索引前先执行SHOW INDEX FROM sc;确认对象存在,再按名字删除。这个样子很适合给同学同步:不用把整个数据库目录拷过去,一个 sql 文件就把视图、索引和存储过程全部带走,对方导入到本地就能跑。养成这个习惯之后,后面做数据库课程设计,环境迁移也从来不是问题。

5. 事务与并发实验避坑:锁等待、死锁和连接池

5.1 事务隔离级别在实验里怎么验证

事务这章最容易翻车,因为单开一个窗口根本验证不了并发行为。我一般会开两个 mysql 客户端窗口,一个执行操作,一个观察结果。先看当前隔离级别:

SELECT @@transaction_isolation;

MySQL 8 默认值是 REPEATABLE-READ。验证事务是否防脏读,可以这样做。窗口 A 开启事务更新一行但不提交:

START TRANSACTION; UPDATE sc SET grade = grade - 10 WHERE sno = '001'; -- 此时不 COMMIT

窗口 B 查询这一行,在默认隔离级别下看不到未提交的修改,说明当前隔离级别已经防住了脏读。想进一步区分 READ COMMITTED 和 REPEATABLE READ,可以在会话 B 里先执行一次 SELECT,让窗口 A 提交后再执行一次,看两次结果是否一致。REPEATABLE READ 下同一个事务内多次读结果一致,READ COMMITTED 下第二次读能看到新提交的数据。把两个窗口的截图并排贴进实验报告,比任何文字都有说服力。

需要提醒的是,隔离级别只是"读"的规则,单靠它防不住更新丢失。要防丢失更新,得靠锁机制。实验里如果发现并发叠加成绩总是少一次加法,大概率是应用层没有用事务加锁保护临界区,这不是 MySQL 配置能解决的。

5.2 死锁和锁等待:现象、日志与处理

数据库并发锁里最经典的现象是:窗口 A 更新了一行没提交,窗口 B 更新同一行就一直转圈。这不是程序卡死,是 B 在等 A 释放行锁。处理办法很简单:让 A 提交或回滚。但在实验里,这个"卡住"的过程恰恰是你要抓的证据。卡住的时候去另一个窗口查锁状态:

SHOW ENGINE INNODB STATUS\G

输出里 LATEST DETECTED DEADLOCK 段会列出涉及的事务、持有锁的表和行、等待的关系。死锁比单纯锁等待更麻烦,它不需要有人手动干预,InnoDB 会自动选一个事务回滚掉,另一个继续执行。所以死锁的本质是两个事务以相反的顺序申请锁,每一方都攥着对方想要的东西。

在实验里复现死锁也很简单,两个窗口分别执行顺序相反的更新:

-- 窗口 A 先执行 START TRANSACTION; UPDATE sc SET grade = grade + 1 WHERE sno = '001' AND cno = 'C1'; -- 不要提交,切到窗口 B -- 窗口 B 再执行 START TRANSACTION; UPDATE sc SET grade = grade + 1 WHERE sno = '002' AND cno = 'C2'; -- 不要提交,切回窗口 A

然后窗口 A 更新 sno='002' 的行,窗口 B 更新 sno='001' 的行,两边立即死锁,其中一个事务被 InnoDB 回滚。解决死锁的工程做法是统一全局加锁顺序:所有事务先更新学号小的,再更新学号大的。这个结论写在实验报告里,比单贴死锁日志更显水平。锁等待超时时间在 my.ini 里配innodb_lock_wait_timeout,默认 50 秒,实验时不想干等可以临时调小到 5 秒。

5.3 连接池参数:为什么连不上、为什么慢

有些实验册会把连接池放到选做,但数据库课程设计和实习面试都绕不开它。连接池本身不复杂,作用就是复用连接,避免每次 SQL 都走一次 TCP 握手和身份认证。用 HikariCP 的最小配置我一般这么写:

HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:mysql://localhost:3306/db_lab?serverTimezone=Asia/Shanghai"); config.setUsername("lab"); config.setPassword("lab123456"); config.setMaximumPoolSize(10); config.setMinimumIdle(2); config.setConnectionTimeout(30000); config.setMaxLifetime(1800000);

参数里最常出问题的是 maximumPoolSize 和 maxLifetime。池上限设得比数据库 max_connections 还大,高峰期连接会被数据库直接拒绝;maxLifetime 设得比数据库 wait_timeout 长,空闲连接被服务端断掉后,客户端拿到的是"死连接",一执行 SQL 就抛异常。实验里最常见的报错是Connection is not available, request timed out,先查代码里有没有在 finally 中关闭 Statement 和 ResultSet,再查事务有没有提交或回滚。连接泄漏十有八九是这两个地方没写干净,不是池参数的问题。

5.4 避坑清单:5 条血泪经验

  1. 现象:第二个窗口 UPDATE 同一行,一直不返回。 原因:InnoDB 行锁等待,第一个事务还没提交。 解决:让第一个窗口 COMMIT 或 ROLLBACK;调大锁等待超时只是拖延问题,不是解法。

  2. 现象:两个事务各自执行到一半,其中一个报Deadlock found。 原因:两个事务按相反顺序给同一批数据加锁。 解决:全局统一加锁顺序;对被回滚的事务做重试。

  3. 现象:按实验步骤操作,并发场景却没有任何锁效果。 原因:表用了 MyISAM,或者操作没有包在事务里。 解决:建表统一用 InnoDB;写操作前后显式 START TRANSACTION,不要依赖自动提交。

  4. 现象:Navicat 连接 MySQL 8 报认证插件无法加载。 原因:客户端太旧,不认识 caching_sha2_password。 解决:升级客户端版本;临时方案是给实验账号换用 mysql_native_password。

  5. 现象:实验做了一下午,表里的数据改得一团乱,又记不清原始值。 原因:没做基线备份,高危操作没放事务。 解决:每次实验开始前执行 init_lab.sql 恢复干净环境;高危操作前 SELECT 验证影响范围。

这些坑单独看都不大,但每一个都真实吃掉过我自己的时间。第 3 条尤其隐蔽,很多同学写完建表脚本顺手用了默认存储引擎,结果到并发实验怎么都复现不出来,最后发现是引擎不符,重新建表才解决。

6. 把课后实验变成面试素材:用面试题反过来验证实验

做完这一轮课后实验,最后一步不是交实验报告,而是把每个实验改写成面试答案。数据库面试题里有几个高频话题,恰好都能在这套实验里找到证据。

比如问"数据库死锁怎么排查",不要只背概念,可以说:我在实验里开两个终端复现死锁,然后用 SHOW ENGINE INNODB STATUS 看 LATEST DETECTED DEADLOCK,定位到两个事务加锁顺序相反,最后统一加锁顺序解决。这段经历比"死锁是两个事务互相等待"有价值得多。

比如问"索引为什么能提速",可以说:我在 sc 表建索引前后分别跑 EXPLAIN,type 从 ALL 变成 ref,扫描行数从全表降到一行,同时对比了低区分度字段建索引没有效果。这就把 B+ 树、回表、区分度几个概念全串起来了。

再比如事务隔离级别,面试里问"MySQL 默认隔离级别是什么,怎么复现不可重复读",你可以直接说:开两个窗口,一个读,一个改,在 READ COMMITTED 下第二次读能看到新数据,在 REPEATABLE READ 下看不到。这套操作就是课后实验的延伸。

我自己的习惯是每做完一个实验,在报告最后补一行"如果不这样做会怎么样"。不加 WHERE 会更新全表,不关游标会占连接,不提交事务会锁行,连接池不关连接会耗尽。这行字一开始写起来很慢,但积累几周后,数据库的实验现象就全变成自己的经验了。

当年我带实验课的时候,很多同学交完报告就把实验丢在一边,面试被问到并发锁只能背定义,一深问就卡壳。后来我要求他们把实验当一个可复现的小项目,每个坑记下来,面试反而有东西讲。把课后实验做进简历,不丢人。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询