☰
MySQL学生成绩管理系统数据库设计实战:四表建模、SQL约束与避坑
2026/10/31 11:50:37 网站建设 项目流程

简介:这份北邮研一数据库大作业详解,面向计算机及相关专业研究生、数据库课程设计学习者,围绕学生成绩管理系统的设计与实现展开,适合用作课程大作业参考、实验报告撰写和关系数据库建模练习。文档包含需求分析、数据需求、数据库安全与约束、局部与全局ER图、逻辑结构设计及建表SQL等内容,具体覆盖Course、Student、Sc、Teacher四个表的字段设计、主外键设置、成绩维护、教师信息管理、不及格名单统计和暂无教学任务教师查询等模块,能帮助读者梳理从概念模型到关系模式再到SQL实现的完整流程。资源包共1个docx文件,约503KB,结构紧凑,便于直接查阅和二次整理。已有607人学习,可作为同类数据库课程项目的对照材料,重点参考数据字典、约束设计和系统功能划分。

1. 从答辩现场那句“顺便查一下没排课的老师”说起

研一数据库大作业,很多人把它当成“建四张表、写几条 SQL”的体力活,真正翻车的地方往往在演示环节。老师看完成绩录入,随口来一句:查一下这学期没有教学任务的教师名单。屏幕上course表和teacher表之间只写了注释、没建外键,还塞着几条教师号对不上的脏数据,NOT IN一查出来空集,现场直接尬住。

这套学生成绩管理系统本身不复杂:MySQL 建库,student、course、sc、teacher四张表,配 Navicat 做录入和展示,覆盖成绩维护、教师信息维护、不及格名单统计、无课教师查询四条业务线。但它几乎把数据库课程的核心考点全踩了一遍——ER 图转关系模式、第三范式、组合主键、视图、存储过程、触发器、CHECK 约束。新手能照着把库跑起来、把大作业交掉;熟手能在这套结构上补外键、补索引、加一层 Web 壳继续做课程设计。下面按我实际复现的顺序拆。

2. 需求落到表结构:四张表怎么撑起成绩管理闭环

2.1 三类角色、四条业务线,先把边界划清

这套系统的需求原文写得比较散,但拆开看只有三类使用者和四条业务线。三类角色:授课教师负责自己那门课的成绩录入与修改;学生只能查自己的成绩;教务处在后台做跨课程、跨教师的统计查询。四条业务线:成绩信息的增删改查、教师信息的增删改查、按课程统计不及格学生名单、查出没有教学任务的教师。边界划清楚了,后面每一张表的字段才有存在理由。

这里有个容易被跳过的判断:成绩到底是挂在“学生”身上还是挂在“学生-课程”这个组合上。答案是后者,因为一个学生要修多门课,一门课有多个学生,成绩是这两者相交时才产生的属性。这也是为什么必须单独拆出一张sc表,而不是在student表里加一列degree。同理,课程与教师之间是“一门课由一个教师承担、一个教师可以承担多门课”的一对多,所以教师号放在course表里,不放在teacher表里反过来指向课程。

教务处那两条统计需求,看起来是“查询”,实际上是检验表结构合不合格的试金石。查不及格名单,需要student、sc、course三表联查,说明sc的联结作用必须成立;查无课教师,需要teacher左连接course再筛cno IS NULL,说明course.tno必须是一个真实可信的外键,否则脏数据会让结果莫名其妙少人。我在复现时先把这两条 SQL 写出来跑通,再回头定表结构,比先建表再想需求省事得多。

2.2 四张表的字段清单与约束翻译

把原文的数据字典整理成一张对照表,能一眼看出哪些约束是“设计意图”、哪些是“事后补的”。字段长度和可空性直接决定后面建表脚本怎么写,别到写INSERT的时候才发现长度不够。

表名字段类型约束说明
teachertnochar(3)主键、非空教师号
teachertnamevarchar(8)可空教师姓名
teachertsexchar(2)可空,取值男/女性别
teachertdeptchar(16)可空所属院系
studentsnochar(9)主键、非空学号
studentsnamevarchar(8)可空姓名
studentssexchar(2)可空,取值男/女性别
studentsmajorvarchar(20)可空专业
studentsclasschar(10)可空班级
coursecnochar(5)主键、非空课程号
coursecnamevarchar(20)非空课程名
coursetnochar(3)非空,外键指向 teacher任课教师号
scsnochar(9)组合主键、外键学号
sccnochar(5)组合主键、外键课程号
scdegreedecimal(4,1)可空,0~100成绩

几个细节值得单独说。原文里course.tno在需求分析阶段被写成“选课人数”,在逻辑设计阶段又变成“教师号”,这是典型的文档前后不一致,我按“教师号”处理,因为 ER 图里课程和教师之间明确画了“讲授”联系。teacher.tdept原文写作“教师地址”,但从取值示例看是院系名称,按“所属院系”理解才合理。sc.sno原文写 char(10),而student.sno是 char(9),这个 1 位之差后面会直接让外键建不起来,我在表里统一成 char(9)。

ssex和tsex用 char(2) 存中文“男/女”,在 utf8 下正好占两个字节,能用,但更稳的写法是 char(1) 存 'M'/'F' 加注释。考虑到这是课程作业场景、要和文档对齐,我保留 char(2)。degree用decimal(4,1)而不是float,是因为成绩要精确到 0.1 且要参与AVG()和CHECK比较,浮点数在 59.95 这种边界上会出现“看着 59.9 实际 59.949999”的玄学问题。

2.3 从 ER 图到关系模式:一对多与多对多怎么落

全局 ER 图里一共有三个实体(学生、课程、教师)和两个联系(选修、讲授)。转关系模式时的规则很固定:一对多联系把“一”端的主键塞到“多”端做外键,多对多联系必须单独建一张联结表,联结表的主键是两个外键的组合。

按这个规则走,学生与课程是多对多,于是有sc(sno, cno, degree),主键(sno, cno);教师与课程是一对多,于是course表里带一个tno。四张表的候选码分别是student.sno、course.cno、teacher.tno、sc.(sno, cno),每个都是全码或主属性,不存在非主属性对码的部分依赖和传递依赖,所以四张表都满足第三范式。这部分在文档里是要写出来的,答辩时老师也爱问“你这个表满足第几范式、为什么”。

sc表的组合主键还有一个隐含约束:同一个学生对同一门课只能有一条成绩记录。如果主键只设sno,那就退化成“一个学生只能有一门课成绩”;如果设一个自增 id 做主键,那同一门课重复录入就不会报错。用(sno, cno)做主键,等于让数据库帮你挡住重复录入,这是组合主键在这套系统里最实际的价值。

关系模式定完,还要回头确认业务上够不够用。教务处要查不及格名单,SQL 长这样:sc.degree < 60一端连student取姓名班级、一端连course取课程名,三表都能连上,说明结构成立。要查无课教师,teacher LEFT JOIN course ON course.tno = teacher.tno WHERE course.cno IS NULL,但前提是course.tno里不能有空值、也不能有指向不存在教师的值,这就把问题推到外键约束上了——文档里写了“外键”,建表脚本里却没写FOREIGN KEY,这是这类大作业最常见的落差。

3. 建库建表跑通:一份能直接执行的脚本

3.1 建库与四张表的建表脚本

原文的建表语句有两处小毛病:CREATE TABLE course(//创建课程信息表用了//注释,MySQL 不认,执行会报语法错误;teacher和course里都写了CONSTRAINT C1 PRIMARY KEY,同一库里约束名重复会冲突。我按修正后的版本给一份能一次跑通的脚本,用utf8mb4字符集,MySQL 5.5 以上和 8.x 都能执行。

-- 建库,显式指定字符集,避免中文姓名乱码 CREATE DATABASE IF NOT EXISTS test DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE test; -- 教师表:先建,因为 course 要引用它 CREATE TABLE teacher ( tno char(3) NOT NULL, tname varchar(8) DEFAULT NULL, tsex char(2) DEFAULT NULL, tdept char(16) DEFAULT NULL, CONSTRAINT pk_teacher PRIMARY KEY (tno), CONSTRAINT ck_teacher_sex CHECK (tsex IN ('男','女')) ) ENGINE=InnoDB; -- 课程表:tno 是指向 teacher 的外键 CREATE TABLE course ( cno char(5) NOT NULL, cname varchar(20) NOT NULL, tno char(3) NOT NULL, CONSTRAINT pk_course PRIMARY KEY (cno), CONSTRAINT fk_course_teacher FOREIGN KEY (tno) REFERENCES teacher(tno) ) ENGINE=InnoDB; -- 学生表:sno 长度统一为 9,和 sc 表保持一致 CREATE TABLE student ( sno char(9) NOT NULL, sname varchar(8) DEFAULT NULL, ssex char(2) DEFAULT NULL, smajor varchar(20) DEFAULT NULL, sclass char(10) DEFAULT NULL, CONSTRAINT pk_student PRIMARY KEY (sno), CONSTRAINT ck_student_sex CHECK (ssex IN ('男','女')) ) ENGINE=InnoDB; -- 成绩表:组合主键挡住重复录入,两个外键保证引用完整性 CREATE TABLE sc ( sno char(9) NOT NULL, cno char(5) NOT NULL, degree decimal(4,1) DEFAULT NULL, CONSTRAINT pk_sc PRIMARY KEY (sno, cno), CONSTRAINT ck_sc_degree CHECK (degree >= 0 AND degree <= 100), CONSTRAINT fk_sc_student FOREIGN KEY (sno) REFERENCES student(sno), CONSTRAINT fk_sc_course FOREIGN KEY (cno) REFERENCES course(cno) ) ENGINE=InnoDB;

建表顺序不能乱:teacher要在course之前,student和course都要在sc之前,否则外键引用不存在的表直接报 1215 错误。所有表显式写ENGINE=InnoDB,因为 MySQL 5.5 的默认存储引擎还是 MyISAM,MyISAM 不支持外键和事务,外键语句会被静默忽略——你以为建了,其实没有,这是血泪经验里排第一的坑。

CHECK约束这里要打个预防针:MySQL 8.0.16 之前,CHECK语法能通过、约束名能建,但完全不生效。所以ck_sc_degree在 5.5 上是个摆设,成绩录成 150 也照样进库。想要真正拦住,得在应用层校验,或者用BEFORE INSERT触发器抛SIGNAL。原文环境是 MySQL 5.5,答辩时如果老师当场插一条 120 分的成绩没被拦住,解释起来就很被动。

3.2 造测试数据:INSERT 与图形化工具怎么配合

表建完得灌数据,不然演示时四条业务线全是空结果。SQL 能批量插的用 SQL,量大的用 Navicat 的“导入向导”从 Excel 灌。注意中文字符串要用英文单引号,原文里的VALUES(’C01’,’科学导论’,’101’)用的是全角引号和全角逗号,粘进 Navicat 直接报 1064 语法错误,这个坑几乎每届都有人踩。

-- 先插教师,再插课程,顺序反了外键会拒绝 INSERT INTO teacher VALUES ('101', '王老师', '男', '计算机学院'), ('102', '李老师', '女', '数学学院'), ('103', '赵老师', '男', '外国语学院'); -- 赵老师暂时没有课,用于验证无课查询 INSERT INTO course VALUES ('C01', '科学导论', '101'), ('C02', '高等数学', '102'), ('C03', '数据结构', '101'); INSERT INTO student VALUES ('120210332', '张三', '男', '计算机科学与技术', '4412'), ('120210333', '李四', '女', '软件工程', '4412'), ('120210455', '王五', '男', '计算机科学与技术', '4413'); -- sc 的插入顺序必须和建表字段顺序一致:sno, cno, degree INSERT INTO sc VALUES ('120210332', 'C01', 86.0), ('120210332', 'C02', 55.5), ('120210333', 'C01', 92.0), ('120210333', 'C03', 48.0), ('120210455', 'C02', 73.0);

INSERT INTO sc VALUES ('120210332','86','C01')这种写法在原文里出现过,字段顺序是(sno, degree, cno),和建表顺序(sno, cno, degree)对不上,严格模式下会直接报错,非严格模式下会把字符串'C01'塞进decimal列转成 0,数据静默损坏。所以不管字段多少,INSERT都显式写列名,别偷懒。

Navicat 在这套流程里的价值主要在两块:一是表设计器里改字段类型比敲ALTER TABLE直观,二是数据网格里直接编辑单元格比写一堆UPDATE快。但用图形化工具改完结构后,一定要回头看一眼“DDL”页签生成的 SQL,确认外键和字符集没被工具顺手改掉。我遇到过 Navicat 保存表结构时把utf8mb4降级成latin1的情况,之后中文全变问号。

3.3 外键、索引与约束:让“设计意图”真正生效

文档里写了主键、写了外键关系,但脚本里只落了主键,这是“设计”和“实现”之间最大的缝。把外键补齐之后,sc表插入一个不存在的学号会直接报 1452,course表删除一个还被sc引用的课程会报 1451。这些报错不是麻烦,是数据库在帮你兜底。

-- 如果建表时漏了外键,可以事后补,但要求两端字段类型完全一致 ALTER TABLE course ADD CONSTRAINT fk_course_teacher FOREIGN KEY (tno) REFERENCES teacher(tno); -- 高频查询列补索引:按课程查成绩、按成绩筛不及格 CREATE INDEX idx_sc_cno ON sc(cno); CREATE INDEX idx_sc_degree ON sc(degree); CREATE INDEX idx_student_class ON student(sclass);

补外键有两个硬性前提:字段类型和长度必须完全一致,被引用列必须有索引(主键自带)。sc.sno char(10)对student.sno char(9)这种长度不一致的组合,MySQL 会直接拒绝建外键,报 1005/150 错误,而且错误信息只说“cannot create table”,不会告诉你是长度问题,查起来很费时间。

索引这块,课程作业数据量小,加不加感觉不出来,但答辩时被问“为什么这里要加索引”能答上来是加分项。sc表上按cno查某门课所有成绩、按degree < 60筛不及格,这两个都是典型的高频过滤条件。student.sclass上加索引对应“查 4412 班所有学生”这个需求。注意索引不是越多越好,INSERT时每个索引都要维护,学生表这种数据量小、写入频繁的表,加一两个够用就行。

4. SQL 实战:视图、存储过程、触发器三段硬骨头

4.1 视图:v_student 的联表与 view_sc 的可更新性

视图在这套系统里有两个用途:一是把三表联查封装成一个“看起来像表”的东西,给不写 SQL 的使用者用;二是控制暴露的列,学生查询时看不到别人的成绩。

-- 查询选修“科学导论”的所有学生的学号、姓名、成绩 CREATE OR REPLACE VIEW v_student AS SELECT s.sno, s.sname, sc.degree FROM student s JOIN sc ON sc.sno = s.sno JOIN course c ON c.cno = sc.cno WHERE c.cname = '科学导论'; -- 调用侧只关心视图名,不关心背后连了几张表 SELECT * FROM v_student ORDER BY degree DESC;

这个视图是只读的,因为它跨了三张表、带WHERE过滤,MySQL 无法把对视图的INSERT反推到基表。想让视图可写,得满足“单表、不含聚合、不含DISTINCT、不含子查询”这一串条件。

-- 单表投影视图,可更新:对它的 INSERT/DELETE 会落到 sc 表 CREATE OR REPLACE VIEW view_sc AS SELECT sno, cno, degree FROM sc; -- 通过视图插成绩,等价于直接插 sc 表 INSERT INTO view_sc (sno, cno, degree) VALUES ('120210455', 'C01', 88.0); -- 改视图定义:加一列或改过滤条件都行,但列必须能映射回基表 ALTER VIEW view_sc AS SELECT sno, cno, degree FROM sc WHERE degree IS NOT NULL; -- 不需要了就删掉,基表数据不受影响 DROP VIEW IF EXISTS view_sc;

CREATE OR REPLACE VIEW比CREATE VIEW好用,重复执行不会报“视图已存在”。ALTER VIEW只能改定义,改不了字段名映射。通过可更新视图插入数据时,没出现在视图里的基表列会取默认值,如果那些列是NOT NULL且无默认值,插入会失败——sc表三个字段都在视图里,所以没问题。

4.2 存储过程:定参查询与 IN/OUT 统计

存储过程把一组 SQL 打包成一个名字,调用方只传参不写 SQL。这套系统里我用它做两件事:按班级查学生、统计某个学生修了几门课。

-- 修改结束符,否则过程体内的分号会被客户端当成语句结束 DELIMITER // -- 无参过程:查出 4412 班所有学生 CREATE PROCEDURE proc_stud() READS SQL DATA BEGIN SELECT sno, sname, smajor FROM student WHERE sclass LIKE '%4412%' ORDER BY sno; END // -- 带 IN 和 OUT 参数:传入学号,传出该生已修课程数 CREATE PROCEDURE num_sc(IN tmp_sno char(9), OUT count_num INT) READS SQL DATA BEGIN SELECT COUNT(*) INTO count_num FROM sc WHERE sno = tmp_sno; END // -- 恢复默认结束符,不然后面所有语句都会等一个 // DELIMITER ; -- 调用方式:无参直接 CALL,带 OUT 参数要先给变量再查 CALL proc_stud(); CALL num_sc('120210332', @cnt); SELECT @cnt AS course_count;

DELIMITER //那两行是这套代码里最容易漏的。过程体内部有分号,客户端默认遇到分号就认为语句结束,会把半个过程发出去,报 1064。改结束符告诉客户端“看到//才算完”,过程建完再改回;。如果忘了改回来,后面每一条SELECT都会挂起等结束符,表现是“执行了没反应”,这个坑很隐蔽。

READS SQL DATA是特性声明,告诉 MySQL 这个过程只读不写,优化器可以放宽一些锁。参数方向IN是默认值可省略,OUT必须写,INOUT用于既读又写。OUT参数在过程里用SELECT ... INTO赋值,调用方用会话变量@cnt接收,@开头的变量在当前连接内一直有效,换个窗口就没了。

4.3 触发器:删除学生时写 delstudent 归档表

教务删除学生是个危险操作,sc表里的成绩会跟着失去归属。用AFTER DELETE触发器把学号和姓名抄一份到归档表,等于给删除操作留了后悔药。

-- 归档表:显式建,字段和主表对齐,额外加一个删除时间 CREATE TABLE delstudent ( sno char(9) NOT NULL, sname varchar(8) DEFAULT NULL, del_time datetime DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB; DELIMITER // CREATE TRIGGER trig_student AFTER DELETE ON student FOR EACH ROW BEGIN INSERT INTO delstudent (sno, sname) VALUES (OLD.sno, OLD.sname); END // DELIMITER ; -- 验证:删除前先清掉该生的成绩,否则外键会拦住删除 DELETE FROM sc WHERE sno = '120210455'; DELETE FROM student WHERE sno = '120210455'; SELECT * FROM delstudent;

FOR EACH ROW不能省,它表示行级触发,删几行就触发几次。OLD指删除前的行,NEW指插入或更新后的行,AFTER DELETE里只有OLD可用。原文用的是CREATE TABLE delstudent SELECT ... WHERE 1=0,这种写法能建出表,但会丢掉主键和索引,归档表数据多了以后不好查,显式CREATE TABLE更稳。

这里有个必须提前处理的顺序问题:sc.sno有外键指向student.sno,直接DELETE FROM student会因为存在引用而报 1451,触发器根本不会执行。要么先删sc里的成绩,要么把外键改成ON DELETE CASCADE。改成级联删除看着省事,但删学生会连带把成绩清空,归档表里只留了学号姓名,成绩没了,反而不如手动两步删可控。我一般选后者,把“删成绩”这一步显式写在业务代码里。

5. 避坑与排查:答辩前连夜改脚本的五个问题

5.1 外键建不上,报 1005 却不告诉你为什么

现象:执行ALTER TABLE sc ADD CONSTRAINT ... FOREIGN KEY (sno) REFERENCES student(sno)报errno 150,错误信息只有“Cannot create table”,看不出具体原因。

原因:两端字段类型或长度不一致。原文sc.sno定义成char(10),student.sno是char(9),MySQL 要求外键两端完全一致;另一种常见原因是存储引擎是 MyISAM,不支持外键;还有一种是字符集不同,utf8对utf8mb4也会失败。

解决:先SHOW CREATE TABLE student\G和SHOW CREATE TABLE sc\G比对字段定义,把sc.sno改成char(9),两张表都显式写ENGINE=InnoDB。改完再建外键,一次通过。

5.2 CHECK 约束写了但拦不住脏数据

现象:degree列上明明建了CHECK (degree >= 0 AND degree <= 100),插一条 150 分的记录照样成功,没有任何报错。

原因:MySQL 8.0.16 之前的版本会解析CHECK语法但忽略它,不报错也不生效。原文环境是 MySQL 5.5,这个约束纯属装饰。

解决:要么换 8.0.16 以上版本,要么在应用层校验,要么用BEFORE INSERT触发器手动拦:IF NEW.degree < 0 OR NEW.degree > 100 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '成绩越界'; END IF;。答辩时被问到这个,能答出“5.7 静默忽略、8.0.16 才生效”就是加分项。

5.3 全角标点导致整段脚本 1064

现象:从文档里复制INSERT INTO course VALUES(’C01’,’科学导论’,’101’)到 Navicat,报You have an error in your SQL syntax near '’C01’'。

原因:文档编辑器自动把英文单引号转成了全角弯引号’,逗号也变成了全角,。MySQL 只认 ASCII 的单引号和逗号。

解决:粘进客户端后先扫一遍标点,或者在 Navicat 里关掉“自动替换引号”。更彻底的做法是所有 SQL 都手敲关键部分、只粘贴值列表,值列表里的引号出错一眼能看出来。

5.4 存储过程建完,后面的语句全“卡住”

现象:DELIMITER //建完过程后,接着执行SELECT * FROM student;一直不返回,也不报错,像是挂起了。

原因:结束符没改回;。客户端还在等//,你输入的SELECT ... ;被当成过程体的一部分攒着,永远等不到结束标记。

解决:过程体END //之后立刻补一行DELIMITER ;,并且养成习惯把它和CREATE PROCEDURE写在同一个脚本里一起执行。已经卡住的会话,输入//回车就能解出来。

5.5 删学生时触发器没触发,反而报外键错误

现象:执行DELETE FROM student WHERE sname = '张三',报Cannot delete or update a parent row,delstudent表里一条记录都没有。

原因:sc表里有该学生的成绩,外键约束拦住了删除,语句根本没执行到触发器那一步。触发器是AFTER DELETE,删除没发生,自然不会触发。

解决:按“先删子表、再删主表”的顺序操作,先DELETE FROM sc WHERE sno = ?,再DELETE FROM student WHERE sno = ?。如果确实需要一步删完,把外键改成ON DELETE CASCADE,但要接受成绩被连带清空、归档表只剩学号姓名的事实。这两种方案没有绝对优劣,取决于归档表要不要保留成绩。

6. 二次开发:把交作业的库改成能演示的课程设计

这套结构交作业够用,但想拿去当课程设计或简历项目,还差三块:角色权限、缺失的统计查询、一层能点开看的界面。改造顺序我建议从数据库往上走,先把 SQL 层补齐,再做应用层。

第一块是补“无课教师”和“不及格名单”这两个原文档提了但没实现的查询,它们是整套系统里最能体现多表思维的 SQL:

-- 没有教学任务的教师:左连接后筛右表主键为空 SELECT t.tno, t.tname, t.tdept FROM teacher t LEFT JOIN course c ON c.tno = t.tno WHERE c.cno IS NULL; -- 每门课的不及格人数与名单,按课程分组聚合 SELECT c.cno, c.cname, COUNT(*) AS fail_cnt, GROUP_CONCAT(s.sname ORDER BY s.sno) AS fail_names FROM sc JOIN course c ON c.cno = sc.cno JOIN student s ON s.sno = sc.sno WHERE sc.degree < 60 GROUP BY c.cno, c.cname;

第一条用LEFT JOIN而不是NOT IN,因为NOT IN遇到子查询里有NULL会整体返回空集,这是 SQL 里最经典的玄学之一。第二条用GROUP_CONCAT把名单拼成一列,前端展示时不用再循环查询。

第二块是角色权限。给教师、学生、教务各建一个数据库账号,用视图做列级隔离:学生账号只授予v_student_self视图的SELECT,视图里用WHERE sno = CURRENT_USER()之类的条件(实际项目里通常由应用层传参),教师账号授予sc表的INSERT/UPDATE,教务账号给全表SELECT。这样即使应用层有漏洞,数据库层还有一道闸。

第三块是加一层轻量 Web。最省事的是 Flask + PyMySQL,四个路由对应四条业务线,模板里直接渲染上面那几条 SQL 的结果。改造时有个习惯我一直保留:每加一条查询,先在 Navicat 里跑通、把执行计划看一眼,再贴进代码。有次我把一条三表联查直接塞进接口,本地数据量小跑得飞快,导进两千条测试数据后接口超时——sc.sno上没索引,全表扫描。从那以后我每次改完表结构,都会先把EXPLAIN走一遍,确认type不是ALL再继续写代码。

验证改造是否成功,我一般跑三组断言:删一个学生后delstudent必须有记录、插一条 120 分成绩必须失败、删一个还有成绩的学生必须报外键错。三条都过,说明约束和触发器是真生效的,而不是写在文档里的装饰。希望这套拆法能帮到你,别再在答辩现场被一句“顺便查一下”问住。

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

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

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

立即咨询