简介:这份资源是西北工业大学《数据库原理》课程的第五次实验报告文档,面向正在学习数据库课程的高校学生及需要巩固SQL Server实操的开发者。内容围绕数据库对象的核心操作展开,涵盖使用sp_rename重命名视图、创建带参数的jsearch存储过程与加密的jmsearch存储过程、借助sp_helptext查看过程文本,以及针对SC表和S表设计insert_s、dele_s1、dele_s2、update_s等多个触发器,并涉及禁用与删除触发器的完整流程。资源包内仅含1个doc文件,大小约231KB,属于纯文档型实验资料,便于直接查阅与打印。目前已有716人学习浏览,说明其在同类实验报告中具有一定参考价值。读者可借此获得完整的实验步骤、可复用的SQL代码片段与触发器验证思路,适合作为课程作业参考或期末复习的实操范本。
1. 数据库实验报告5到底在做什么:从一份文档名反推整套实验链路
看到“数据库实验报告5.doc”这个标题,很多人第一反应是找模板、找答案、找能直接交差的文档。但真正做过数据库课程实验的人都知道,实验报告5通常对应的是整个实验体系里最综合的那一环——它不再是单表增删改查,而是把建库、建表、约束、索引、视图、存储过程、触发器、事务控制串成一条完整链路。换句话说,这份文档背后真正要交付的不是几段SQL,而是一套能跑通、能解释、能复现的数据库方案。
我见过太多同学前面四个实验靠复制粘贴混过去,到实验5直接翻车:表建好了但外键顺序不对,数据插不进去;视图查出来和预期差三行,排查半天发现是NULL参与比较;存储过程调试时参数传错类型,报错信息又看不懂。这些坑不是玄学,是数据库实验里反复出现的结构性问题。
这篇文章面向三类人:正在做数据库综合实验、需要把实验5从建表到事务完整落地的人;已经写完但跑不通、想系统排查的人;以及想把实验报告写成“可复现技术文档”而不是“截图堆砌”的人。下面按建库建表、查询与视图、存储过程与触发器、事务与并发、避坑排查、进阶验证的顺序展开,每一步都给可抄的SQL和参数说明。
2. 建库建表与约束设计:先把地基打对再谈查询
2.1 从实验5的典型需求反推表结构
数据库实验5通常给一个业务场景,比如“学生选课系统”“图书借阅系统”“简易订单系统”。不管场景叫什么,核心都是三到五张表加若干关联。我一般先做三件事:确定实体、确定关系基数、确定哪些字段必须唯一。
以选课场景为例,实体是学生、课程、选课记录。学生和课程是多对多,所以选课记录是中间表。中间表的主键有两种常见做法:一是用自增ID做代理主键,二是用(学生ID, 课程ID)做联合主键。实验报告里如果没明确要求,我倾向联合主键,因为它天然防止重复选课,少一个唯一索引。
-- 建库,字符集用utf8mb4,避免中文和特殊符号乱码 CREATE DATABASE IF NOT EXISTS lab5_db DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE lab5_db; -- 学生表:学号做主键,姓名非空,年龄加检查约束 CREATE TABLE student ( stu_id VARCHAR(12) NOT NULL, stu_name VARCHAR(30) NOT NULL, age TINYINT UNSIGNED, gender CHAR(1) DEFAULT 'M', PRIMARY KEY (stu_id), CONSTRAINT chk_age CHECK (age IS NULL OR (age >= 15 AND age <= 60)) ) ENGINE=InnoDB; -- 课程表:课程号主键,学分限制在合理范围 CREATE TABLE course ( course_id VARCHAR(10) NOT NULL, course_name VARCHAR(50) NOT NULL, credit DECIMAL(3,1) NOT NULL DEFAULT 2.0, PRIMARY KEY (course_id), CONSTRAINT chk_credit CHECK (credit > 0 AND credit <= 10) ) ENGINE=InnoDB; -- 选课表:联合主键防重复,外键分别指向学生和课程 CREATE TABLE sc ( stu_id VARCHAR(12) NOT NULL, course_id VARCHAR(10) NOT NULL, score DECIMAL(5,2), select_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (stu_id, course_id), CONSTRAINT fk_sc_stu FOREIGN KEY (stu_id) REFERENCES student(stu_id) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT fk_sc_course FOREIGN KEY (course_id) REFERENCES course(course_id) ON DELETE RESTRICT ON UPDATE CASCADE ) ENGINE=InnoDB;这段代码的关键不在语法,而在三个决策。第一,存储引擎必须显式写InnoDB,因为实验5大概率要用事务,MyISAM不支持。第二,外键的ON DELETE策略要区分:选课记录跟着学生走,学生删了选课记录没意义,所以CASCADE;但课程被选后不能随便删,所以RESTRICT。第三,CHECK约束在MySQL 8.0.16之后才真正生效,如果实验室环境是5.7,写了也不报错但不执行,报告里要说明这一点,否则老师一问就露馅。
2.2 插入测试数据的顺序与批量写法
建表之后插数据,顺序必须是先父表后子表,否则外键直接拒绝。我习惯用一条多值INSERT而不是多条单值INSERT,减少事务开销,也方便回滚测试。
-- 先插学生和课程,再插选课记录 INSERT INTO student (stu_id, stu_name, age, gender) VALUES ('2024001', '张一', 20, 'M'), ('2024002', '李二', 21, 'F'), ('2024003', '王三', 19, 'M'), ('2024004', '赵四', 22, 'F'); INSERT INTO course (course_id, course_name, credit) VALUES ('C001', '数据库原理', 4.0), ('C002', '操作系统', 3.5), ('C003', '计算机网络', 3.0); INSERT INTO sc (stu_id, course_id, score) VALUES ('2024001', 'C001', 88.5), ('2024001', 'C002', 76.0), ('2024002', 'C001', 92.0), ('2024003', 'C003', NULL), ('2024004', 'C002', 81.5);注意最后一条score是NULL,这是故意的。实验5的查询题经常考NULL处理,比如“查所有没出成绩的选课记录”,用WHERE score = NULL永远查不到,必须用IS NULL。这个点在报告里写一句,比贴十张截图都有说服力。
2.3 索引不是越多越好:实验5里该建哪几个
实验报告常要求“为常用查询建索引”。我的原则是:外键列自动有索引(InnoDB会建),联合主键的第二列如果单独查要补索引,经常出现在WHERE和ORDER BY里的列考虑建。
-- 按课程查选课情况,联合主键里course_id是第二列,单独查不会走主键索引 CREATE INDEX idx_sc_course ON sc(course_id); -- 按成绩范围查,score选择性不高时索引可能不划算,但实验里可以建了对比 CREATE INDEX idx_sc_score ON sc(score); -- 查看执行计划,确认索引是否被使用 EXPLAIN SELECT * FROM sc WHERE course_id = 'C001';EXPLAIN的输出重点看type和key两列。type是ALL表示全表扫描,ref或range表示走了索引。如果建了索引但type还是ALL,可能是数据量太小优化器觉得没必要,实验报告里可以插几百行数据再测,效果更明显。
3. 查询、视图与存储过程:把业务逻辑写进数据库
3.1 多表连接查询的三种写法和适用场景
实验5的查询题一般要求“查询每个学生的选课门数和平均分”“查询没人选的课程”这类。前者用INNER JOIN加GROUP BY,后者用LEFT JOIN加IS NULL判断。
-- 每个学生的选课门数和平均分,没选课的学生也要显示(用LEFT JOIN) SELECT s.stu_id, s.stu_name, COUNT(sc.course_id) AS course_count, ROUND(AVG(sc.score), 2) AS avg_score FROM student s LEFT JOIN sc ON s.stu_id = sc.stu_id GROUP BY s.stu_id, s.stu_name ORDER BY avg_score DESC; -- 没人选的课程:从课程表左连接选课表,选课表主键为NULL的就是没人选 SELECT c.course_id, c.course_name FROM course c LEFT JOIN sc ON c.course_id = sc.course_id WHERE sc.stu_id IS NULL;这里有个细节:COUNT(sc.course_id)和COUNT()在LEFT JOIN下结果不同。COUNT()会统计包含NULL的行,没选课的学生会算1;COUNT(sc.course_id)只统计非NULL,没选课的学生算0。实验报告里如果写“选课门数”,必须用COUNT(sc.course_id),否则数据是错的。
3.2 视图的创建与“视图能不能更新”的边界
视图在实验5里通常要求“创建一个视图展示学生选课详情”。视图的好处是封装复杂查询,但很多人不知道视图的更新限制。
-- 创建视图:展示学生姓名、课程名、成绩 CREATE OR REPLACE VIEW v_stu_course AS SELECT s.stu_id, s.stu_name, c.course_name, sc.score FROM student s JOIN sc ON s.stu_id = sc.stu_id JOIN course c ON sc.course_id = c.course_id; -- 查询视图 SELECT * FROM v_stu_course WHERE score > 80;这个视图包含三表连接,属于不可更新视图。如果实验要求“通过视图修改成绩”,必须改成单表视图或者用INSTEAD OF触发器(MySQL不支持INSTEAD OF,只能用存储过程替代)。常见做法是:视图只读,更新走基表。报告里写清楚“本视图用于查询,更新操作通过存储过程实现”,逻辑就闭环了。
3.3 存储过程:参数、流程控制与调试方法
存储过程是实验5区分“会写SQL”和“会做数据库开发”的分水岭。我一般写一个带输入输出参数的存储过程,比如“根据学号查平均分并返回等级”。
DELIMITER $$ CREATE PROCEDURE sp_get_stu_level( IN p_stu_id VARCHAR(12), OUT p_avg_score DECIMAL(5,2), OUT p_level VARCHAR(10) ) BEGIN -- 先算平均分,注意NULL处理 SELECT ROUND(AVG(score), 2) INTO p_avg_score FROM sc WHERE stu_id = p_stu_id; -- 如果没选课,平均分为NULL,直接返回未知 IF p_avg_score IS NULL THEN SET p_level = '无成绩'; ELSEIF p_avg_score >= 90 THEN SET p_level = '优秀'; ELSEIF p_avg_score >= 80 THEN SET p_level = '良好'; ELSEIF p_avg_score >= 60 THEN SET p_level = '及格'; ELSE SET p_level = '不及格'; END IF; END$$ DELIMITER ; -- 调用并查看输出 CALL sp_get_stu_level('2024001', @avg, @lvl); SELECT @avg AS avg_score, @lvl AS level;调试存储过程最常见的翻车是DELIMITER没改回来,导致后续SQL全部报错。我的习惯是写完存储过程立刻执行DELIMITER ;,再继续写其他语句。另外,OUT参数在调用时必须用用户变量(@开头),不能直接用常量。
4. 触发器与事务:实验5最容易翻车的两个点
4.1 触发器实现业务规则:选课人数上限与日志记录
触发器常考“选课人数超过上限时阻止插入”或“成绩修改后记录日志”。我写一个前者,因为逻辑更完整。
-- 先建一个日志表,记录被拒绝的选课尝试 CREATE TABLE sc_log ( log_id INT AUTO_INCREMENT PRIMARY KEY, stu_id VARCHAR(12), course_id VARCHAR(10), log_time DATETIME DEFAULT CURRENT_TIMESTAMP, reason VARCHAR(100) ) ENGINE=InnoDB; DELIMITER $$ CREATE TRIGGER trg_sc_limit BEFORE INSERT ON sc FOR EACH ROW BEGIN DECLARE v_count INT; -- 查该课程已选人数 SELECT COUNT(*) INTO v_count FROM sc WHERE course_id = NEW.course_id; -- 假设每门课上限5人 IF v_count >= 5 THEN -- 记录日志后抛错,阻止插入 INSERT INTO sc_log (stu_id, course_id, reason) VALUES (NEW.stu_id, NEW.course_id, '课程人数已满'); SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '选课人数已达上限,插入被拒绝'; END IF; END$$ DELIMITER ;触发器的坑在于:SIGNAL抛错后,触发器里之前的INSERT(写日志)也会被回滚,除非日志表用MyISAM。但MyISAM不支持事务,实验里如果要求“日志必须保留”,就得换思路——比如用独立连接写日志,或者接受日志也回滚。我一般会在报告里说明这个权衡,老师反而觉得你理解深。
4.2 事务隔离级别与并发问题复现
实验5通常要求“演示事务的ACID”或“复现脏读/不可重复读”。MySQL默认隔离级别是REPEATABLE READ,要复现脏读需要改成READ UNCOMMITTED。
-- 会话A:查看当前隔离级别 SELECT @@transaction_isolation; -- 会话A:设置读未提交 SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -- 会话A:开启事务并修改数据但不提交 START TRANSACTION; UPDATE sc SET score = 100 WHERE stu_id = '2024001' AND course_id = 'C001'; -- 会话B:在另一个连接里查,能读到未提交的100,这就是脏读 SELECT score FROM sc WHERE stu_id = '2024001' AND course_id = 'C001'; -- 会话A:回滚 ROLLBACK; -- 会话B:再查,数据变回原值 SELECT score FROM sc WHERE stu_id = '2024001' AND course_id = 'C001';这个实验需要两个客户端连接同时操作,报告里要写清楚“会话A”和“会话B”分别执行什么。很多人只在一个窗口里跑,结果永远复现不了。另外,MySQL 8.0的隔离级别变量名是transaction_isolation,5.7是tx_isolation,写报告时注意版本差异。
5. 数据库实验5的避坑与排查:5条血泪经验
5.1 外键报错1452:插入顺序和数据类型都要查
现象:插入选课记录时报Cannot add or update a child row: a foreign key constraint fails。原因通常有两个:父表里没有对应的主键值,或者外键列和主键列的数据类型/字符集不一致。解决:先SELECT父表确认值存在,再用SHOW CREATE TABLE对比两表的列定义,字符集不同也会导致外键建不上。
5.2 视图查不到数据:NULL比较和连接条件写错
现象:视图创建成功,但查出来行数比预期少。原因:连接条件里用了=比较可能为NULL的列,或者WHERE里写了score = NULL。解决:把=改成IS NULL或<=>(NULL安全等于),连接条件逐列核对。
5.3 存储过程创建失败:DELIMITER和权限问题
现象:CREATE PROCEDURE报语法错误,或者提示Access denied。原因:没改DELIMITER导致分号提前结束语句;或者当前用户没有CREATE ROUTINE权限。解决:先DELIMITER $$再写过程,结束后改回DELIMITER ;;权限问题找管理员授权或换有权限的账号。
5.4 触发器导致死锁:同表读写要小心
现象:插入数据时卡住,最后报Deadlock found。原因:触发器里又去查同一张表,InnoDB在并发下容易死锁。解决:触发器里尽量只做简单判断,避免复杂查询;或者把逻辑挪到应用层,用事务加锁控制。
5.5 事务不回滚:DDL语句的隐式提交
现象:START TRANSACTION后执行了CREATE TABLE或ALTER TABLE,再ROLLBACK发现之前的DML也没回滚。原因:MySQL里DDL会隐式提交当前事务。解决:事务里只放DML,建表改表放在事务外;报告里要说明这个行为,否则实验结论就是错的。
6. 进阶验证:用执行计划和慢查询日志证明你的方案有效
实验报告写到“能跑通”只是及格,写到“能证明为什么这样跑更快”才是拉开差距的地方。我一般会加两个验证:EXPLAIN看索引命中,慢查询日志看实际耗时。
-- 开启慢查询日志,阈值设为0.1秒 SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 0.1; -- 执行一个可能慢的查询:按成绩范围查并排序 SELECT * FROM sc WHERE score BETWEEN 60 AND 90 ORDER BY score DESC; -- 查看慢查询日志路径 SHOW VARIABLES LIKE 'slow_query_log_file'; -- 用EXPLAIN分析同一查询 EXPLAIN SELECT * FROM sc WHERE score BETWEEN 60 AND 90 ORDER BY score DESC;如果EXPLAIN的type是ALL且Extra里出现Using filesort,说明没走索引还额外排序。可以建复合索引(score)再测,对比key列的变化。报告里放一张前后对比表,比任何文字都有力。
| 验证项 | 优化前 | 优化后 |
|---|---|---|
| 查询类型 | 全表扫描 | 范围索引扫描 |
| EXPLAIN type | ALL | range |
| Extra | Using filesort | Using index condition |
| 预估行数 | 全表 | 匹配行数 |
最后说一个我自己的习惯:每次写完实验5的SQL脚本,一定从头到尾在一个干净数据库里重跑一遍,不跳步、不注释、不依赖之前的环境。因为实验报告交上去,老师很可能就是按顺序执行你的脚本,中间任何一步依赖了“我之前手动插的数据”,都会导致复现失败。这个习惯帮我省了至少三次返工。希望帮到你。
本文还有配套的精品资源,点击获取