ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ZZU数据库实验报告:从SQL语法到工程实践的实战指南

ZZU数据库实验报告:从SQL语法到工程实践的实战指南 简介本资源为郑州大学《数据库系统原理实验》课程配套的完整实验报告书面向计算机科学与技术、软件工程等专业的本科生聚焦数据库核心能力培养覆盖DBMS认知、SQL实战、视图设计、完整性与安全性控制、事务并发管理、备份恢复及JDBC编程等关键实践环节。报告以openGauss为实验平台详述9个递进式实验的操作步骤、问题分析、结果截图与思考总结突出理论联系实际的教学逻辑。压缩包含1个3.23MB的docx文档结构规范含前言、目录及全部实验章节含实验一至九每节均包含目的、内容、操作流程与报告撰写要求便于复现与教学参考。目前已有1520人学习下载适合课程同步复习、实验预习、报告撰写参考及数据库实操能力系统提升。1. ZZU数据库实验报告书不是模板套用而是把SQL写进真实业务逻辑里的实操手册郑州大学ZZU计算机类专业学生交的“数据库实验报告书”常被当成应付作业的填空文档——建个表、插几条数据、跑几个SELECT就完事。但真正让老师眼前一亮、让面试官多看两眼的是那份能体现数据建模思维、事务边界意识、索引失效归因能力的报告书。它不靠花哨排版而靠在“学生选课系统”里写出带并发控制的退课逻辑在“图书借阅模块”中用EXPLAIN验证联合索引是否命中在“成绩录入界面”后端脚本里显式声明SAVEPOINT防部分失败。这份报告书本质是一份可执行、可复现、可压测的微型数据库工程交付物DDL语句带注释说明范式选择依据DML操作附带事务隔离级别实测对比甚至包含用mysqldump生成的最小可复现实验快照。适合刚学完《数据库系统概论》第5章、正卡在“为什么加了索引还是慢”困惑里的本科生也适合想用真实教学场景反推企业级SQL规范的助教——你不需要部署K8s集群但得让MySQL 8.0在本地跑出和生产环境一致的锁等待现象。2. 从DDL到DML用ZZU典型实验场景构建可验证的数据库骨架ZZU数据库实验通常围绕三个核心业务域展开学生选课系统含课程、教师、班级、选课记录、图书借阅系统含读者、图书、借阅日志、逾期规则、成绩管理系统含学期、课程、学生、平时分、期末分。这些不是虚构案例而是直接映射校内教务系统简化模型。要写出有说服力的实验报告书第一步不是写SQL而是用真实约束倒逼设计决策。2.1 学生选课系统的范式落地第三范式不是教条是避免更新异常的救命绳以“学生选课”为例常见错误是把“课程名、教师名、学分、上课时间”全堆在选课表里。这会导致修改某门课教师时需UPDATE所有选该课的学生记录——一旦漏改数据就自相矛盾。正确做法是拆成三张表并用外键级联约束固化关系-- 课程表存储课程元信息主键course_id CREATE TABLE course ( course_id CHAR(10) PRIMARY KEY, course_name VARCHAR(50) NOT NULL, credit TINYINT CHECK (credit BETWEEN 1 AND 6), teacher_id CHAR(10) NOT NULL, CONSTRAINT fk_teacher FOREIGN KEY (teacher_id) REFERENCES teacher(teacher_id) ON UPDATE CASCADE ); -- 教师表独立维护教师信息 CREATE TABLE teacher ( teacher_id CHAR(10) PRIMARY KEY, teacher_name VARCHAR(20) NOT NULL, dept VARCHAR(30) ); -- 选课表只存关联关系复合主键防重复选课 CREATE TABLE enrollment ( student_id CHAR(10), course_id CHAR(10), semester CHAR(6), -- 格式202301 grade DECIMAL(3,1) DEFAULT NULL, PRIMARY KEY (student_id, course_id, semester), FOREIGN KEY (student_id) REFERENCES student(student_id) ON DELETE CASCADE, FOREIGN KEY (course_id) REFERENCES course(course_id) ON DELETE RESTRICT );关键参数说明ON UPDATE CASCADE当教师工号变更时自动同步course表中的teacher_id避免手动UPDATE引发遗漏ON DELETE RESTRICT禁止删除仍有课程开设的教师强制先清空course表对应记录PRIMARY KEY (student_id, course_id, semester)用三字段组合主键天然防止同一学生同学期重复选同一门课比单独加UNIQUE索引更符合业务语义。这种设计让后续所有DML操作都有明确的数据一致性保障。比如退课操作只需DELETE enrollment表一行不会误删课程或教师信息——这是实验报告书里必须写明的“设计依据”而非简单罗列建表语句。2.2 图书借阅系统的事务封装一个借书动作背后的真实事务边界借书看似简单读者ID 图书ID → 插入借阅记录 更新图书状态。但在高并发下若不加事务控制会出现“超借”问题库存为0时仍成功插入借阅记录。ZZU实验要求必须演示READ COMMITTED与REPEATABLE READ隔离级别的差异这就需要构造可复现的竞争场景。以下是在MySQL 8.0中模拟双线程借书冲突的最小化脚本-- 步骤1准备测试数据图书库存初始为1 INSERT INTO book (book_id, title, stock) VALUES (B001, 数据库系统概论, 1); -- 步骤2开启两个会话均设置为READ COMMITTED SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; -- 会话A执行先查库存 START TRANSACTION; SELECT stock FROM book WHERE book_id B001; -- 返回1 -- 此时不提交让会话B介入 -- 会话B执行同样查库存 START TRANSACTION; SELECT stock FROM book WHERE book_id B001; -- 也返回1 -- 会话B继续判断可借更新库存 UPDATE book SET stock stock - 1 WHERE book_id B001 AND stock 0; -- 影响行数1提交 COMMIT; -- 会话A继续未察觉库存已变仍执行更新 UPDATE book SET stock stock - 1 WHERE book_id B001 AND stock 0; -- 影响行数0但会话A仍尝试插入借阅记录... INSERT INTO borrow (reader_id, book_id, borrow_time) VALUES (R001, B001, NOW()); COMMIT;这个实验的关键在于用AND stock 0作为UPDATE的WHERE条件把库存检查和扣减合并为原子操作。即使在READ COMMITTED下第二次UPDATE因WHERE不成立而影响0行从而避免超借。实验报告书中必须记录两次UPDATE的ROW_COUNT()返回值并截图SHOW ENGINE INNODB STATUS中的锁信息——这才是验证事务有效性的硬证据不是口头说“我用了事务”。3. 索引优化实战用EXPLAIN读懂MySQL的“心里话”而不是背口诀ZZU实验报告书里最常被敷衍的部分是“创建索引并分析效果”。很多同学直接CREATE INDEX idx_student_name ON student(name);然后贴一张执行时间对比图就结束。但真正的索引优化是从EXPLAIN输出里读出MySQL的决策逻辑它为什么没走索引为什么走了索引却变成ALL扫描为什么联合索引的字段顺序决定了生死3.1 用EXPLAIN诊断“明明建了索引却全表扫描”的三大原因以学生表查询为例假设建了复合索引INDEX idx_stu_dept_grade (dept, grade)但执行SELECT * FROM student WHERE grade 85;时仍走ALL扫描。这不是MySQL抽风而是索引最左前缀原则的必然结果-- 创建联合索引 CREATE INDEX idx_stu_dept_grade ON student(dept, grade); -- 执行计划分析 EXPLAIN SELECT * FROM student WHERE grade 85;EXPLAIN输出中key列为NULLtype为ALL。原因在于WHERE条件只用了索引的第二个字段grade跳过了最左字段dept导致索引失效。解决方案不是盲目加单列索引而是根据查询模式重构索引-- 方案1为高频单字段查询建独立索引 CREATE INDEX idx_stu_grade ON student(grade); -- 方案2调整联合索引顺序若dept查询频率低grade高频 CREATE INDEX idx_stu_grade_dept ON student(grade, dept);参数说明EXPLAIN中key_len值反映实际使用索引字节数例如key_len4表示只用了INT类型字段4字节若预期是VARCHAR(20)却显示key_len4说明只匹配了前4个字符rows值是MySQL估算的扫描行数不是实际返回行数但rows远大于filtered过滤后剩余百分比时说明索引选择性差需考虑添加更精确的WHERE条件或更换索引字段。3.2 覆盖索引让SELECT不用回表把IO降到最低在成绩查询场景中常需获取“学生姓名、课程名、成绩”三字段。若分别从student、course、score三表JOIN即使各表都有索引仍需多次回表查name字段。覆盖索引可一步到位-- 在score表上创建覆盖索引 CREATE INDEX idx_score_cover ON score(score_id, student_id, course_id, grade) INCLUDE (student_name, course_name); -- MySQL 8.0 支持INCLUDE语法或用联合索引包含所需字段 -- 查询时强制使用该索引 SELECT s.student_name, c.course_name, sc.grade FROM score sc JOIN student s ON sc.student_id s.student_id JOIN course c ON sc.course_id c.course_id WHERE sc.grade 90;此时EXPLAIN中Extra列显示Using index而非Using index condition证明所有字段均从索引页直接读取无需访问数据页。实验报告书中应截图对比Using index与Using where; Using join buffer的rows和cost值——这才是索引优化的量化证据。4. 避坑指南ZZU数据库实验报告书里90%学生踩过的5个具体坑写实验报告书不是拼SQL数量而是暴露并解决真实问题。以下是我在批改近三年ZZU数据库实验作业时统计出的最高频、最隐蔽、最容易被忽略的5个坑。每一条都来自真实翻车现场附带可复现的验证方法和修复命令。4.1 坑字符集混乱导致中文乱码报错却显示“Unknown column”现象建表时用CHARACTER SET utf8mb4但插入中文后SELECT显示问号更诡异的是执行ALTER TABLE student ADD COLUMN remark TEXT;报错ERROR 1054 (42S22): Unknown column remark in field list而字段明明刚加过。原因MySQL客户端连接字符集与服务端不一致。mysql命令行默认用latin1连接即使表是utf8mb4客户端发送的中文会被转成乱码再存入后续查询时MySQL解析失败误判字段名不存在。解决连接时显式指定字符集mysql -u root -p --default-character-setutf8mb4或在配置文件/etc/mysql/my.cnf中全局设置[client] default-character-set utf8mb4 [mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci验证命令SHOW VARIABLES LIKE character_set%;确保character_set_client、character_set_connection、character_set_results均为utf8mb4。4.2 坑外键约束名重复导致DROP TABLE失败现象在多个实验中反复创建enrollment表某次执行DROP TABLE enrollment;报错ERROR 1217 (HY000): Cannot delete or update a parent row: a foreign key constraint fails但SHOW CREATE TABLE enrollment;显示无外键。原因MySQL外键约束名在数据库内全局唯一。若之前建表时未显式命名约束如CONSTRAINT fk_enroll_course FOREIGN KEY...MySQL会自动生成类似enrollment_ibfk_1的名字。当重建同名表时新表可能继承旧约束名导致删除时残留依赖。解决建表时强制命名所有外键CONSTRAINT fk_enroll_stu FOREIGN KEY (student_id) REFERENCES student(student_id), CONSTRAINT fk_enroll_course FOREIGN KEY (course_id) REFERENCES course(course_id)删除前先查约束SELECT CONSTRAINT_NAME, TABLE_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMAyour_db AND REFERENCED_TABLE_NAME IS NOT NULL;手动删除约束ALTER TABLE enrollment DROP FOREIGN KEY fk_enroll_stu;4.3 坑TIMESTAMP默认值设为CURRENT_TIMESTAMP导致INSERT不指定该字段时报错现象建表时CREATE TABLE log (id INT, ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP);执行INSERT INTO log(id) VALUES(1);报错ERROR 1364 (HY000): Field ts doesnt have a default value。原因MySQL 5.7严格模式下TIMESTAMP字段若未显式声明NOT NULL且无默认值会拒绝NULL插入。DEFAULT CURRENT_TIMESTAMP仅对INSERT时未提供值生效但若字段本身允许NULLMySQL仍可能触发严格模式校验。解决显式声明NOT NULLts TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP或用DATETIME替代无此限制ts DATETIME DEFAULT CURRENT_TIMESTAMP检查模式SELECT sql_mode;若含STRICT_TRANS_TABLES必须按上述方式处理。4.4 坑GROUP BY与SELECT字段不匹配本地能跑线上报错现象在本地MySQL 5.7执行SELECT name, AVG(grade) FROM score GROUP BY student_id;成功但提交到ZZU实验平台MySQL 8.0报错ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause。原因MySQL 5.7默认关闭ONLY_FULL_GROUP_BY模式允许SELECT非GROUP BY字段MySQL 8.0默认开启强制要求SELECT所有非聚合字段必须出现在GROUP BY中。解决方案1推荐修正SQL让语义清晰SELECT s.name, AVG(sc.grade) FROM score sc JOIN student s ON sc.student_ids.student_id GROUP BY s.name方案2临时关闭模式仅限实验环境SET SESSION sql_mode(SELECT REPLACE(sql_mode,ONLY_FULL_GROUP_BY,));实验报告书中必须注明所用MySQL版本及sql_mode设置否则结论不可复现。4.5 坑mysqldump导出的SQL在另一台机器导入失败报错“Unknown collation: utf8mb4_0900_ai_ci”现象用mysqldump -u root -p zzu_exp backup.sql导出在同学电脑上执行mysql -u root -p zzu_exp backup.sql报错提示字符序不识别。原因utf8mb4_0900_ai_ci是MySQL 8.0新增字符序MySQL 5.7不支持。dump时未指定兼容性参数。解决导出时指定兼容版本mysqldump --compatiblemysql57 -u root -p zzu_exp backup.sql或强制指定字符集mysqldump --default-character-setutf8mb4 -u root -p zzu_exp backup.sql验证命令head -20 backup.sql | grep CHARSET确认CREATE TABLE语句中字符集声明为utf8mb4而非utf8mb4_0900_ai_ci。5. 实验报告书的终极验证用sysbench压测你的DDL/DML让性能数据替你说话一份合格的ZZU数据库实验报告书不能止步于“功能正确”必须回答“当100个学生同时选课你的事务能扛住吗”“当图书表有10万条记录你的索引能让查询稳定在50ms内吗”——这需要脱离IDE进入真实压力场景。我坚持用sysbench做最小化压测因为它不依赖应用层代码直接驱动MySQL协议结果可信度远超ab或curl。5.1 构建可复现的压测数据集从实验表结构生成百万级测试数据以enrollment表为例先用sysbench内置的oltp_read_write模板生成基础数据再用自定义脚本注入符合ZZU业务规则的数据# 步骤1初始化sysbench假设已安装 sysbench oltp_read_write \ --db-drivermysql \ --mysql-hostlocalhost \ --mysql-port3306 \ --mysql-userroot \ --mysql-passwordyourpass \ --mysql-dbzzu_exp \ --tables1 \ --table-size100000 \ prepare # 步骤2替换为真实enrollment表结构需提前在zzu_exp库中创建好表 # 注意sysbench默认表无外键需手动添加 ALTER TABLE sbtest1 ADD COLUMN student_id CHAR(10); ALTER TABLE sbtest1 ADD COLUMN course_id CHAR(10); ALTER TABLE sbtest1 ADD COLUMN semester CHAR(6); ALTER TABLE sbtest1 ADD COLUMN grade DECIMAL(3,1); ALTER TABLE sbtest1 DROP COLUMN k; ALTER TABLE sbtest1 DROP COLUMN c; -- 添加外键约束需确保student/course表已存在 ALTER TABLE sbtest1 ADD CONSTRAINT fk_sb_stu FOREIGN KEY (student_id) REFERENCES student(student_id);关键参数说明--table-size100000生成10万行测试数据模拟中等规模业务量--tables1只压测单表聚焦enrollment性能prepare阶段不执行压测仅建表填数耗时约2分钟SSD硬盘。5.2 设计贴近真实的压测场景混合读写事务边界ZZU选课系统的核心压力点是“高并发插入实时查询”因此压测脚本需模拟70%请求为INSERT模拟选课20%为SELECT COUNT(*)模拟查看已选人数10%为UPDATE模拟成绩录入所有操作包裹在START TRANSACTION ... COMMIT中。# 自定义lua脚本 custom.lua存于sysbench目录 -- 定义事务逻辑 function thread_init() drv sysbench.sql.driver() con drv:connect() end function event() -- 70%概率插入选课记录 if sysbench.rand.uniform(1, 100) 70 then con:query(START TRANSACTION) con:query(INSERT INTO enrollment (student_id, course_id, semester, grade) VALUES (S .. sysbench.rand.string(6) .. , C .. sysbench.rand.string(6) .. , 202301, NULL)) con:query(COMMIT) -- 20%概率查人数 elseif sysbench.rand.uniform(1, 100) 90 then con:query(SELECT COUNT(*) FROM enrollment WHERE semester 202301) -- 10%概率更新成绩 else con:query(UPDATE enrollment SET grade .. sysbench.rand.uniform(60, 100) .. WHERE id .. sysbench.rand.uniform(1, 100000)) end end执行压测sysbench custom.lua \ --db-drivermysql \ --mysql-hostlocalhost \ --mysql-port3306 \ --mysql-userroot \ --mysql-passwordyourpass \ --mysql-dbzzu_exp \ --threads32 \ --time120 \ --report-interval10 \ run解读结果重点关注transactions:行中的tps每秒事务数和latency (ms)中的95th percentile。若95th percentile超过200ms说明索引或事务设计有瓶颈若tps随--threads线性增长至32线程后陡降说明锁竞争严重——这些数据必须写入实验报告书的“性能分析”章节并对应到前面的索引优化或事务拆分建议中。5.3 把压测结果转化为报告书的硬核结论不是“性能良好”而是“TPS达128P95延迟142ms满足500人并发选课需求”我改掉的最后一版ZZU实验报告书删掉了所有“系统运行稳定”“响应速度较快”这类玄学描述。取而代之的是表格对比不同索引方案下的tps与P95 latency截图SHOW PROCESSLIST中长时间运行的SQL标注其State为Sending data还是Locked附上pt-query-digest分析慢查询日志的TOP3语句及优化建议。比如这样写“在32线程压测下原始enrollment表无复合索引TPS为42P95延迟为318ms添加INDEX idx_enroll_sem_stu (semester, student_id)后TPS提升至128P95降至142ms。瓶颈从磁盘IOiostat -x 1显示%util 95%转移至CPUtop显示mysqld占85%证实索引有效减少了随机读。”这种写法让报告书从“作业”变成“技术交付物”。它不承诺完美但每一行结论都有数据支撑每一个优化都有可复现路径。我带过的助教班里凡是按这个路子写的报告90%以上被选为优秀范例——不是因为代码多而是因为敢把失败的EXPLAIN截图贴出来敢写“这个索引在WHERE grade95时失效因为选择性太低”。希望帮到你。本文还有配套的精品资源点击获取
返回列表