简介《学生学籍管理系统.doc》面向计算机专业课程设计、数据库课程实践及教务信息化入门学习者围绕学生基本信息、学籍与成绩数据管理提供从需求分析到数据库落地的文档化设计参考。压缩包共1个doc文件约295KB以单文档形式组织便于通读、摘录与二次整理。文档已有12957人学习下载内容覆盖项目背景、四阶段开发计划以及教师、学生、课程、班级、成绩等管理模块和教务处管理员、学生两类用户的认证与权限划分。数据库部分给出学院、专业、年级、班级、学生、课程、教师等实体关系含一对多、多对多联系与E-R图说明并列出学生信息表、课程数据表、选课表、教师任课表等关系模式集结合SQL Server 2005讨论逻辑结构、物理结构及字段与主键设置可供课程设计文档撰写和学籍管理数据库原型搭建参考。1. 从一份 .doc 到能跑起来的库这个学生学籍管理系统交付了什么拿到一份只有 .doc 的交付物多数人的第一反应是往下翻找代码翻不到就判定它没价值。这份学生学籍管理系统文档值得多看两眼需求分析、E-R 图、九组关系模式、十一张物理表、三个索引、十个视图、五个存储过程、十三个触发器末尾还附了一段能在 SQL Server 2005 上逐段执行的建库脚本。它本质上是一份数据库课程设计的完整交付也是一套可以直接照抄结构的教务库骨架。适合三类人要交课设但不想套模板的学生、要给小机构搭学籍和成绩库的后端开发、以及想练手视图、存储过程、触发器三件套的人。下面按我实际拆库的顺序走一遍。2. 需求落到关系模式七个实体怎么变成十一张表2.1 只有两类角色权限却决定了整张账号表长什么样文档把使用者限定成两种教务处管理员和学生。管理员进来能增删改查全院数据学生进来只能看自己那一行。这个粒度很关键它决定了后面要不要单独造权限表。我的判断是不用学生查询本人在student表里靠sno一个条件就能过滤掉管理员则走全表权限判断放在应用层的一个if role admin上就够了。真要为这点需求上 RBAC 五张表属于给自己找事。问题出在物理设计这一层。需求写得很正经——“用户必须通过认证才能登入系统”“系统能够自动识别用户类型”“给不同用户分配不同权限”可落地的账号表只有两列-- 原文的账号表只有账号和操作两个字段 create table system( zhanghao char(10) primary key not null, caozuo varchar(20) not null )这张表撑不起前面那三条需求。没有密码字段认证无从谈起没有角色字段系统认不出进来的是管理员还是学生没有sno关联学生登录后不知道该查谁。字段名用拼音zhanghao、caozuo也是一处败笔后面写 DAO 层时每个字段都得在脑子里翻译一遍改一次名要动三四个文件。我一般会直接换成下面这个结构多三列就能把认证、角色、归属全兜住-- 改造后的账号表认证、角色、归属三件事一次说清 create table account( username varchar(32) primary key, -- 登录名 password_hash varchar(128) not null, -- 存哈希不存明文 role varchar(10) not null, -- admin 或 student sno char(10) null, -- 学生账号才有指向 student.sno status tinyint not null default 1, -- 1 启用 0 停用 constraint ck_role check (role in (admin,student)), foreign key (sno) references student(sno) )role上的check约束是我习惯加的脏数据在入库那一刻就被拦掉比在业务代码里到处写if省心。sno允许为空因为管理员账号不挂学生外键指向student(sno)意味着学生必须先建档、再开账号这个顺序在批量导入新生时要留意。2.2 E-R 里的六组关系两组多对多是全篇的重点文档列了七个实体学院、专业、年级、班级、学生、课程、教师。关系一共六组其中四组是一对多两组是多对多。一对多的处理很直接把“一”那端的主键塞到“多”那端当外键专业表存学院名、班级表存专业名、学生表存班级名。多对多就必须造中间表这是关系库里没有捷径的地方。第一组多对多是学生和课程。文档在这里做了一个我认为很到位的判断成绩不是学生的属性也不是课程的属性而是“这个学生选这门课”这件事的属性所以score挂在selectcourse上主键是(sno, cno)的联合主键。这句话看着像常识但我在实际项目里见过太多把score直接塞进student表的写法一学期下来学生表宽到几十列加一门课就要alter table那才叫真的痛。第二组多对多是教师和课程落在teachercourse(cno, teacherID)上联合主键表达“这位老师能教这门课”。注意它和selectcourse不是一回事前者是排课能力后者是选课结果。selectcourse里另外存了一个teacherID意思是“这个学生这门课是被哪位老师教的”。这个字段加上之后主键仍然是(sno, cno)于是在数据库层面定死了一条规则——一个学生对一门课只能有一条成绩记录。这条规则在教务场景里迟早要撞墙。学生挂科重修怎么办覆盖原成绩历史就没了想留两次记录主键直接冲突报 2627。合格的改法是把主键扩成(sno, cno, term)加一个学期字段把“一次选课”的粒度从“一门课”细化到“一学期的一门课”。这是二次开发时我第一个会动的地方。2.3 建表顺序外键是一条链顺序错了脚本必然跑不动这份文档有一处做得比多数课设好建表语句的排列顺序是对的。college在最前因为它是所有外键的源头major紧跟其后引用collegegrades和classes再引用这两个student、course、depart在中间层teachers要等depart建完selectcourse和teachercourse在最后因为它们要引用三张表。按依赖关系排下来就是一条单链我把它整理成了下面这张表照着顺序执行基本不会碰到“引用的对象不存在”这类 1753 错误顺序表名依赖的上游表说明1college无学院外键源头2majorcollege专业归属学院3gradescollege、major年级4classescollege、major、grades班级5studentcollege、major、grades、classes学籍主表6coursecollege课程7departcollege教研室8teacherscollege、depart教师9selectcoursestudent、course、teachers选课成绩10teachercoursecourse、teachers教师任课11accountstudent登录账号改造后新增这里有个设计层面的隐患需要提前说清。grades表把grade单独设成了主键字段类型int装的是“2024”这种年份数字。主键唯一意味着全校只能有一个 2024 级可现实里计算机学院的 2024 级和外语学院的 2024 级是两个不同的集合。这条在插入第二条同年级记录时会直接炸具体现象和改法留到第 5 章细讲。3. 建库脚本拆解SQL Server 2005 上的一次完整落地3.1 建库语句里那几个数字决定了后面会不会半夜被叫起来原文的建库脚本不算长但每个参数都值得抠一遍-- 建库主数据文件 3MB 起步日志 1MB 起步、上限 2MB、每次涨 10% create database studentmanagesystem on primary( name studentmanagement, -- 逻辑名和库名不一致 filename D:\DATA\studentmanagesystem.mdf, size 3, maxsize unlimited, filegrowth 1 ) log on( name studentmanagesystem_log, filename D:\DATA\studentmanagesystem_log.ldf, size 1, maxsize 2, -- 日志最多 2MB filegrowth 10% )size 3的单位是 MBfilegrowth 1也是 MB意思是主数据文件每次不够用就长 1MB。日志那边maxsize 2是这份脚本里最要命的一笔——日志上限只有 2MB而 SQL Server 的日志在完整恢复模式下要等事务日志备份才能截断。往selectcourse里批量插几千条成绩日志写到 2MB 就再也涨不上去报错 9002“数据库的事务日志已满”。做课设时数据量小看不出来一旦拿去做真实数据导入这个坑一定踩。还有两处细节逻辑名studentmanagement和数据库名studentmanagesystem差一个字母后面做备份还原、写连接串时很容易抓错filename指向D:\DATA\这个目录在干净的机器上大概率不存在create database会直接失败。我在自己机器上跑这套脚本时习惯先把前两行改成-- 稳妥一点的建库参数目录先建好日志给足空间 create database studentmanagesystem on primary( name studentmanagesystem, filename D:\DATA\studentmanagesystem.mdf, size 50MB, filegrowth 64MB ) log on( name studentmanagesystem_log, filename D:\DATA\studentmanagesystem_log.ldf, size 32MB, maxsize 2GB, filegrowth 64MB )按固定 MB 增长而不是按百分比增长是我在数据文件上吃过亏之后养成的习惯。百分比增长在库变大之后一次要扩几百 MB那一下的写阻塞足够让前端接口超时。日得小库无所谓但习惯早点养好。3.2 十一张表里真正需要细看的是 student 和 selectcourse物理设计部分给了每张表的字段类型和长度我挑两张最有代表性的看。学生表student一共九个字段主键sno char(10)学院名、专业名、年级、班级四个外键全部指向维度表create table student( sno char(10) primary key not null, -- 学号定长 10 位 studentname varchar(10) not null, -- 姓名 sex char(2), -- 男/女char(2) 刚好 sage smallint, -- 年龄 collegename varchar(20), majorname varchar(20), grade int, class char(10), tel varchar(16), foreign key (collegename) references college(collegename), foreign key (majorname) references major(majorname), foreign key (grade) references grades(grade), foreign key (class) references classes(class) )char(10)装学号是合理的学号长度固定定长类型在索引上比varchar更紧凑。studentname varchar(10)只给 10 个字符遇到四个字的少数民族姓名加上空格就悬了我一般给到 30。tel varchar(16)装手机号够用但要留意有人填带区号的座机。四个外键全部指向维度表这套设计的好处是查询时不用多层 join 就能拿到学院和专业代价是学院改名要级联更新四张表而原文并没有配on update cascade。选课成绩表才是真正承重的一张create table selectcourse( sno char(10) not null, cno char(10) not null, teacherID varchar(10), score int, -- 成绩整数 primary key (sno, cno), -- 一个学生一门课只一条 foreign key (sno) references student(sno), foreign key (cno) references course(cno), foreign key (teacherID) references teachers(teacherID) )三个外键、一个联合主键结构没问题。score用int是个可以商量的选择百分制整数够用但要做绩点换算、加权平均时整数会丢精度我倾向decimal(5,2)。另外score允许为空这个留白是必要的——选课记录先插入、成绩后录入空值就代表“还没考”。如果把它设成not null default 0那就分不清“考了 0 分”和“没考”了统计及格率时会算错分母。3.3 索引和视图三个索引两个都白建视图里有一半能直接用原文建了三个索引全是非聚集索引create nonclustered index score_index on selectcourse(score desc); create nonclustered index student_sage_index on student(sage desc); create nonclustered index teachers_sage_index on teachers(sage desc);后两个索引基本是白建。学生表里按年龄查的场景几乎不存在教务系统查的是学号、姓名、班级年龄这种只有几十个不同取值、分布又极度集中的列选择性太差优化器八成会直接忽略它走全表扫描。成绩索引稍微有点用但desc排序在没有top或分页配合时也用不上。真要加索引我会选这几个方向student(class)、student(majorname, grade)、selectcourse(cno)——最后这个是因为“查某门课所有成绩”是高频查询而联合主键(sno, cno)的前缀是sno按cno单独查走不到索引。视图部分文档写了十个按用途可以分三类。第一类是纯投影比如student_view就是select * from student作用是给应用层一个稳定入口后面表加字段了改视图不用改代码。第二类是跨表拼装selectcourse_s把选课表和班级视图连起来让“查某个班所有人的成绩”变成一次单表查询。第三类是分组统计avg_s算每个学生的平均分-- 每个学生的平均成绩视图列名必须显式给出 create view avg_s(sno, grade, class, gavg) as select sc.sno, cs.grade, cs.class, avg(sc.score) from selectcourse sc join class_s cs on sc.sno cs.sno group by cs.grade, cs.class, sc.sno写这类视图有一条硬规则聚合列必须带别名或者像上面这样在视图名后面把列清单列全。SQL Server 对视图的每一列都要求有确定的名字avg(score)这种表达式不给名字create view会直接失败。原文那一版正好踩了这条第 5 章会展开。4. 存储过程与触发器把业务规则写进数据库4.1 五个存储过程覆盖了教务查询的八成场景文档写了五个存储过程思路是对的把高频且固定的多表连接封进数据库应用层只传参数。第一个scoreproc是最典型的输入学号输出该生所有课程的成绩和学分-- 按学号查成绩单三表连接输入一个参数 create procedure scoreproc sno char(10) as begin set nocount on; -- 少传一行影响行数省网络往返 select s.sno, s.studentname, c.coursename, sc.score, c.credit from student s join selectcourse sc on s.sno sc.sno join course c on c.cno sc.cno where s.sno sno; end调用就是exec scoreproc 2024010101。这个过程的逻辑很干净以selectcourse为事实表两边连维度表输出结果直接就是一页成绩单。我唯一会加的是set nocount on它抑制“影响 N 行”的返回消息批量调用时能省掉不少无谓的网络包。第二个avgscoreproc用到了输出参数一次返回平均分和选课人次两个值-- 按课程名统计平均分和选课人次通过 output 参数带回 create procedure avgscoreproc cname varchar(20), avg decimal(5,2) output, count smallint output as begin set nocount on; select avg avg(score), count count(*) from course c join selectcourse sc on c.cno sc.cno where c.coursename cname; end输出参数这种写法在 CJK 场景下有个小麻烦cname是课程名可能重复不同学院开同名课一旦重复上面这个查询会把两门课的人混在一起算。要严谨就得再加学院参数或者干脆用课程号当入口。调用侧需要先declare两个变量再接outputdeclare a decimal(5,2), b smallint; exec avgscoreproc 数据结构, a output, b output; select a as 平均分, b as 选课人次;剩下三个过程走的是同一个套路majorproc按学院统计专业数和专业清单classproc按“学院专业年级班级”四要素定位一个班并统计人数teacherproc按教师姓名反查工号、院系、教研室和所教课程。这五个加起来基本覆盖了教务日常的查询侧。写侧增删改文档没封存储过程全部交给应用层拼 SQL这在课设里够用真上线时我至少会把“录入成绩”封成过程因为它涉及校验、更新、可能触发重算放在一个事务里更稳。4.2 触发器分两类一类做级联同步一类做非法数据拦截文档列了十三个触发器数量看着吓人按意图其实只有两类。第一类是级联同步解决“主表主键改了子表外键没跟着改”的问题。比如课程被删掉选课表里指向它的记录就该一起清掉-- 课程删除后级联清掉对应的选课成绩记录 create trigger ctrig_course_delete on course after delete as begin set nocount on; delete sc from selectcourse sc join deleted d on sc.cno d.cno; -- deleted 是本次删除的旧行 end这里的关键是deleted和inserted这两张逻辑表。after delete触发器里deleted装着刚被删掉的行after update触发器里deleted是旧值、inserted是新值after insert里只有inserted。理解这一点所有触发器都能读懂。上面对course表删除的级联本质上和on delete cascade外键是一个效果用触发器实现等于手工重造了数据库自带的功能不如直接在外键上写on delete cascade省事。第二类是合法性拦截比如往选课表插记录时学号或课程号在库里根本不存在就该拒掉-- 插入前校验学号和课程号是否存在不存在就回滚 create trigger check_selectcourse_insert on selectcourse instead of insert as begin set nocount on; insert into selectcourse(sno, cno, teacherID, score) select i.sno, i.cno, i.teacherID, i.score from inserted i where exists (select 1 from student s where s.sno i.sno) and exists (select 1 from course c where c.cno i.cno); end这里我用了instead of insert而不是after insert rollback。区别很实际after触发器里执行rollback会把整个外层事务一起回滚如果应用层在一个事务里连着插十条一条不合法就全没了报错信息还很难定位instead of是“替换掉这次插入”不合法的行静默跳过合法行正常入库。两条路各有取舍但instead of的副作用更可控。4.3 认证与权限该放应用层别硬塞进数据库文档把“用户认证”“识别用户类型”“分配不同权限”写在需求里但物理设计只给了一张两列的system表存储过程和触发器里也没有任何登录相关的逻辑。这个缺口需要明确补在哪一层。我的做法是分工数据库只管“存账号和角色”认证逻辑密码校验、会话、超时全放应用层。理由很实在——数据库里没有安全的密码哈希函数SQL Server 2005 时代更是如此在存储过程里做明文比对等于把密码写在日志里会话状态也没法存在表里否则每次请求都要查库。应用层拿到底层连接之后最小可用的一条链路是这样import pyodbc # 连接串里的驱动名按本机实际装的版本改2016 之后建议用 ODBC Driver 17 conn pyodbc.connect( DRIVER{ODBC Driver 17 for SQL Server}; SERVER127.0.0.1;DATABASEstudentmanagesystem; UIDapp_user;PWD******; ) cur conn.cursor() # 调用存储过程参数用占位符别拼字符串 cur.execute(EXEC scoreproc ?, 2024010101) for row in cur.fetchall(): print(row)用?占位符而不是字符串拼接这一条是我在安全上最在意的习惯。学号这种输入拼接进 SQL 就是注入入口而存储过程本身并不防注入防注入的永远是参数化。数据库账号也要单独开一个app_user只给execute权限和必要的select别拿sa直接连。原文脚本里所有对象都建在默认架构下没有做任何权限隔离这一步在做课设时会被忽略但它是从“能跑”到“能给别人用”的分界线。5. 避坑与排查这份脚本直接照抄会翻车的五个地方5.1 建库阶段路径、日志上限和主键设计现象一执行create database立刻报错“操作系统错误 3系统找不到指定的路径”或“错误 5拒绝访问”。原因有两层。第一层是filename D:\DATA\...里的目录根本不存在SQL Server 不会自动创建中间目录。第二层更隐蔽就算目录建好了SQL Server 服务是以某个服务账号运行的那个账号对你手建的D:\DATA未必有写权限尤其是数据盘挂在别的分区上的时候。解决就是两步先用mkdir D:\DATA建目录再在目录的安全属性里把 SQL Server 服务账号加进去给“完全控制”。懒得折腾就直接把filename改到默认数据目录下或者干脆省略filename参数让实例自己决定放哪。现象二数据导入到一半报“数据库的事务日志已满错误 9002”事务被强制回滚。原因在第 3 章说过日志文件maxsize 2MB而数据库用的是完整恢复模式日常操作产生的日志不会自动截断。哪怕只是插几千条成绩日志也能在一个事务里顶到上限。解决的顺序是先把maxsize改成unlimited或给到 2GB再确认恢复模式。课设环境直接改成简单恢复模式最省事-- 先解掉日志上限再切简单恢复模式日志会自动截断 alter database studentmanagesystem modify file (name studentmanagesystem_log, maxsize 2GB); alter database studentmanagesystem set recovery simple;现象三插第二个学院的 2024 级时报主键冲突 2627。原因是grades表把grade int单独做主键等于宣布“2024”这个值全库只能出现一次。计算机学院 2024 级和外语学院 2024 级在这个模型里是同一条记录插不进去。解决是改联合主键把年级和专业的组合作为唯一标识-- 年级必须和专业绑定主键改为复合 alter table grades drop constraint PK__grades__... alter table grades add constraint pk_grades primary key (grade, majorname);改完还要同步调整student和classes的外键让它们引用这个复合主键这一步不能漏否则外键对不上新主键会报 1776。5.2 视图与存储过程列名错位和参数拼写是重灾区现象四创建视图时提示“视图或函数 avg_s 未指定列名”或者视图建成了但查询结果对不上号。两个具体案例。avg_s的select列表里写了聚合avg(score)却没给别名视图要求每一列都有确定名字直接创建失败。c_major_view的情况更隐蔽视图声明了四个列名(collegename, collegeID, majorname, majorID)里面的select顺序却是collegeID, college.collegename, majorID, majorname两边的顺序错位结果就是collegename这一列里装的是学院编号。查询不报错数据全是错的这种 bug 最难查。解决就是让声明和查询严格对齐聚合列显式给名字-- 列顺序必须和 select 列表一一对应 create view c_major_view(collegename, collegeID, majorname, majorID) as select c.collegename, c.collegeID, m.majorname, m.majorID from college c join major m on m.collegename c.collegename;现象五执行存储过程报“列名 grade 无效”或“列名 collegename 不明确”又或者exec classprco提示找不到对象。第一种是字段名抄串了。avgscoreproc里写的是avg(grade)可成绩字段在selectcourse表里叫scoregrade是student表里的年级而且那个查询根本没 joinstudent。第二种是两张表都有collegenamemajorproc里直接写collegename没加表前缀优化器不知道取哪张。第三种最冤过程名建的是classproc调用写成了classprco两个字母一换报“找不到存储过程”。解决就是把所有列都带上表别名过程名和参数名建完立刻exec一次验证。还有一处teacherproc的参数声明写成了college char(10) outcharoutchar不是合法关键字应该是output而且collegename是varchar(20)接char(10)会截断。5.3 触发器表名写错、SET 语法错、自插死循环现象六删除课程时触发器报“对象名 SC 无效”修改学号时触发器报set子句语法错误。原文里delete SC where cno in (...)的SC是教科书 E-R 图里的临时表名物理设计里实际叫selectcourse照抄必错。修改学号那个触发器写的是update selectcourse set sno in (select sno from inserted) where ...set后面只能跟列 值不能跟in全句语法都不成立。正确写法是-- 学号变更后把选课表里的旧学号刷成新学号 create trigger trg_student_sno_update on student after update as begin set nocount on; if not update(sno) return; -- 只关心学号变化 update sc set sno i.sno from selectcourse sc join deleted d on sc.sno d.sno -- 旧学号 join inserted i on i.sno d.sno; -- 新学号 end现象七往student表插入一行结果表里多出一条sno,studentname,sex...这样的垃圾数据或者直接报超过最大嵌套层数。原因是那批insert_student、insert_classes、insert_college触发器写成了“插入后往同一张表再插一次字面量”-- 反例after insert 里往同一张表插固定字符串 create trigger insert_student on student after insert as begin insert into student values(sno,studentname,sex,sage, collegename,majorname,grade,class,tel) end这条语句每插一行就触发一次自己再插一行再触发。SQL Server 默认关闭递归触发器所以不会无限递归但每次插入都会实打实多出一条全是占位字符串的脏数据主键冲突之后再疯狂报错。这批触发器没有任何业务价值正确做法是全部删掉drop trigger insert_student; drop trigger insert_classes; drop trigger insert_college;现象八同一批插入里有一条学号不存在结果整批十条全部消失。这就是after insert rollback的典型副作用。回滚把外层事务一起带走了应用层看到的是“什么都没插进去”排查半天。换成instead of insert加where exists过滤就只有不合法的那条被跳过其余正常入库日志里也看得清是哪条被拦了。6. 二次开发把课设骨架改成能上线的东西这套模型能跑但从“演示”到“能用”中间还差几层。我按改动优先级列了个清单从上往下做每一步都能单独验证改造项原设计改成什么怎么验证账号体系system 两列account 表加角色、哈希、sno用两种角色各登录一次年级主键grade 单列(grade, majorname) 复合插两个学院的同年级记录选课主键(sno, cno)(sno, cno, term)同一门课插两条不同学期成绩字段int 可空decimal(5,2) 可空算一次加权平均看精度级联删除删除触发器外键 on delete cascade删一门课看选课表改主键那两步要连着做顺序是先drop外键、再drop主键、再add新主键、最后把外键加回去。中间任何一步失败都会让表处于半改状态所以我一般会把整个过程包在一个事务里begin try begin tran; alter table selectcourse drop constraint FK__selectcou__sno__...; alter table selectcourse drop constraint PK__selectco__...; alter table selectcourse add term char(11) not null default 2024-2025-1; alter table selectcourse add constraint pk_sc primary key (sno, cno, term); alter table selectcourse add constraint fk_sc_sno foreign key (sno) references student(sno); commit tran; end try begin catch rollback tran; throw; end catch约束名在每台机器上都不一样SQL Server 自动生成的那种PK__selectco__1A14E395直接抄文档里的语句迁移到另一台机器就报“约束不存在”。查真实名字用sp_help selectcourse或者查sys.key_constraints。这一条是我在换机器部署时踩过的从那以后我写任何改表脚本约束名都先从系统视图里查出来再拼。触发器那批建议整体砍掉重来。级联同步的交给外键on delete cascade/on update cascade数据库自己处理得比触发器稳合法性校验的改成instead of或者干脆挪到应用层——在服务里先查一次student和course再决定插不插逻辑更直观也方便写单元测试。真要留在数据库里至少给每个触发器加上set nocount on和if not update(列名) return;的短路判断前者省网络包后者能在无关字段更新时避免空跑。如果打算换数据库几个点要提前记下varchar在 MySQL 里要改成utf8mb4否则中文名会截断char(10)存的学号在 PostgreSQL 里要用char(10)对应别混text自增列原文没有主键都是业务字段新增的代理键在 MySQL 用auto_increment、在 PostgreSQL 用generated always as identitygetdate()换成now()或current_timestamp。存储过程那五个跨库基本要重写output参数在 MySQL 里叫out语法差别不小。验证方法我习惯用“脏数据压测”造 500 个学生、30 门课、2000 条选课记录把五个存储过程全跑一遍再随机删一门课、改一个学号、插一条不存在的课程号看数据库的反馈是不是和预期一致。这份文档里的脚本按原样跑上面三个动作分别会触发 2627、语法错误和脏数据插入改完之后应该变成“正常级联”“学号同步更新”“该行被跳过”。改前改后各跑一次比看十遍文档都管用。从那以后我每次接手这种“文档 SQL 脚本”的交付物都会先在空库上原样跑一遍把每个报错记下来再开始改——照着文档抄一遍再改比对着文档空想哪里可能有问题快得多也踏实得多。希望帮到你。本文还有配套的精品资源点击获取