
简介本资源是一套面向软件工程专业本科生的数据库综合实践大作业围绕小区物业收费管理系统展开覆盖需求分析、E-R建模、SQL脚本开发、权限管理与实验报告撰写全流程。资源包共16个文件含13个功能明确的SQL脚本如建表、插入、查询、视图、索引、授权、用户操作等、1份Word格式实验报告、1份PDF版E-R图及1份可编辑的VSD源图全面支撑从概念设计到系统实现的完整学习路径。压缩包大小为12.33MB结构清晰、模块分明便于按实验阶段逐步实践与验证。已有1428人学习下载内容紧扣课程核心能力——关系建模、T-SQL编程、安全性控制与规范化文档输出特别适合数据库原理与应用课程的课设、实验复习及期末综合实训使用。1. SQL Server实验大作业不是抄代码交报告而是用真实数据流跑通建库→建表→查询→事务→备份全链路你手头这份“Microsoft SQL Server实验大作业”大概率正躺在某高校数据库课程的期末任务清单里——带“.sql”文件、含Word实验报告、要求截图结果分析。但真正卡住90%同学的从来不是“怎么写SELECT”而是当CREATE DATABASE执行完却连不上自己刚建的库当INSERT插入100条数据后事务回滚失败导致脏数据残留当备份脚本跑出“.bak”文件却在还原时提示“媒体集不完整”。这不是考试题是微型生产环境的缩影权限错配、字符集冲突、日志截断失控、时间戳精度丢失……这些坑在课堂演示里从不出现但在你双击SSMS连接那一刻就扑面而来。本文不讲T-SQL语法ABC只聚焦一个目标用一套可复现、可验证、带错误注入的最小闭环流程把“实验作业”变成你本地能反复调试、故障可回溯、参数可调优的SQL Server实操沙盒。适合正在赶DDL的本科生、需要补数据库实操短板的转行者以及想快速验证某个隔离级别行为的开发人员。2. 本地环境搭建绕过安装陷阱用Docker Compose启动纯净SQL Server实例含SA密码与端口映射SQL Server实验最耗时的环节往往不是写SQL而是装环境。Windows上装SQL Server Express常因.NET Framework版本、VC运行库、系统服务权限报错Linux下手动配置mssql-conf又容易漏掉telemetry设置导致后台进程抢占资源。更隐蔽的坑是默认安装的SQL Server实例名MSSQLSERVER和命名实例如SQLEXPRESS在连接字符串中写法完全不同新手常在此处浪费3小时。我们跳过所有GUI向导用Docker Compose构建一个开箱即用的容器化实例——它不依赖宿主机环境每次docker-compose down就能彻底重置且镜像已预置中文排序规则Chinese_PRC_CI_AS避免后续建表时varchar字段乱码。2.1 编写docker-compose.yml指定版本、端口、SA密码与初始化脚本version: 3.8 services: sqlserver: image: mcr.microsoft.com/mssql/server:2019-latest container_name: sqlserver-lab environment: SA_PASSWORD: YourStrongPassw0rd # 必须含大小写字母数字特殊符号否则容器启动失败 ACCEPT_EULA: Y MSSQL_PID: Express # 使用Express版满足教学需求内存限制1.4GB足够实验 TZ: Asia/Shanghai # 设置时区避免GETDATE()返回UTC时间 ports: - 1433:1433 # 宿主机1433端口映射到容器内1433SSMS直接连localhost volumes: - ./init-scripts:/docker-entrypoint-initdb.d # 挂载初始化SQL脚本目录 - ./data:/var/opt/mssql/data # 持久化数据库文件避免容器删除后数据丢失 healthcheck: test: [CMD-SHELL, /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P YourStrongPassw0rd -Q SELECT 1 || exit 1] interval: 30s timeout: 10s retries: 3关键参数说明SA_PASSWORD必须满足复杂度要求8位以上含大小写字母、数字、特殊符号否则容器会反复重启并报错ERROR: Password validation failedMSSQL_PID: Express明确指定Express版避免默认Developer版占用过多内存Developer版无生产限制但实验场景无需此特性volumes挂载/docker-entrypoint-initdb.d是SQL Server官方支持的初始化目录容器首次启动时自动执行该目录下所有.sql文件按字母序比手动进容器执行sqlcmd更可靠healthcheck用sqlcmd命令验证SQL Server服务是否真正就绪避免应用层连接时抛出Login failed for user sa实际是服务未启动完成。2.2 创建初始化脚本在容器启动时自动建库、建表、插测试数据在项目根目录创建init-scripts/01-create-database.sql内容如下-- 01-create-database.sql创建实验专用数据库显式指定排序规则 CREATE DATABASE LabDB ON PRIMARY ( NAME LabDB_Data, FILENAME /var/opt/mssql/data/LabDB.mdf, SIZE 10MB, MAXSIZE UNLIMITED, FILEGROWTH 5MB ) LOG ON ( NAME LabDB_Log, FILENAME /var/opt/mssql/data/LabDB.ldf, SIZE 5MB, MAXSIZE 200MB, FILEGROWTH 2MB ) COLLATE Chinese_PRC_CI_AS; -- 强制中文排序解决LIKE查询大小写敏感问题 -- 切换到新库并建表 USE LabDB; GO -- 学生成绩表包含主键、外键、约束、默认值 CREATE TABLE Students ( StudentID INT IDENTITY(1,1) PRIMARY KEY, Name NVARCHAR(50) NOT NULL, Gender CHAR(1) CHECK (Gender IN (M,F)), EnrollmentDate DATE DEFAULT GETDATE(), CreatedAt DATETIME2 DEFAULT GETDATE() ); CREATE TABLE Courses ( CourseID INT IDENTITY(1,1) PRIMARY KEY, CourseName NVARCHAR(100) NOT NULL, Credits TINYINT CHECK (Credits BETWEEN 1 AND 6), Department NVARCHAR(50) DEFAULT N计算机系 ); CREATE TABLE Scores ( ScoreID INT IDENTITY(1,1) PRIMARY KEY, StudentID INT NOT NULL, CourseID INT NOT NULL, Score DECIMAL(5,2) CHECK (Score BETWEEN 0 AND 100), ExamDate DATE DEFAULT GETDATE(), CONSTRAINT FK_Student FOREIGN KEY (StudentID) REFERENCES Students(StudentID) ON DELETE CASCADE, CONSTRAINT FK_Course FOREIGN KEY (CourseID) REFERENCES Courses(CourseID) ON DELETE NO ACTION ); GO -- 插入测试数据使用UNION ALL避免多次INSERT开销且显式指定列名防错 INSERT INTO Students (Name, Gender, EnrollmentDate) VALUES (N张三, M, 2023-09-01), (N李四, F, 2023-09-02), (N王五, M, 2023-09-03); INSERT INTO Courses (CourseName, Credits, Department) VALUES (N数据库原理, 4, N计算机系), (N操作系统, 4, N计算机系), (N数据结构, 3, N信息学院); -- 关联插入Scores用子查询确保外键存在 INSERT INTO Scores (StudentID, CourseID, Score, ExamDate) SELECT s.StudentID, c.CourseID, ROUND(RAND(CHECKSUM(NEWID()))*100,2), 2024-06-15 FROM Students s CROSS JOIN Courses c WHERE s.StudentID 3 AND c.CourseID 3; GO逻辑说明与参数设计意图COLLATE Chinese_PRC_CI_AS中文排序规则CICase InsensitiveASAccent Sensitive使WHERE Name LIKE %张%能正确匹配中文避免默认SQL_Latin1_General_CP1_CI_AS导致的模糊查询失效DATETIME2而非DATETIME精度达100纳秒且范围更大0001-9999年避免GETDATE()在跨世纪场景下溢出CHECK (Score BETWEEN 0 AND 100)业务约束前置到数据库层比应用层校验更可靠ON DELETE CASCADE学生删除时自动清理其成绩记录体现参照完整性RAND(CHECKSUM(NEWID()))生成随机分数NEWID()确保每次调用产生新GUIDCHECKSUM将其转为整数种子避免RAND()在单条INSERT中返回相同值。2.3 启动容器并验证连接用sqlcmd命令行工具快速确认服务就绪在项目目录执行docker-compose up -d # 等待约30秒检查容器状态 docker-compose ps # 输出应显示 Up (healthy) 表示健康检查通过验证数据库是否创建成功# 进入容器执行sqlcmd无需安装客户端 docker exec -it sqlserver-lab /opt/mssql-tools/bin/sqlcmd \ -S localhost -U sa -P YourStrongPassw0rd \ -Q SELECT name, collation_name FROM sys.databases WHERE name LabDB预期输出name collation_name ------ ------------------------- LabDB Chinese_PRC_CI_AS为什么不用SSMS图形界面验证因为SSMS连接失败时错误信息模糊如A network-related or instance-specific error...而sqlcmd直接返回SQL Server原生错误码如Error: 18456, Severity: 14, State: 1表示登录失败便于精准定位是密码错、实例名错还是防火墙拦截。这是工程师的第一道排查防线。3. 核心实验模块实现用5个递进式SQL脚本覆盖建库→增删改查→事务→索引→备份全链路实验报告常被诟病“只贴正确结果”但真实工程中理解错误场景比记住正确语法更重要。本节5个脚本均设计为“可破坏性实验”每个脚本末尾预留一个故意写错的语句如违反CHECK约束、死锁触发点、缺失索引的慢查询你需要手动注释/修改它来观察SQL Server如何响应。所有脚本存于scripts/目录按编号执行。3.1 脚本1建库与建表验证lab1_schema.sql——检测排序规则与约束生效-- lab1_schema.sql验证建表时的约束与默认值是否生效 USE LabDB; GO -- 测试1插入违反CHECK约束的数据观察错误 INSERT INTO Students (Name, Gender) VALUES (N赵六, X); -- 应报错Violation of CHECK constraint -- 测试2不提供EnrollmentDate验证DEFAULT GETDATE()是否生效 INSERT INTO Students (Name, Gender) VALUES (N钱七, M); SELECT TOP 1 Name, EnrollmentDate FROM Students ORDER BY StudentID DESC; -- 测试3插入含中文的Department验证Chinese_PRC_CI_AS是否支持 INSERT INTO Courses (CourseName, Credits, Department) VALUES (N人工智能导论, 3, N智能科学与技术系); SELECT CourseName, Department FROM Courses WHERE Department LIKE N%智能%; GO执行后关键观察点第1条INSERT应返回错误Msg 547, Level 16, State 0证明CHECK约束已激活第2条SELECT返回的EnrollmentDate应为当前日期非NULL确认DEFAULT生效第3条SELECT应能匹配到智能科学与技术系若返回空集则排序规则未生效需检查建库时COLLATE是否写错。3.2 脚本2多表关联查询与性能对比lab2_query.sql——用SET STATISTICS IO暴露I/O瓶颈-- lab2_query.sql对比有/无索引的JOIN性能差异 USE LabDB; GO -- 步骤1清空查询缓存模拟首次执行 DBCC FREEPROCCACHE; DBCC DROPCLEANBUFFERS; -- 步骤2执行无索引JOIN故意删除CourseID索引后运行 SET STATISTICS IO ON; SELECT s.Name, c.CourseName, sc.Score FROM Students s JOIN Scores sc ON s.StudentID sc.StudentID JOIN Courses c ON sc.CourseID c.CourseID WHERE c.Credits 3; SET STATISTICS IO OFF; -- 观察逻辑读取次数Logical Reads通常1000次 -- 步骤3创建索引后重试 CREATE NONCLUSTERED INDEX IX_Scores_CourseID ON Scores(CourseID); GO -- 步骤4再次执行相同查询 SET STATISTICS IO ON; -- ... 同上SELECT语句 ... SET STATISTICS IO OFF; -- 对比逻辑读取次数应降至10次 GO为什么用SET STATISTICS IO而非Execution Plan图形执行计划在SSMS中易受缩放、颜色干扰而STATISTICS IO输出的logical reads是量化指标它直接反映SQL Server从内存或磁盘读取的数据页数。1次logical read 1个8KB数据页若某查询logical reads达5000意味着它扫描了40MB数据——这比看“聚集索引扫描”图标更能刺痛你去建索引。这是DBA的性能诊断直觉训练。3.3 脚本3事务控制与死锁模拟lab3_transaction.sql——用WAITFOR制造可控竞争-- lab3_transaction.sql演示READ COMMITTED隔离级别下的不可重复读及死锁 USE LabDB; GO -- 场景1不可重复读Session A -- 在SSMS新开查询窗口执行此部分标记为Session A BEGIN TRAN; SELECT Score FROM Scores WHERE ScoreID 1; -- 假设返回85 WAITFOR DELAY 00:00:05; -- 等待5秒让Session B修改 SELECT Score FROM Scores WHERE ScoreID 1; -- 再次查询可能返回92Session B已更新 COMMIT; -- 场景2死锁模拟需两个窗口同时运行 -- Session A 执行 BEGIN TRAN; UPDATE Students SET Name N张三_更新 WHERE StudentID 1; WAITFOR DELAY 00:00:02; UPDATE Scores SET Score 95 WHERE StudentID 1; COMMIT; -- Session B 执行几乎同时 BEGIN TRAN; UPDATE Scores SET Score 88 WHERE StudentID 1; WAITFOR DELAY 00:00:02; UPDATE Students SET Name N张三_B WHERE StudentID 1; COMMIT; -- 其中一个会收到死锁错误Msg 1205, Level 13, State 46, Line X GO死锁排查关键动作当死锁发生SQL Server会自动选择牺牲者victim。立即执行以下语句捕获死锁图SELECT * FROM sys.dm_exec_requests WHERE status suspended; -- 查看blocking_session_id SELECT * FROM sys.dm_os_waiting_tasks WHERE session_id [blocking_session_id];死锁的本质是循环等待资源A锁了Students表要等ScoresB锁了Scores表要等Students。解决方案不是加锁粒度而是统一访问顺序如约定先Students后Scores。3.4 脚本4备份与还原全流程lab4_backup.sql——用COPY_ONLY避免破坏日志链-- lab4_backup.sql完整备份 差异备份 日志备份含COPY_ONLY USE master; GO -- 步骤1完整备份基础 BACKUP DATABASE LabDB TO DISK /var/opt/mssql/data/LabDB_full.bak WITH INIT, FORMAT, CHECKSUM, STATS 10; -- STATS10每10%进度输出一次避免长时间无响应 -- 步骤2插入新数据模拟业务变化 USE LabDB; INSERT INTO Students (Name, Gender) VALUES (N孙八, F); -- 步骤3差异备份仅备份自上次完整备份后的变化 BACKUP DATABASE LabDB TO DISK /var/opt/mssql/data/LabDB_diff.bak WITH DIFFERENTIAL, INIT, CHECKSUM; -- 步骤4再插入数据 INSERT INTO Scores (StudentID, CourseID, Score) VALUES (4, 1, 89); -- 步骤5日志备份关键必须用COPY_ONLY否则影响生产日志链 BACKUP LOG LabDB TO DISK /var/opt/mssql/data/LabDB_log.trn WITH COPY_ONLY, INIT, CHECKSUM; -- 步骤6还原验证按顺序完整→差异→日志 RESTORE DATABASE LabDB_Restore FROM DISK /var/opt/mssql/data/LabDB_full.bak WITH MOVE LabDB_Data TO /var/opt/mssql/data/LabDB_Restore.mdf, MOVE LabDB_Log TO /var/opt/mssql/data/LabDB_Restore.ldf, NORECOVERY, REPLACE; RESTORE DATABASE LabDB_Restore FROM DISK /var/opt/mssql/data/LabDB_diff.bak WITH NORECOVERY; RESTORE LOG LabDB_Restore FROM DISK /var/opt/mssql/data/LabDB_log.trn WITH RECOVERY; GOCOPY_ONLY的血泪经验若在生产环境中执行BACKUP LOG不加COPY_ONLY会截断事务日志导致后续日志备份链断裂。实验环境虽无影响但养成加COPY_ONLY的习惯能避免你在真实运维中误操作。这是DBA的“后悔药”开关。3.5 脚本5存储过程与错误处理lab5_procedure.sql——用TRY...CATCH捕获约束冲突-- lab5_procedure.sql创建带事务与错误处理的存储过程 USE LabDB; GO -- 创建存储过程安全插入学生成绩自动处理主键冲突与外键缺失 CREATE OR ALTER PROCEDURE InsertScoreSafe StudentName NVARCHAR(50), CourseName NVARCHAR(100), Score DECIMAL(5,2) AS BEGIN SET NOCOUNT ON; -- 避免返回X rows affected干扰应用层 BEGIN TRY BEGIN TRAN; -- 步骤1获取StudentID若不存在则插入 DECLARE StudentID INT; SELECT StudentID StudentID FROM Students WHERE Name StudentName; IF StudentID IS NULL BEGIN INSERT INTO Students (Name, Gender) VALUES (StudentName, M); SET StudentID SCOPE_IDENTITY(); END -- 步骤2获取CourseID若不存在则插入 DECLARE CourseID INT; SELECT CourseID CourseID FROM Courses WHERE CourseName CourseName; IF CourseID IS NULL BEGIN INSERT INTO Courses (CourseName, Credits) VALUES (CourseName, 3); SET CourseID SCOPE_IDENTITY(); END -- 步骤3插入成绩此处可能触发CHECK约束 INSERT INTO Scores (StudentID, CourseID, Score) VALUES (StudentID, CourseID, Score); COMMIT; SELECT Success: Score inserted for StudentName in CourseName AS Result; END TRY BEGIN CATCH IF TRANCOUNT 0 ROLLBACK; SELECT ERROR_NUMBER() AS ErrorNumber, ERROR_MESSAGE() AS ErrorMessage, ERROR_SEVERITY() AS ErrorSeverity; END CATCH END; GO -- 测试传入超分数据触发CHECK约束 EXEC InsertScoreSafe StudentName N周九, CourseName N网络安全, Score 105; -- 应返回ErrorNumber547ErrorMessage含CHECK constraint GO为什么用SCOPE_IDENTITY()而非IDENTITYIDENTITY会返回当前会话中任何作用域包括触发器产生的最后一个标识值而SCOPE_IDENTITY()只返回当前作用域即当前存储过程的标识值。若Courses表上有触发器插入日志表IDENTITY会返回日志表的ID导致外键插入失败——这是线上事故高频原因。4. 避坑指南5个让实验报告返工的高频错误与现场修复方案实验中最折磨人的不是不会写SQL而是错误现象与根本原因严重错位。比如“插入失败”可能源于排序规则、字符集、约束、权限四层中的任意一层而报错信息只告诉你“违反约束”。以下是我在指导某高校数据库课程时学生提交的137份实验报告中返工率最高的5个坑附带3步定位法现象→原因→修复。4.1 现象INSERT INTO Students VALUES (...)报错Cannot insert the value NULL into column Name但明明写了值原因建表时定义Name NVARCHAR(50) NOT NULL但INSERT语句未显式指定列名且VALUES中字段数少于表列数。SQL Server按列序匹配若表有5列而VALUES只给3个值后2列被赋NULL触发NOT NULL约束。修复查表结构SELECT COLUMN_NAME, IS_NULLABLE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAMEStudents检查INSERT语句是否漏列INSERT INTO Students (Name, Gender, EnrollmentDate) VALUES (...)永久习惯所有INSERT必须显式列出列名禁用INSERT INTO table VALUES (...)。4.2 现象SELECT * FROM Students WHERE Name 张三返回空集但SELECT * FROM Students能看到张三原因数据库排序规则为SQL_Latin1_General_CP1_CI_AS默认英文规则而Name字段是NVARCHARUnicode比较时隐式转换导致匹配失败。张三作为VARCHAR字面量会被转为Latin1编码无法匹配Unicode存储的汉字。修复查当前排序规则SELECT DATABASEPROPERTYEX(LabDB, Collation)若非Chinese_PRC_*重建数据库见2.1节临时救急WHERE Name N张三加前缀N表示Unicode字面量。4.3 现象备份脚本执行成功但还原时提示The media set has 2 media families but only 1 are provided原因BACKUP DATABASE使用了MIRROR TO或FORMAT参数不当导致备份集元数据损坏更常见的是同一.bak文件被多次BACKUP ... WITH INIT覆盖但SQL Server仍保留旧备份集头信息。修复查备份集信息RESTORE HEADERONLY FROM DISK /path/to/file.bak若Position列有多个值如1,2说明是多备份集文件需指定POSITION 1根治方案每次备份用唯一文件名如LabDB_full_$(date %Y%m%d).bak禁用WITH INIT。4.4 现象事务中UPDATE Scores SET Score 101未报错但后续SELECT发现Score仍是100原因Score字段有CHECK (Score BETWEEN 0 AND 100)约束但事务中执行UPDATE后未COMMIT且另一会话以READ UNCOMMITTED隔离级别查询看到的是未提交的脏数据101而COMMIT时约束检查失败回滚最终值仍为100。修复在事务内执行SELECT Score FROM Scores WHERE ScoreID X确认当前值检查事务是否COMMIT或ROLLBACK防御性编程所有UPDATE后加IF ERROR 0 ROLLBACK。4.5 现象CREATE INDEX IX_Scores_StudentID ON Scores(StudentID)执行成功但SELECT ... JOIN Scores仍很慢原因索引创建成功但查询优化器未使用它。常见原因有三① 查询条件未覆盖索引列如WHERE Score 80而索引是StudentID② 统计信息过期UPDATE STATISTICS Scores可刷新③ 表数据量太小1000行优化器认为全表扫描更快。修复强制使用索引SELECT ... FROM Scores WITH (INDEX(IX_Scores_StudentID)) ...查看执行计划SET STATISTICS XML ON确认Index Seek是否出现验证标准对1万行以上数据测试逻辑读取50次才算有效。5. 实验报告撰写技巧用3个可视化证据替代10页文字描述让老师一眼看到你的深度实验报告的价值不在于证明“我会写SQL”而在于展示“我理解SQL Server如何工作”。某高校数据库课程导师曾告诉我“看100份报告90份在抄教材定义只有10份让我想问‘这个现象你怎么解释’”。以下是我总结的3个必放证据它们不增加代码量但能让报告从“作业”升维成“技术分析”。5.1 证据1用sys.dm_exec_query_stats抓取慢查询的CPU与I/O指纹在执行完lab2_query.sql后立即运行以下语句将结果截图放入报告“性能分析”章节-- 抓取最近执行的慢查询逻辑读1000或CPU时间1000ms SELECT qs.execution_count, qs.total_logical_reads / qs.execution_count AS avg_logical_reads, qs.total_worker_time / qs.execution_count / 1000 AS avg_cpu_ms, SUBSTRING(qt.text, (qs.statement_start_offset/2)1, ((CASE qs.statement_end_offset WHEN -1 THEN DATALENGTH(qt.text) ELSE qs.statement_end_offset END - qs.statement_start_offset)/2) 1) AS query_text FROM sys.dm_exec_query_stats AS qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS qt WHERE qs.total_logical_reads 1000 OR qs.total_worker_time 1000000 ORDER BY qs.total_logical_reads DESC;为什么这个截图比“执行时间对比表”更有说服力avg_logical_reads是数据库引擎真实的物理/内存页读取次数它不受SSMS界面渲染、网络延迟、客户端缓存干扰。若建索引后该值从2500降至12老师立刻明白你解决了I/O瓶颈若仍是2500则说明索引未被使用——这比写“索引提高了性能”有力十倍。5.2 证据2用DBCC PAGE导出数据页原始结构需开启trace flag这是SQL Server最硬核的底层证据。在lab1_schema.sql插入数据后执行-- 步骤1启用DBCC PAGE权限需sysadmin角色 DBCC TRACEON(3604, -1); -- 将输出重定向到客户端 -- 步骤2查Students表的数据页ID SELECT p.partition_id, p.rows, au.type_desc, au.container_id FROM sys.partitions p JOIN sys.allocation_units au ON p.partition_id au.container_id WHERE p.object_id OBJECT_ID(Students); -- 步骤3假设container_id72057594038321152查第1页page_id1 DBCC PAGE(LabDB, 1, 1, 3); -- 参数3详细模式显示每行数据的十六进制存储报告中如何呈现截图DBCC PAGE输出中Slot 0 Offset 0x60 Length 42这一行标注0x60 96字节偏移即第1行数据起始位置Length 42 该行共42字节其中Name字段占12字节NVARCHAR(50)实际存储长度取决于内容0x5F005A0061006E006700 “张”字的UTF-16编码0x5F00是‘张’的Unicode码点0x5F00。这证明你看到了数据在磁盘上的真实字节布局不是黑匣子。5.3 证据3用sys.dm_tran_locks捕捉死锁瞬间的资源持有链在lab3_transaction.sql死锁发生后立即执行-- 查死锁时各会话持有的锁 SELECT tl.request_session_id, tl.resource_type, tl.resource_description, tl.request_mode, tl.request_status, t.text AS sql_text FROM sys.dm_tran_locks tl JOIN sys.dm_exec_requests er ON tl.request_session_id er.session_id CROSS APPLY sys.dm_exec_sql_text(er.sql_handle) t WHERE tl.resource_database_id DB_ID(LabDB) ORDER BY tl.request_session_id;报告中关键标注找到两个session_id如57和58截图并箭头标注Session 57持有OBJECT锁Students表等待KEY锁Scores表的某行Session 58持有KEY锁Scores表等待OBJECT锁Students表。这就是死锁的拓扑图A→B→A的循环等待。老师看到这个就知道你理解了死锁本质而非只会背定义。最后说一句个人习惯我写实验报告时所有截图都带时间戳水印用系统自带截图工具所有SQL脚本文件名含日期如lab2_query_20240615.sql。这不是为了防伪而是强迫自己建立“可追溯”的工程思维——今天能复现的实验三个月后仍能一键重跑。希望帮到你。本文还有配套的精品资源点击获取