资讯动态

数据库原理实验报告完整指南:从建表到事务的SQL实操路径

发布时间:2026/10/14 3:11:41 来源:尧图企业网站定制
简介这份资源是郑州大学计算机与人工智能学院《数据库系统原理》课程的实验报告文档面向修读该课程的学生及需要完成数据库实验的学习者帮助梳理从DBMS认识到GreatSQL实操的完整实验流程。报告围绕万里数据库GreatSQL展开涵盖DBMS工作原理与系统构架、逻辑与物理组件、GreatSQL的启动暂停停止及主要工具使用并详细记录了虚拟机VMware搭配CentOS7的环境配置、关闭SELinux与防火墙的原因与命令、依赖包安装、二进制包下载解压及服务注册等实践操作。资源包内含1个docx文件约3.69MB以实验报告正文为主体包含实验目的、内容、类型、学时分配、仪器材料、实验过程与结果分析等规范模块并配有截图编号、图题及表格格式要求。目前已有507人学习下载适合需要参考实验报告撰写规范、复盘GreatSQL安装配置步骤或准备课程实验的学生使用。1. 数据库原理实验报告从建表到事务一份能跑通的完整路径很多同学拿到数据库原理实验任务时第一反应是打开编辑器就开始写 SQL结果建表语句跑通了插入数据报外键错误改完外键又发现查询结果对不上最后事务那一部分干脆放弃。这不是能力问题是顺序问题。数据库原理实验报告的核心不是“写一份报告”而是用一套可复现的流程把关系模型、SQL 实操、完整性约束、事务与并发这几块串起来。它适合正在上数据库原理课、需要完成实验报告的同学也适合想系统梳理 SQL 实操但一直没找到完整路径的开发者。下面按“先能跑、再能查、再能控”的顺序拆开讲每一步都给出可抄的代码和参数说明。2. 实验环境与建表先把最小可运行库搭起来2.1 选 MySQL 还是 PostgreSQL实验场景下的取舍数据库原理实验通常要求体现关系代数、完整性约束和事务选型时优先考虑两点一是对标准 SQL 的支持程度二是事务隔离级别的可观测性。MySQL 8.0 在窗口函数和 CTE 上已经够用社区资料多遇到报错容易搜到答案PostgreSQL 对 CHECK 约束、事务隔离级别的行为更贴近教材描述但部分学校机房环境默认装的是 MySQL。我一般建议如果实验指导书没有硬性指定选 MySQL 8.0因为它的报错信息更直白新手排查成本低。如果实验明确要求演示“可重复读”和“幻读”PostgreSQL 的默认隔离级别就是 read committed需要手动改反而多一步MySQL 默认 repeatable read更贴近教材里对隔离级别的讲解顺序。安装完成后先确认版本和默认存储引擎-- 查看版本和默认引擎确保 InnoDB 可用 SELECT VERSION(); SHOW ENGINES;InnoDB 必须显示为 DEFAULT 或 YES因为事务和行级锁依赖它。如果默认引擎是 MyISAM事务实验会直接失效这是第一个容易翻车的地方。2.2 建库建表把 ER 图翻译成 DDL 的四个检查点假设实验题目是“学生选课系统”实体有学生、课程、选课记录。建表时不要一口气写完所有约束按“先主键、再外键、最后 CHECK”的顺序加每加一层跑一次报错时能立刻定位。-- 建库时显式指定字符集避免中文乱码 CREATE DATABASE IF NOT EXISTS lab_db DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_0900_ai_ci; USE lab_db; -- 学生表学号为主键姓名非空年龄用 CHECK 限制范围 CREATE TABLE student ( sno CHAR(8) NOT NULL, sname VARCHAR(20) NOT NULL, sage TINYINT UNSIGNED, sdept VARCHAR(30), PRIMARY KEY (sno), CONSTRAINT chk_age CHECK (sage BETWEEN 15 AND 60) ) ENGINEInnoDB; -- 课程表课程号为主键学分用 DECIMAL 避免浮点误差 CREATE TABLE course ( cno CHAR(4) NOT NULL, cname VARCHAR(40) NOT NULL, credit DECIMAL(3,1) NOT NULL, PRIMARY KEY (cno), CONSTRAINT chk_credit CHECK (credit 0 AND credit 10) ) ENGINEInnoDB; -- 选课表复合主键两个外键分别指向学生和课程 CREATE TABLE sc ( sno CHAR(8) NOT NULL, cno CHAR(4) NOT NULL, grade DECIMAL(4,1), PRIMARY KEY (sno, cno), CONSTRAINT fk_sc_student FOREIGN KEY (sno) REFERENCES student(sno) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT fk_sc_course FOREIGN KEY (cno) REFERENCES course(cno) ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT chk_grade CHECK (grade IS NULL OR (grade 0 AND grade 100)) ) ENGINEInnoDB;四个检查点第一字符集用 utf8mb4否则中文姓名可能变成问号第二学分用 DECIMAL 而不是 FLOAT因为 0.1 在浮点里无法精确表示后续算 GPA 会出现 3.5999999 这种结果第三外键的 ON DELETE 行为要区分——学生删除时选课记录跟着删CASCADE课程删除时如果还有人选则拒绝RESTRICT这对应教材里“参照完整性”的不同策略第四CHECK 约束在 MySQL 8.0.16 之前是被忽略的如果版本低于这个年龄和成绩的检查不会生效需要改用触发器这是血泪经验。2.3 插入测试数据顺序和批量插入的注意点有外键的情况下插入顺序必须是“先被参照表后参照表”。批量插入时把多行写在一条 INSERT 里比逐条快一个数量级但要注意单条语句长度限制。-- 先插学生和课程再插选课记录 INSERT INTO student (sno, sname, sage, sdept) VALUES (20240001, 张三, 20, 计算机), (20240002, 李四, 21, 计算机), (20240003, 王五, 19, 软件工程); INSERT INTO course (cno, cname, credit) VALUES (C001, 数据库原理, 3.0), (C002, 操作系统, 4.0), (C003, 计算机网络, 3.5); INSERT INTO sc (sno, cno, grade) VALUES (20240001, C001, 88.5), (20240001, C002, 76.0), (20240002, C001, 92.0), (20240003, C003, NULL);如果插入 sc 时报 “Cannot add or update a child row”说明 sno 或 cno 在父表里不存在先查父表确认数据是否真的插进去了。grade 允许 NULL 表示“选了但还没出成绩”这和“没选”是两种语义查询时要区分。3. 查询与完整性验证把关系代数写成能跑的 SQL3.1 单表到多表连接从笛卡尔积到等值连接教材里讲连接时先讲笛卡尔积再讲等值连接实验报告里最好把两种写法都跑一遍对比行数这样能直观理解“连接条件”的作用。-- 笛卡尔积3 个学生 × 3 门课 9 行没有意义但能说明问题 SELECT COUNT(*) FROM student, course; -- 等值连接查每个学生选了哪些课带成绩 SELECT s.sno, s.sname, c.cname, sc.grade FROM student s JOIN sc ON s.sno sc.sno JOIN course c ON sc.cno c.cno ORDER BY s.sno, c.cno;JOIN 的 ON 条件就是关系代数里的连接谓词。如果漏写 ONMySQL 会报语法错误因为 JOIN 必须跟 ON 或 USING但写成逗号加 WHERE 的旧式写法漏了条件就会变成笛卡尔积结果集爆炸。我一般统一用 JOIN … ON可读性更好也不容易漏条件。3.2 聚合与分组GROUP BY 和 HAVING 的分工查“每门课的平均分”和“平均分大于 80 的课程”前者用 GROUP BY后者在 GROUP BY 之后用 HAVING。WHERE 在分组前过滤行HAVING 在分组后过滤组这是实验报告里必须体现的一个知识点。-- 每门课的平均分、最高分、选课人数 SELECT c.cno, c.cname, AVG(sc.grade) AS avg_grade, MAX(sc.grade) AS max_grade, COUNT(sc.sno) AS student_count FROM course c LEFT JOIN sc ON c.cno sc.cno GROUP BY c.cno, c.cname HAVING AVG(sc.grade) 80 OR AVG(sc.grade) IS NULL;用 LEFT JOIN 是为了让没人选的课也出现在结果里COUNT 会返回 0AVG 返回 NULL。如果只写 JOIN没人选的课直接消失实验报告里如果要求“列出所有课程及其选课情况”就会漏数据。HAVING 里加IS NULL是为了把没人选的课也保留具体看题目要求。3.3 完整性约束验证主动制造错误来证明约束生效实验报告里不能只说“我加了外键”要给出违反约束时的报错证明约束真的在起作用。下面三条语句分别测试实体完整性、参照完整性和用户定义完整性。-- 测试主键冲突应报 Duplicate entry INSERT INTO student (sno, sname) VALUES (20240001, 重复学号); -- 测试外键冲突应报 Cannot add or update a child row INSERT INTO sc (sno, cno, grade) VALUES (99999999, C001, 80); -- 测试 CHECK 约束应报 Check constraint violated INSERT INTO student (sno, sname, sage) VALUES (20240004, 赵六, 200);把这三条报错原文截图或粘贴到报告里比写“约束已生效”有说服力得多。注意 MySQL 的报错编号分别是 1062、1452、3819不同版本编号可能略有差异以实际为准。4. 事务与并发把 ACID 从概念变成可观测现象4.1 事务的提交与回滚用一个转账场景验证原子性事务实验最经典的场景是转账A 账户扣钱、B 账户加钱两步必须同时成功或同时失败。先建一张账户表再用 START TRANSACTION 和 ROLLBACK 演示。CREATE TABLE account ( id INT PRIMARY KEY, name VARCHAR(20), balance DECIMAL(10,2) NOT NULL, CONSTRAINT chk_balance CHECK (balance 0) ) ENGINEInnoDB; INSERT INTO account VALUES (1, A, 1000.00), (2, B, 500.00); -- 事务一正常提交 START TRANSACTION; UPDATE account SET balance balance - 200 WHERE id 1; UPDATE account SET balance balance 200 WHERE id 2; COMMIT; -- 事务二回滚余额应恢复原值 START TRANSACTION; UPDATE account SET balance balance - 300 WHERE id 1; ROLLBACK;回滚后查SELECT * FROM accountid1 的余额应该还是 800不是 500。如果回滚没生效先确认表引擎是 InnoDB再确认 autocommit 没有被意外打开。MySQL 默认 autocommit1但 START TRANSACTION 会临时关闭自动提交直到 COMMIT 或 ROLLBACK。4.2 隔离级别观测两个会话下的脏读与不可重复读隔离级别实验需要开两个客户端会话手动控制提交时机。下面用“不可重复读”举例会话 A 第一次读到的值和第二次读到的值不同因为会话 B 在中间提交了修改。-- 会话 A设置隔离级别为 READ COMMITTED SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; START TRANSACTION; SELECT balance FROM account WHERE id 1; -- 第一次读假设是 800 -- 会话 B修改并提交 START TRANSACTION; UPDATE account SET balance balance 100 WHERE id 1; COMMIT; -- 会话 A再读一次同一个事务内值变了 SELECT balance FROM account WHERE id 1; -- 第二次读变成 900 COMMIT;如果会话 A 的隔离级别是 REPEATABLE READMySQL 默认第二次读仍然是 800因为 InnoDB 用 MVCC 保证了可重复读。实验报告里把两种隔离级别的输出都贴出来对比说明比只写概念定义强得多。注意 SET SESSION 只影响当前连接重连后会恢复默认值。4.3 锁等待与死锁制造一次死锁并读懂报错死锁实验能体现数据库的自动检测能力。两个事务各持有一把锁再互相请求对方的锁InnoDB 会检测到并回滚其中一个。-- 会话 A START TRANSACTION; UPDATE account SET balance balance - 10 WHERE id 1; -- 持有 id1 的行锁 -- 会话 B START TRANSACTION; UPDATE account SET balance balance - 10 WHERE id 2; -- 持有 id2 的行锁 -- 会话 A 再请求 id2 UPDATE account SET balance balance - 10 WHERE id 2; -- 等待 -- 会话 B 再请求 id1 UPDATE account SET balance balance - 10 WHERE id 1; -- 死锁InnoDB 回滚其中一个报错信息是 “Deadlock found when trying to get lock; try restarting transaction”被回滚的事务需要应用层重试。实验报告里要写清楚死锁不是 bug是并发控制的正常现象关键是应用层要有重试逻辑。如果实验环境不允许开两个会话可以用SHOW ENGINE INNODB STATUS查看最近的死锁信息里面会记录两个事务的 SQL 和等待的锁。5. 避坑与排查实验报告里最容易翻车的五个点5.1 中文乱码字符集没对齐的三层检查现象是插入中文后查询显示问号或乱码。原因通常是数据库、表、连接三层的字符集不一致。解决建库时指定 utf8mb4建表时继承库的字符集连接串里加characterEncodingutf8。用SHOW VARIABLES LIKE character%检查确保 character_set_client、character_set_connection、character_set_results 都是 utf8mb4。5.2 外键报错 1452插入顺序和数据类型都要查现象是插入子表数据时报 “Cannot add or update a child row”。原因有两个一是父表里没有对应的主键值二是外键列和主键列的数据类型不完全一致比如一个 CHAR(8) 一个 VARCHAR(8)。解决先查父表确认数据存在再用SHOW CREATE TABLE对比两列的类型和字符集必须完全一致。5.3 事务不回滚引擎和 autocommit 是两个嫌疑现象是 ROLLBACK 之后数据还在。原因通常是表用了 MyISAM 引擎不支持事务或者中间执行了 DDL 语句如 CREATE TABLEMySQL 会隐式提交。解决建表时显式写 ENGINEInnoDB事务里不要混 DDL用SELECT autocommit确认自动提交状态。5.4 GROUP BY 报错 1055ONLY_FULL_GROUP_BY 模式现象是 SELECT 里出现了 GROUP BY 没有的列报 “Expression not in GROUP BY clause”。原因是 MySQL 5.7 之后默认开启 ONLY_FULL_GROUP_BY。解决要么把该列加到 GROUP BY要么用 ANY_VALUE() 包起来要么临时改 sql_mode。实验报告里建议按标准 SQL 写把非聚合列都放进 GROUP BY这样换数据库也能跑。5.5 隔离级别设置不生效SESSION 和 GLOBAL 的区别现象是改了隔离级别但行为没变。原因是 SET GLOBAL 只影响之后新建的连接当前连接要用 SET SESSION。解决在事务开始前执行SET SESSION TRANSACTION ISOLATION LEVEL ...然后用SELECT transaction_isolation确认当前会话的值。6. 进阶技巧用存储过程和触发器把实验报告做出区分度基础实验做完之后如果想在报告里体现更深的理解可以加一个存储过程或触发器。存储过程适合封装“选课”这种多步操作触发器适合做“成绩修改后自动记录日志”这种审计需求。下面用一个触发器示例每次修改 sc 表的 grade自动往日志表写一条记录。-- 日志表记录谁改了哪条成绩改前改后分别是多少 CREATE TABLE grade_log ( log_id INT AUTO_INCREMENT PRIMARY KEY, sno CHAR(8), cno CHAR(4), old_grade DECIMAL(4,1), new_grade DECIMAL(4,1), changed_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; -- 触发器AFTER UPDATE把旧值和新值都写进日志 DELIMITER // CREATE TRIGGER trg_grade_update AFTER UPDATE ON sc FOR EACH ROW BEGIN IF OLD.grade NEW.grade OR (OLD.grade IS NULL AND NEW.grade IS NOT NULL) THEN INSERT INTO grade_log (sno, cno, old_grade, new_grade) VALUES (OLD.sno, OLD.cno, OLD.grade, NEW.grade); END IF; END // DELIMITER ;DELIMITER 是为了让 MySQL 把整个 BEGIN…END 当成一个语句否则遇到分号就截断了。触发器里用 OLD 和 NEW 分别引用修改前和修改后的行IF 条件处理了 NULL 的比较——SQL 里 NULL NULL 结果是 NULL 不是 TRUE所以必须单独判断。测试时执行UPDATE sc SET grade 95 WHERE sno 20240001 AND cno C001然后查 grade_log应该能看到一条记录。存储过程方面可以写一个“选课”过程内部先检查课程是否存在、是否已选、人数是否超限再插入 sc 表。这样把业务逻辑下沉到数据库层实验报告里能体现对存储过程参数模式IN、OUT、INOUT的理解。参数用 IN 传入学号和课程号用 OUT 返回状态码调用时用CALL enroll(20240001, C002, result)再SELECT result查看结果。验证触发器是否生效除了查日志表还可以用SHOW TRIGGERS查看当前库的所有触发器。如果触发器没触发先确认 UPDATE 语句确实影响了行ROW_COUNT() 大于 0再确认触发器的时机是 BEFORE 还是 AFTER——AFTER 触发器里不能修改 NEW 的值但可以读 OLD 和 NEW。我自己的习惯是每加一个触发器或存储过程先写一条最简单的测试语句跑通再逐步加条件分支。数据库的报错信息通常很具体顺着报错查比盲改快得多。实验报告的价值不在于代码多长而在于每一步都有验证、每个报错都有解释。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑