资讯详情

基于 SQL Server 的学生选课系统数据库设计:从 ER 图到并发控制完整指南

📅 2026/10/13 9:01:37 | 华诺云谱 👁 阅读
基于 SQL Server 的学生选课系统数据库设计:从 ER 图到并发控制完整指南
简介基于 SQL Server 的学生选课系统数据库设计源码与配套文档面向需要完成课程设计、期末大作业的高校学生尤其是 SQL Server 初学者。压缩包共 5 个文件约 138KB包含 SQL 脚本、Word 详细设计文档、README 说明以及两张 PNG 截图脚本中附有注释便于理解表结构、关系与核心业务逻辑文档与截图可辅助快速部署和答辩展示。目前已吸引 433 人学习下载。资料覆盖学生、课程、选课等主要数据表给出完整建库、建表及示例数据可直接导入 SQL Server 运行。相比零散代码这份资源同时提供界面效果图与文档说明既可用于完善课设报告也能帮助新手对照实践是兼顾实用性与完整度的数据库设计参考。1. 基于 SQL Server 的学生选课系统数据库设计这个高分项目到底在考什么正在找“基于 SQL Server 的学生选课系统数据库设计源码详细文档”的人多半是在赶数据库课程设计。这个题目看起来只是搭一个选课页面但真正给高分的评判点几乎全在数据库这一层ER 图完不完整、约束有没有用对、选课会不会超发、并发下会不会重复选课、文档能不能让老师不看代码就复现你的建库过程。所谓源码在这个场景里核心是能反复执行的 SQL 脚本所谓详细文档是需求分析、ER 图、数据字典、存储过程说明。新手可以从建表跟到事务老手可以直接看锁与索引的取舍。这篇就把一条能直接交付的落地路径拆开讲完。2. 先把业务边界画清楚实体关系、开课班与必须用约束的地方我拿到这类题目第一件事不是写 SQL而是画 ER 图。后面所有 DDL、存储过程、索引都从这张图长出来。很多学生交上来的选课系统只有三张表学生、课程、选课记录。这种设计在演示视频里跑得通但老师一旦追问“同一门课两个老师开课怎么办”“这个学期和下个学期的数据怎么分”“退课之后能不能再选”就很容易翻车。2.1 课程、开课班与学期多出来的几张表在解什么题选课系统的核心业务不是“学生选课程”而是“学生选某个老师在某学期开的某个教学班”。课程表只保存课程本身的属性比如课程名、学分、课程类型谁教、在哪上、容量多少应该放到开课班表里。所以一个能拿高分的最小结构至少是学生表、教师表、课程表、开课班表、学期表、选课记录表。学期表解决“哪些课是这个学期的课”。很多项目把 Semester 直接写成开课班的字段不建表也不是不能用但你要做“只查本学期可选课程”的报表时每次都要硬编码一个学期字符串维护起来很别扭。开课班表解决“同一门课多人开”。CourseID 和 OfferingID 必须分开否则同一个 CourseID 在同一个学期只能有一个老师这不真实。选课记录表解决“历史”。退课不删行而是把 Status 改成 0这样以后想做“这门课退课率”“哪些学生反复退选”都有据可查。2.2 用约束把“一人一门课只能选一次”写进数据库数据库设计的评分重点之一是规则到底写在哪里。正确的做法是把能静态声明的规则全部写成约束而不是只靠应用层代码判断。“一个学生在一个开课班里最多只有一条有效选课记录”必须用唯一索引保证。要注意的是这里不能用普通的 UNIQUE 约束因为退课记录还留在表里。如果对(StudentID, OfferingID)建普通唯一约束学生退课后再选同一门课就会插入失败。应该用过滤唯一索引只对Status 1的记录生效。“容量不能超过开课班容量”不能用 CHECK 约束实现因为 CHECK 只能判断当前行的字段不能对子查询计数。这个规则必须放到事务和存储过程里。课程设计的答辩里老师很喜欢问这一点能答清楚说明你理解约束的边界。2.3 从 ER 图到建表脚本一份可跑通的基础 DDL下面这份 DDL 是我常用的起点按依赖顺序先创建无外键依赖的表再创建引用它们的表。代码里加了注释直接放到 SQL Server Management Studio 里可以重复执行。USE master; GO IF DB_ID(NCourseSelectionDB) IS NULL CREATE DATABASE CourseSelectionDB COLLATE Chinese_PRC_CI_AS; GO USE CourseSelectionDB; GO -- 删表顺序先删子表再删父表否则会被外键挡住 IF OBJECT_ID(Ndbo.Enrollment, NU) IS NOT NULL DROP TABLE dbo.Enrollment; IF OBJECT_ID(Ndbo.CourseOffering, NU) IS NOT NULL DROP TABLE dbo.CourseOffering; IF OBJECT_ID(Ndbo.Course, NU) IS NOT NULL DROP TABLE dbo.Course; IF OBJECT_ID(Ndbo.Teacher, NU) IS NOT NULL DROP TABLE dbo.Teacher; IF OBJECT_ID(Ndbo.Student, NU) IS NOT NULL DROP TABLE dbo.Student; IF OBJECT_ID(Ndbo.Semester, NU) IS NOT NULL DROP TABLE dbo.Semester; GO CREATE TABLE dbo.Semester ( SemesterID NCHAR(5) NOT NULL, -- 例如 2025S SemesterName NVARCHAR(20) NOT NULL, -- 例如 2025春季 StartDate DATE NULL, EndDate DATE NULL, IsCurrent BIT NOT NULL DEFAULT 0, CONSTRAINT PK_Semester PRIMARY KEY (SemesterID) ); GO CREATE TABLE dbo.Student ( StudentID NCHAR(8) NOT NULL, -- 学号固定8位 StudentName NVARCHAR(20) NOT NULL, Gender NCHAR(1) NOT NULL, EnrollmentDate DATE NULL, Major NVARCHAR(50) NULL, CONSTRAINT PK_Student PRIMARY KEY (StudentID), CONSTRAINT CK_Student_Gender CHECK (Gender IN (N男, N女)) ); GO CREATE TABLE dbo.Teacher ( TeacherID NCHAR(6) NOT NULL, TeacherName NVARCHAR(20) NOT NULL, Title NVARCHAR(20) NULL, CONSTRAINT PK_Teacher PRIMARY KEY (TeacherID) ); GO CREATE TABLE dbo.Course ( CourseID NCHAR(6) NOT NULL, CourseName NVARCHAR(50) NOT NULL, Credits DECIMAL(3,1) NOT NULL, CourseType NVARCHAR(20) NULL, -- 必修/选修/任选 CreditHours INT NULL, CONSTRAINT PK_Course PRIMARY KEY (CourseID), CONSTRAINT CK_Course_Credits CHECK (Credits 0 AND Credits 20) ); GO CREATE TABLE dbo.CourseOffering ( OfferingID INT IDENTITY(1,1) NOT NULL, CourseID NCHAR(6) NOT NULL, TeacherID NCHAR(6) NOT NULL, SemesterID NCHAR(5) NOT NULL, SectionNo TINYINT NOT NULL DEFAULT 1, -- 平行班号 Capacity INT NOT NULL, Room NVARCHAR(30) NULL, CONSTRAINT PK_CourseOffering PRIMARY KEY (OfferingID), CONSTRAINT FK_CourseOffering_Course FOREIGN KEY (CourseID) REFERENCES dbo.Course(CourseID), CONSTRAINT FK_CourseOffering_Teacher FOREIGN KEY (TeacherID) REFERENCES dbo.Teacher(TeacherID), CONSTRAINT FK_CourseOffering_Semester FOREIGN KEY (SemesterID) REFERENCES dbo.Semester(SemesterID), CONSTRAINT CK_CourseOffering_Capacity CHECK (Capacity BETWEEN 10 AND 300), CONSTRAINT UQ_CourseOffering_Section UNIQUE (CourseID, TeacherID, SemesterID, SectionNo) ); GO CREATE TABLE dbo.Enrollment ( EnrollmentID INT IDENTITY(1,1) NOT NULL, StudentID NCHAR(8) NOT NULL, OfferingID INT NOT NULL, SelectTime DATETIME2(0) NOT NULL CONSTRAINT DF_Enrollment_SelectTime DEFAULT (SYSDATETIME()), DropTime DATETIME2(0) NULL, Status TINYINT NOT NULL CONSTRAINT DF_Enrollment_Status DEFAULT (1), -- 1有效 0已退 CONSTRAINT PK_Enrollment PRIMARY KEY (EnrollmentID), CONSTRAINT FK_Enrollment_Student FOREIGN KEY (StudentID) REFERENCES dbo.Student(StudentID), CONSTRAINT FK_Enrollment_Offering FOREIGN KEY (OfferingID) REFERENCES dbo.CourseOffering(OfferingID), CONSTRAINT CK_Enrollment_Status CHECK (Status IN (0, 1)) ); GO -- 同一学生在同一开课班里只能有一条有效选课记录 CREATE UNIQUE NONCLUSTERED INDEX UX_Enrollment_Active ON dbo.Enrollment (StudentID, OfferingID) WHERE Status 1; GO这段脚本里有几个值得在文档里写清楚的设计决策学号、工号、课程号用NCHAR固定长度因为这类编号长度稳定固定长度在随机读时开销更小。姓名、课程名用NVARCHAR兼容中文。开课班用IDENTITY自增主键选课记录也用自增主键而不用业务字段做主键。这样退课、重选不会碰到主键冲突。Semester单独建表IsCurrent字段用来标记当前学期。报表里经常要过滤“本学期开课”这个字段很有用。选课记录保留DropTime退课是更新而不是删除。评分文档里可以写“保留选课轨迹便于后续做选课行为分析”。如果你的文档里有数据字典每个字段说明要和这里的 DDL 保持一致。老师最反感的是文档画得很漂亮脚本里字段名完全对不上。3. 选课事务与并发控制存储过程怎么写才不会超选、不会重复选建表只是开始。选课系统的核心难点是并发选课时同一门课只剩最后一个名额两个学生同时提交系统不能让两个人都选上。课程设计项目虽然流量不大但数据库设计题就爱考这个点。3.1 为什么不能只用 SELECT 判断名额并发下的超选很多初版代码是这样写的先查已选人数再判断是否小于容量最后插入选课记录。这个逻辑单用户跑没问题一旦并发就出事。假设容量是 20现在已选 19。用户 A 和用户 B 同时执行SELECT COUNT(*)都读到 19都判断“可以选”随后都插入。最终变成 21 人超选。这不是 SQL 语法问题而是事务隔离级别下的读写竞争。要想解决选课过程必须串行化。最直接的方式是让所有选课动作都锁住同一个开课班记录后进来的人必须等前面的人提交完才能继续读名额。3.2 选课存储过程的正确结构UPDLOCK、HOLDLOCK 与 XACT_ABORT我一般会把选课逻辑封装成一个存储过程业务层只传学号和开课班 ID通过输出参数拿到选课结果。存储过程的几个关键点事务一定要提交或回滚提前RETURN之前要先ROLLBACK否则连接上会挂着未提交事务后面的选课全部卡死。CREATE PROCEDURE dbo.usp_SelectCourse StudentID NCHAR(8), OfferingID INT, Result INT OUTPUT -- 0成功 1已选 2已满 3学生不存在 4课程不存在 AS BEGIN SET NOCOUNT ON; SET XACT_ABORT ON; BEGIN TRY BEGIN TRANSACTION; IF NOT EXISTS (SELECT 1 FROM dbo.Student WHERE StudentID StudentID) BEGIN SET Result 3; ROLLBACK TRANSACTION; RETURN; END -- 锁住开课班记录让并发的选课请求在这里排队 DECLARE Capacity INT; SELECT Capacity Capacity FROM dbo.CourseOffering WITH (UPDLOCK, HOLDLOCK) WHERE OfferingID OfferingID; IF Capacity IS NULL BEGIN SET Result 4; ROLLBACK TRANSACTION; RETURN; END IF EXISTS ( SELECT 1 FROM dbo.Enrollment WHERE StudentID StudentID AND OfferingID OfferingID AND Status 1 ) BEGIN SET Result 1; ROLLBACK TRANSACTION; RETURN; END IF ( SELECT COUNT(1) FROM dbo.Enrollment WHERE OfferingID OfferingID AND Status 1 ) Capacity BEGIN SET Result 2; ROLLBACK TRANSACTION; RETURN; END INSERT INTO dbo.Enrollment (StudentID, OfferingID, Status) VALUES (StudentID, OfferingID, 1); COMMIT TRANSACTION; SET Result 0; END TRY BEGIN CATCH IF TRANCOUNT 0 ROLLBACK TRANSACTION; SET Result -1; THROW; END CATCH END GO逻辑说明和参数设置WITH (UPDLOCK, HOLDLOCK)是关键。UPDLOCK让这条SELECT拿更新锁HOLDLOCK让锁保持到事务结束。其他选课事务再执行到同一行时会等这把锁释放选课容量判断就变成串行的。所有提前返回的地方都先ROLLBACK TRANSACTION避免事务残留。这是很多项目里黑匣子式卡死的来源。SET XACT_ABORT ON保证遇到运行时错误自动回滚当前事务防止只COMMIT没ROLLBACK的脏数据。Result只输出一个整数给应用层应用层不需要再查一遍数据库来判断是否成功。如果不想依赖锁提示也可以用sp_getapplock拿一把应用锁把整个选课过程串行化。这对课程设计来说更简单但答辩时容易被追问应用锁和行锁的区别所以我更推荐上面这种直接在业务表上加锁的写法。3.3 退课逻辑保留历史记录别让约束变成路障退课不能用DELETE因为你要的是选课历史。把Status改成 0同时记录退课时间。过滤唯一索引已经保证了同一个学生同一个开课班最多一条有效记录所以退课后重新选课不会被挡住。CREATE PROCEDURE dbo.usp_DropCourse StudentID NCHAR(8), OfferingID INT, Result INT OUTPUT AS BEGIN SET NOCOUNT ON; SET XACT_ABORT ON; BEGIN TRY BEGIN TRANSACTION; -- 退课也锁开课班避免和选课事务并发时名额计算错乱 SELECT Capacity FROM dbo.CourseOffering WITH (UPDLOCK, HOLDLOCK) WHERE OfferingID OfferingID; IF NOT EXISTS ( SELECT 1 FROM dbo.Enrollment WHERE StudentID StudentID AND OfferingID OfferingID AND Status 1 ) BEGIN SET Result 1; -- 没有有效选课记录 ROLLBACK TRANSACTION; RETURN; END UPDATE dbo.Enrollment SET Status 0, DropTime SYSDATETIME() WHERE StudentID StudentID AND OfferingID OfferingID AND Status 1; COMMIT TRANSACTION; SET Result 0; END TRY BEGIN CATCH IF TRANCOUNT 0 ROLLBACK TRANSACTION; SET Result -1; THROW; END CATCH END GO退课和选课都要锁同一把锁也就是同一个开课班记录。如果退课不锁选课事务正在读COUNT时一条记录被退掉可能造成容量计算不准。虽然在行锁下最终结果未必错但课程设计的文档里能写清楚这个设计理由是很加分的。4. 让数据查询真正可用索引、视图与实际报表表结构和存储过程不直接等于高分。老师会看你的查询层有没有技术含量。很多项目把所有统计都写进 C# 或 Java 代码里循环取数这既慢又不可维护。在 SQL Server 里把索引建对、把常用查询封装成视图是数据库设计能力的直观体现。4.1 选课表索引设计的三个选择索引不是越多越好而是要对准查询。选课系统最常用的三个查询场景是查某人已选课程、查某开课班已选人数、统计课程热度。围绕这三个场景我给Enrollment表建两组非聚集索引。查询场景索引键包含列查某学生已选课程StudentID, StatusOfferingID, SelectTime查某开课班已选人数OfferingID, StatusStudentID统计课程热度OfferingID, Status无CREATE NONCLUSTERED INDEX IX_Enrollment_Student_Status ON dbo.Enrollment (StudentID, Status) INCLUDE (OfferingID, SelectTime); GO CREATE NONCLUSTERED INDEX IX_Enrollment_Offering_Status ON dbo.Enrollment (OfferingID, Status) INCLUDE (StudentID); GO逻辑说明INCLUDE里的列不参与索引排序但能覆盖查询避免回表。例如查“某学生的有效选课”索引里已经包含OfferingID取出来之后可以直接去关联开课班表。键列顺序是StudentID, Status因为查询条件通常用学号定位再筛状态。如果反着写SQL Server 也能用但范围过滤效率会打折。不要在每个外键列上单独建一个单列索引。比如已经有了(OfferingID, Status)就不需要再来一个单独的OfferingID索引。这个话在文档里写出来老师会知道你理解组合索引。4.2 排名与冲突报表背后的窗口函数选课系统经常要出“热门课程排行”。如果按老办法写个子查询再在外面套一层也能出结果但窗口函数更直接。SELECT o.SemesterID, c.CourseName, COUNT(e.EnrollmentID) AS SelectedCount, DENSE_RANK() OVER (ORDER BY COUNT(e.EnrollmentID) DESC) AS HotRank FROM dbo.CourseOffering o JOIN dbo.Course c ON o.CourseID c.CourseID LEFT JOIN dbo.Enrollment e ON e.OfferingID o.OfferingID AND e.Status 1 GROUP BY o.SemesterID, c.CourseName;这段 SQL 里LEFT JOIN很重要。没有人选的课也要出现在结果里SelectedCount为 0而不是直接丢行。DENSE_RANK和RANK的差别在于并列名次时是否跳号。选课热度报表用DENSE_RANK更合适因为第 1 名并列两门时第 3 名显示成 2 而不是 3。冲突检测也是高分项目里的一个亮点查询。规则是“同一学生同一门课不能同时选多个教学班”但存储过程里只防了同一个开课班跨班冲突需要用报表暴露出来。SELECT e.StudentID, o.CourseID, COUNT(DISTINCT e.OfferingID) AS SelectedSectionCount FROM dbo.Enrollment e JOIN dbo.CourseOffering o ON e.OfferingID o.OfferingID WHERE e.Status 1 GROUP BY e.StudentID, o.CourseID HAVING COUNT(DISTINCT e.OfferingID) 1;这个查询的作用是给管理员看冲突数据不让系统本身处于“明知冲突还在插入”的状态。如果你在文档里写清楚业务规则靠存储过程拦截事后冲突靠查询发现这比单点防御更有说服力。4.3 用视图把成绩统计封装给应用层视图是课程设计里性价比很高的一层封装。应用层不直接写复杂的JOIN和COUNT而是从一个视图里取数据逻辑维护在数据库端。CREATE OR ALTER VIEW dbo.vCourseSelectionStats AS SELECT o.OfferingID, c.CourseID, c.CourseName, o.SemesterID, t.TeacherName, o.Capacity, COUNT(e.EnrollmentID) AS SelectedCount, o.Capacity - COUNT(e.EnrollmentID) AS RemainingSeats FROM dbo.CourseOffering o JOIN dbo.Course c ON o.CourseID c.CourseID JOIN dbo.Teacher t ON o.TeacherID t.TeacherID LEFT JOIN dbo.Enrollment e ON e.OfferingID o.OfferingID AND e.Status 1 GROUP BY o.OfferingID, c.CourseID, c.CourseName, o.SemesterID, t.TeacherName, o.Capacity; GO使用这个视图时要注意视图不是物化表每次查询都会实时执行里面这段聚合逻辑。数据量小没问题数据量大了要回到第 4.1 的索引设计上。在课程设计的文档里可以写一句“视图只做逻辑封装不做性能承诺性能由底层索引和查询设计承担”这能避免答辩时被问“视图为什么不快”时哑口无言。5. 避坑手册从附加数据库到账号恢复的 5 个踩坑记录这一章是我自己带项目时见过最多的问题每一个都足够让交作业前一夜崩溃。5.1 附加数据库失败mdf 权限和路径现象附加数据库时弹出“无法打开物理文件 xxx.mdf操作系统错误 5拒绝访问”。原因SQL Server 服务账户对 mdf 所在目录没有 NTFS 权限。很多同学把下载的 .mdf 文件放在桌面上或放在自己用户名的下载目录里SQL Server 服务账户根本进不去。解决把 mdf 和 ldf 放到 SQL Server 默认数据目录下或者给服务账户授予目录读写权限。最省事的是用脚本附加并把路径明确写在脚本里USE master; GO CREATE DATABASE CourseSelectionDB ON (FILENAME ND:\Data\CourseSelectionDB.mdf), (FILENAME ND:\Data\CourseSelectionDB_log.ldf) FOR ATTACH; GO建议把数据库文件统一放在一个不含中文、不含空格的路径下比如D:\Data。这能少踩一个编码相关的问题。5.2 登录名连不上SQL Server 2012 密码策略到期与默认数据库现象连接字符串和账号密码都对但一连接就报登录失败。有的报错是“密码已过期”有的是“无法打开用户默认数据库”。原因创建 SQL Server 登录时如果开了CHECK_POLICY和CHECK_EXPIRATIONWindows 密码策略会强制登录名定期改密。课程设计里的测试账号经常是建完就忘密码过期后就连接失败。另一个常见原因是登录名的默认数据库被改成了不存在或无权限的库。解决用sa或 Windows 身份登录到 master然后关掉这个登录名的密码策略并把默认数据库改回正确库USE master; GO ALTER LOGIN stu_user WITH CHECK_POLICY OFF, CHECK_EXPIRATION OFF, DEFAULT_DATABASE CourseSelectionDB; GO注意生产环境不建议关密码策略但课程设计和本地开发可以接受。这个坑在 SQL Server 2012 到新版都会出现早点关早安心。5.3 中文乱码排序规则没有统一现象插入学生姓名时中文变成??或者查询结果显示乱码。原因最常见的是创建数据库时用了非中文排序规则比如默认的Latin1_General_CI_AS。还有可能是 insert 语句里的中文字符串没有加N前缀比如直接写张三而不是N张三。解决先确认当前库的排序规则SELECT name, collation_name FROM sys.databases WHERE database_id DB_ID(NCourseSelectionDB);如果是非中文排序规则最稳的做法是重新建库时直接指定CREATE DATABASE CourseSelectionDB COLLATE Chinese_PRC_CI_AS; GO注意已经建好的库再改排序规则不会自动修改旧表里字符串列的排序规则所以最好一开始就建对。另外所有向NVARCHAR列插入中文的地方建议都写成N中文防止脚本文件编码问题在输送过程中从源头变样。5.4 脚本执行到一半报错GO 分批和对象判断现象执行建表脚本时报“数据库中已存在名为 CourseOffering 的对象”或者报“无效的对象名 dbo.Enrollment”。原因SQL Server Management Studio 里GO是批处理分隔符。没有GO的话DDL 和后续引用该表的 DML 会在同一个批处理里编译而编译时表还不存在。另一个原因是脚本之前已经跑过一次表还在又没有删除判断。解决在每个 DROP 和 CREATE 之间加GO并在删除表之前用IF OBJECT_ID判断IF OBJECT_ID(Ndbo.Enrollment, NU) IS NOT NULL DROP TABLE dbo.Enrollment; GO CREATE TABLE dbo.Enrollment (...); GO这个习惯也能让脚本被反复执行老师验收时不会因为第二次运行就翻车。5.5 恢复备份后账号变成孤儿映射孤立用户现象把数据库备份还原到另一台服务器后原来的登录名连不上或者提示“无法打开用户默认数据库”数据库里又能看到这个用户。原因登录名的 SID 和数据库用户的 SID 对不上。新实例上创建的登录名 SID 跟备份库里的用户 SID 不是同一个SQL Server 认为这个数据库用户是“孤立用户”。解决先确认登录名存在再映射用户USE CourseSelectionDB; GO ALTER USER stu_user WITH LOGIN stu_user; GO如果登录名不存在先在 master 里创建USE master; GO CREATE LOGIN stu_user WITH PASSWORD NYourPassword; GO老版本的sp_change_users_login也已经能用但新项目我会直接用ALTER USER ... WITH LOGIN写法更简洁。6. 交付前体检三分钟看穿一个课程设计数据库的好坏交作业之前我会用三件事给数据库做体检。第一件事是删库重跑脚本。把建库脚本、表结构、存储过程、测试数据全部清掉在一个全新数据库里顺序执行。跑不过文档写得再漂亮也是零分。第二件事是并发验证。用一个小工具同时发起 20 个选课请求看最终选课人数是否超过容量看有没有重复选课。第三件事是检查文档和 DDL 是否一致。数据字典里的字段名、类型、默认值和真实表的sys.columns对一遍。如果想知道索引有没有明显缺漏可以直接看 SQL Server 自己记录的缺失索引建议SELECT OBJECT_NAME(ddmid.object_id) AS TableName, ddmid.equality_columns, ddmid.inequality_columns, ddmid.included_columns, ddmigs.user_seeks, ddmigs.user_scans FROM sys.dm_db_missing_index_details ddmid LEFT JOIN sys.dm_db_missing_index_groups ddmig ON ddmid.index_handle ddmig.index_handle LEFT JOIN sys.dm_db_missing_index_group_stats ddmigs ON ddmig.index_group_handle ddmigs.index_group_handle WHERE ddmid.database_id DB_ID(NCourseSelectionDB) ORDER BY ddmigs.user_seeks DESC;这段查询会列出 SQL Server 认为可以帮你加速的索引建议。它不是让你全建而是告诉你最频繁的查询路径到底缺什么。对于课程设计而言拿这条结果和你的索引设计对照能发现不少漏洞。如果压测时发现大量WRITELOG等待先不要急着加索引看看日志文件是不是和数据文件挤在同一块慢速磁盘上。SQL Server 的WRITELOG等待常见原因是事务日志写入慢尤其在你疯狂开事务做选课压测时。把日志文件放到单独的 SSD 上比加十个索引都有效。体检清单不用复杂长这样检查项预期结果删库重跑全部脚本按顺序执行成功重复选课唯一索引拦截插入失败并发选课最终人数不超过 Capacity退课再选新记录成功旧记录保留文档字段与sys.columns一致我自己的习惯是交作业前先删库重建跑三遍第一遍看建库第二遍看存储过程第三遍清掉测试数据只留空库结构。因为老师不会帮你热修脚本数据库设计这种项目跑不通就是硬伤。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑