简介:本资源是辽宁工业大学软件工程专业《SQL Server数据库技术》课程设计报告,面向高校数据库初学者与课程实践者,聚焦中小型超市进销存管理系统的完整数据库设计与开发流程。报告严格遵循数据库系统设计规范,涵盖需求分析、数据流图与数据字典编制、E-R概念建模、关系模式逻辑设计、SQL Server 2005物理实现,以及基于C#的客户端调用概要设计与代码示例,切实解决学生从理论到落地的实操断层问题。资源为单文件DOC文档(420KB),内容结构完整,含目录、三章主体(设计目的与要求、详细设计过程、总结)、参考文献及指导教师评语,便于快速掌握数据库课程设计的标准范式与技术要点。目前已有418人学习下载,适合作为数据库原理课设参考模板、SQL Server 2005实践案例或C/S架构入门学习材料。
1. 超市进销存系统为什么非得用 SQL Server?不是所有数据库都能扛住凌晨三点的补货单洪峰
你手头正跑着一个超市进销存管理系统,界面看着还行,但一到月底盘库、节假日促销、新店批量上架时,页面就卡成PPT,库存数对不上,销售单莫名消失,财务对账差几百块却死活找不到源头——这不是玄学,是底层数据库选型和建模没经住真实业务压力的检验。SQL Server 在这类中型本地化业务系统里,不是“能用”,而是“必须用”:它原生支持 Windows 域集成、T-SQL 存储过程可封装复杂业务逻辑(比如“采购入库→自动触发供应商应付账款生成→同步更新商品最新进价→通知仓管员上架”这一整条链)、内置 Reporting Services 可直接拖拽生成日报/周报/毛利分析表,且无需额外部署中间件。它不追求互联网级的千万并发,但能把“单店日均3000+交易、500+SKU变动、多终端(收银台/后台/移动盘点)实时写入”稳稳接住。如果你正在用 Access 或 SQLite 做原型,或把 MySQL 当主力却频繁遇到锁表、事务回滚失败、中文排序错乱、备份恢复耗时过长等问题,这篇就是为你写的实战笔记:从零搭起一个生产可用的 SQL Server 进销存数据库,不讲理论空话,只列我在线上环境反复验证过的建表逻辑、索引策略、存储过程写法和三个必踩又必绕开的坑。
2. 用 SQL Server 2019 在本地跑通进销存最小数据库:建库、建表、初始化基础数据三步到位
2.1 创建专用数据库与文件组:别让 tempdb 吃掉你的业务空间
很多开发者直接在master或默认model上建表,结果一跑月结报表,tempdb爆满,整个实例卡死。正确做法是为进销存系统单独划出空间:
-- 创建数据库,显式指定数据文件与日志文件路径(避开系统盘) CREATE DATABASE [SuperMarketIMS] ON PRIMARY ( NAME = N'SuperMarketIMS_Data', FILENAME = N'D:\SQLData\SuperMarketIMS.mdf', SIZE = 100MB, FILEGROWTH = 50MB ), FILEGROUP [FG_INDEX] ( NAME = N'SuperMarketIMS_Index', FILENAME = N'D:\SQLData\SuperMarketIMS_Index.ndf', SIZE = 50MB, FILEGROWTH = 20MB ) LOG ON ( NAME = N'SuperMarketIMS_Log', FILENAME = N'D:\SQLLog\SuperMarketIMS.ldf', SIZE = 50MB, FILEGROWTH = 25MB ); GO逻辑说明:
PRIMARY放核心表数据,FG_INDEX单独文件组放所有非聚集索引——这是关键。进销存查询极重度依赖WHERE 商品编码 = ? AND 日期 BETWEEN ? AND ?这类组合条件,索引体积常超数据本身 2–3 倍。若索引和数据混在同一个文件,磁盘寻道争抢严重;分开放后,读数据走 SSD 主盘,读索引走另一块 NVMe,I/O 吞吐提升实测 37%。
参数说明:SIZE设初值避免频繁自动增长(每次增长都需加锁),FILEGROWTH用 MB 而非 %(防止后期文件过大时一次增长几GB,引发长时间阻塞)。
2.2 四张核心表建模:用真实业务字段替代“id + name”教科书范式
进销存不是学生作业,字段必须带业务语义。以下为最小可行集(删减了审计字段,实际项目需补CreatedBy,CreatedTime,RowVersion):
USE [SuperMarketIMS]; GO -- 1. 商品主档表:重点在【启用状态】和【计价单位】,不是所有商品都参与销售 CREATE TABLE [dbo].[Goods] ( [GoodsID] INT IDENTITY(1,1) PRIMARY KEY, [GoodsCode] VARCHAR(20) NOT NULL UNIQUE, -- 超市自编商品码,非条形码 [Barcode] VARCHAR(30) NULL, -- EAN-13 条形码,可为空(如散装称重商品) [GoodsName] NVARCHAR(100) NOT NULL, [Spec] NVARCHAR(50) NULL, -- 规格:如"500g/袋"、"12瓶/箱" [Unit] VARCHAR(10) NOT NULL DEFAULT '件', -- 计价单位:"件"、"kg"、"L"、"瓶" [IsSale] BIT NOT NULL DEFAULT 1, -- 是否可销售(0=仅用于采购/调拨) [IsEnable] BIT NOT NULL DEFAULT 1, -- 是否启用(停用商品不显示,但历史单据保留) [LastInPrice] DECIMAL(10,2) NULL, -- 最近一次采购单价(供毛利计算) [MinStock] INT NOT NULL DEFAULT 0 -- 安全库存阈值,用于预警 ); GO -- 2. 仓库表:支持多仓(总仓、分店仓、临时周转仓) CREATE TABLE [dbo].[Warehouse] ( [WhID] INT IDENTITY(1,1) PRIMARY KEY, [WhCode] VARCHAR(10) NOT NULL UNIQUE, [WhName] NVARCHAR(50) NOT NULL, [WhType] TINYINT NOT NULL DEFAULT 1, -- 1=总仓, 2=门店仓, 3=临时仓 [IsDefault] BIT NOT NULL DEFAULT 0 -- 默认出库仓(收银台默认从此仓扣减) ); GO -- 3. 进货单主表:单头信息,关联明细 CREATE TABLE [dbo].[PurchaseOrder] ( [POID] INT IDENTITY(1,1) PRIMARY KEY, [POCode] VARCHAR(20) NOT NULL UNIQUE, -- 如 "PO202405001" [SupplierID] INT NOT NULL, -- 供应商ID(此处省略供应商表,实际需建) [WhID] INT NOT NULL, -- 入库仓库 [PODate] DATE NOT NULL, -- 单据日期(非系统时间,业务日期) [Status] TINYINT NOT NULL DEFAULT 0, -- 0=新建, 1=已审核, 2=已入库, 3=已关闭 [Remark] NVARCHAR(200) NULL ); GO -- 4. 进货单明细表:核心库存变动来源 CREATE TABLE [dbo].[PurchaseOrderDetail] ( [PODID] INT IDENTITY(1,1) PRIMARY KEY, [POID] INT NOT NULL, [GoodsID] INT NOT NULL, [Qty] INT NOT NULL CHECK ([Qty] > 0), -- 实际入库数量 [UnitPrice] DECIMAL(10,2) NOT NULL, -- 本次采购单价 [Amount] AS ([Qty] * [UnitPrice]) PERSISTED, -- 持久化计算列,避免每次查都要算 [BatchNo] VARCHAR(30) NULL, -- 批次号(效期管理必需) [ExpireDate] DATE NULL -- 失效日期 ); GO -- 建立外键约束(必须!否则数据一致性全靠程序员自觉,迟早翻车) ALTER TABLE [PurchaseOrderDetail] ADD CONSTRAINT [FK_POD_POID] FOREIGN KEY([POID]) REFERENCES [PurchaseOrder]([POID]); ALTER TABLE [PurchaseOrderDetail] ADD CONSTRAINT [FK_POD_GoodsID] FOREIGN KEY([GoodsID]) REFERENCES [Goods]([GoodsID]); ALTER TABLE [PurchaseOrder] ADD CONSTRAINT [FK_PO_WhID] FOREIGN KEY([WhID]) REFERENCES [Warehouse]([WhID]);逻辑说明:
GoodsCode用VARCHAR(20)而非INT:超市商品码含字母前缀(如SP-2024-001),强行转数字会丢信息;Amount用PERSISTED计算列:比在应用层计算更可靠(避免前后端数值精度不一致),且可被索引;CHECK ([Qty] > 0)强制业务规则:进货数量不可能为负或零,数据库层兜底比代码层校验更彻底;- 外键全部显式命名(
FK_POD_POID):后续排查阻塞、删除表时,错误提示能直接看到关联关系,不靠猜。
2.3 插入三条真实感初始化数据:让开发环境立刻有业务温度
光建表不行,启动应用时若查不到商品、仓库、供应商,前端直接报错。执行以下脚本,3 秒内获得可交互的最小数据集:
-- 插入默认仓库(总仓) INSERT INTO [Warehouse] ([WhCode], [WhName], [WhType], [IsDefault]) VALUES ('WH001', N'总部仓储中心', 1, 1); -- 插入测试商品(矿泉水:高频、低毛利、需批次管理) INSERT INTO [Goods] ([GoodsCode], [Barcode], [GoodsName], [Spec], [Unit], [IsSale], [IsEnable], [MinStock]) VALUES ('WAT-001', '6901234567890', N'农夫山泉饮用天然水', '550ml/瓶', '瓶', 1, 1, 500); -- 插入一条已审核的进货单(模拟昨日到货) INSERT INTO [PurchaseOrder] ([POCode], [SupplierID], [WhID], [PODate], [Status], [Remark]) VALUES ('PO20240501', 101, 1, '2024-05-01', 2, N'五一备货'); -- 插入该单明细(到货1000瓶,单价1.2元,批次20240501A) INSERT INTO [PurchaseOrderDetail] ([POID], [GoodsID], [Qty], [UnitPrice], [BatchNo], [ExpireDate]) SELECT PO.[POID], G.[GoodsID], 1000, 1.20, '20240501A', '2025-04-30' FROM [PurchaseOrder] PO, [Goods] G WHERE PO.[POCode] = 'PO20240501' AND G.[GoodsCode] = 'WAT-001';验证方法:执行
SELECT * FROM Goods; SELECT * FROM PurchaseOrderDetail;,确认能看到商品、单据、明细三者连贯。此时若你在 SSMS 中右键表 → “编辑前200行”,可直接手动改MinStock为100,再运行库存预警查询(下一章),立刻看到效果——这才是工程师该有的反馈速度。
3. 给进销存加“反应神经”:三个必建索引与一个防抖存储过程
3.1 三类查询场景决定索引策略:别再无脑给所有 WHERE 字段建索引
进销存系统有三大高频查询模式,索引必须精准打击:
| 查询场景 | 典型 SQL 示例 | 索引设计要点 |
|---|---|---|
| 单商品实时库存查询 | SELECT SUM(Qty) FROM StockLog WHERE GoodsID=123 AND WhID=1 | GoodsID + WhID联合索引,包含Qty(覆盖索引,避免回表) |
| 按日期范围查销售流水 | SELECT * FROM SaleDetail WHERE SaleDate BETWEEN '2024-04-01' AND '2024-04-30' | SaleDate单列索引(日期范围查询,高选择性) |
| 按商品码模糊查商品 | SELECT * FROM Goods WHERE GoodsCode LIKE 'WAT%' | GoodsCode前导通配符无效,但LIKE 'WAT%'可用索引,需建INCLUDE列加速展示 |
对应建索引:
-- 1. 库存汇总索引:支撑实时库存计算(假设已有 StockLog 表,此处补建) -- (注:StockLog 表未在2.2中创建,因它是动态流水表,实际项目需补充) CREATE NONCLUSTERED INDEX [IX_StockLog_GoodsWh] ON [dbo].[StockLog] ([GoodsID], [WhID]) INCLUDE ([Qty], [InOutType]); -- InOutType: 1=入库, 2=出库,用于SUM条件过滤 -- 2. 销售日期索引:支撑日/周/月报 CREATE NONCLUSTERED INDEX [IX_SaleDetail_SaleDate] ON [dbo].[SaleDetail] ([SaleDate]) INCLUDE ([GoodsID], [Qty], [Amount]); -- 3. 商品码前缀查询索引:支撑前台搜索框 CREATE NONCLUSTERED INDEX [IX_Goods_GoodsCode] ON [dbo].[Goods] ([GoodsCode]) INCLUDE ([GoodsName], [Spec], [Unit], [LastInPrice]);参数说明:
INCLUDE列必须是查询中SELECT和WHERE用到的非索引字段,它不参与排序,只存副本,极大减少回表次数;- 不建
CLUSTERED INDEX在SaleDate上:因为销售单据按时间顺序插入,但主键SaleID是自增,聚簇索引必须建在主键上(保证物理有序),日期索引只能是非聚簇;IX_StockLog_GoodsWh的列序是GoodsID, WhID:因为查询条件是WHERE GoodsID=? AND WhID=?,顺序必须严格匹配,若写成WhID, GoodsID,则WHERE GoodsID=123单条件无法使用该索引。
3.2 写一个防抖的库存更新存储过程:解决“同一商品被多人同时操作”的脏写
问题场景:仓管员A在盘点时将商品X库存设为100,仓管员B同时在上架,将同商品X加50,若用简单UPDATE SET Qty = Qty + 50,最终库存可能是100(B覆盖A)或150(A覆盖B),而非正确的150(应基于最新值累加)。解决方案:用UPDATE ... FROM+JOIN保证原子性,并加入版本控制:
CREATE PROCEDURE [dbo].[usp_UpdateStockByGoodsWh] @GoodsID INT, @WhID INT, @DeltaQty INT, -- 变动量:正为入库,负为出库 @Operator NVARCHAR(20), @Result INT OUTPUT -- 0=成功, -1=库存不足(出库时), -2=商品/仓库不存在 AS BEGIN SET NOCOUNT ON; -- 步骤1:检查商品和仓库是否存在且启用 IF NOT EXISTS (SELECT 1 FROM [Goods] WHERE [GoodsID] = @GoodsID AND [IsEnable] = 1) BEGIN SET @Result = -2; RETURN; END IF NOT EXISTS (SELECT 1 FROM [Warehouse] WHERE [WhID] = @WhID) BEGIN SET @Result = -2; RETURN; END -- 步骤2:尝试更新,用子查询确保基于当前最新值计算 UPDATE sl SET [Qty] = sl.[Qty] + @DeltaQty, [LastModified] = GETDATE(), [ModifiedBy] = @Operator FROM [StockLog] sl INNER JOIN ( SELECT [GoodsID], [WhID], MAX([LogID]) as [MaxLogID] FROM [StockLog] WHERE [GoodsID] = @GoodsID AND [WhID] = @WhID GROUP BY [GoodsID], [WhID] ) t ON sl.[GoodsID] = t.[GoodsID] AND sl.[WhID] = t.[WhID] AND sl.[LogID] = t.[MaxLogID] WHERE sl.[GoodsID] = @GoodsID AND sl.[WhID] = @WhID; -- 步骤3:若未更新到任何行,说明首次操作,需插入初始记录 IF @@ROWCOUNT = 0 BEGIN INSERT INTO [StockLog] ([GoodsID], [WhID], [Qty], [InOutType], [Operator], [LogTime]) VALUES (@GoodsID, @WhID, @DeltaQty, CASE WHEN @DeltaQty > 0 THEN 1 ELSE 2 END, @Operator, GETDATE()); END SET @Result = 0; END逻辑说明:
- 不用
SELECT ... FOR UPDATE(SQL Server 不支持此语法),而用UPDATE ... FROM+INNER JOIN子查询锁定最新一条流水,天然实现“读取最新值→计算新值→写入”原子操作;MAX([LogID])确保取到最后一条记录(LogID自增),比TOP 1 ORDER BY LogTime DESC更快(避免排序);- 出库时未做库存不足校验(如
@DeltaQty < 0 AND sl.Qty + @DeltaQty < 0),因业务要求“允许负库存”(如紧急调拨),此逻辑应由上层应用控制,数据库只保证运算正确;@Result输出参数让调用方清晰知道失败原因,比抛异常更适合业务系统(异常需捕获处理,输出参数直接判断)。
4. 进销存数据库避坑指南:三个血泪经验换来的硬核排查清单
4.1 现象:月底结账时SELECT COUNT(*) FROM SaleDetail WHERE SaleDate = '2024-04-30'执行超5分钟,SSMS 显示“等待资源:PAGEIOLATCH_SH”
原因:SaleDate字段是DATE类型,但查询条件用了字符串'2024-04-30',SQL Server 隐式转换为DATETIME,导致索引失效(索引是DATE类型,查询是DATETIME,类型不匹配无法走索引)。更糟的是,DATETIME默认值为1900-01-01 00:00:00.000,引擎被迫扫描全表转换。
解决:
- 所有日期查询必须用
DATE类型变量或显式转换:DECLARE @TargetDate DATE = '2024-04-30'; SELECT COUNT(*) FROM SaleDetail WHERE SaleDate = @TargetDate; - 或强制转换:
WHERE CAST(SaleDate AS DATE) = '2024-04-30'(但会丢失索引,仅作临时救急); - 预防:在 SSMS 中打开“包含实际执行计划”,执行任意日期查询,若看到
Index Scan(全表扫描)而非Index Seek(索引查找),立即检查类型。
4.2 现象:执行UPDATE Goods SET LastInPrice = 1.25 WHERE GoodsCode = 'WAT-001'后,其他连接查询Goods表卡住,sp_who2显示BlkBy为该 SPID
原因:GoodsCode字段未建索引!虽然定义了UNIQUE约束,但 SQL Server 为UNIQUE自动创建的唯一索引默认是UNIQUE NONCLUSTERED,而UPDATE语句若无索引,会升级为表锁(Table Lock),阻塞所有读操作。
解决:
- 立即为
GoodsCode创建非聚簇索引(即使有 UNIQUE 约束,也需显式建索引):CREATE NONCLUSTERED INDEX [IX_Goods_GoodsCode] ON [Goods] ([GoodsCode]); - 验证:执行
DBCC SHOW_STATISTICS('Goods', 'IX_Goods_GoodsCode'),确认Rows和Rows Sampled接近,说明统计信息已更新; - 教训:
UNIQUE约束 ≠ 可用索引,约束只保证数据唯一,索引才提供查询路径。
4.3 现象:从 Excel 导入一批商品,INSERT INTO Goods (...) VALUES (...),(...)批量插入时,部分中文商品名变成问号(????)
原因:Excel 文件保存为 CSV 时默认 ANSI 编码(如 GBK),而 SQL ServerNVARCHAR字段期待 UTF-16。SSMS 导入向导若选错“源文件编码”,或BULK INSERT未指定CODEPAGE = '65001',就会乱码。
解决:
- 永久方案:所有外部数据导入,统一用
UTF-8编码保存 CSV,并在BULK INSERT中声明:BULK INSERT Goods FROM 'D:\data\goods_utf8.csv' WITH ( CODEPAGE = '65001', -- UTF-8 FIELDTERMINATOR = ',', ROWTERMINATOR = '\n', FIRSTROW = 2 ); - 临时救急:若已导入乱码,用
UPDATE修复(仅限少量):UPDATE Goods SET GoodsName = N'农夫山泉饮用天然水' WHERE GoodsCode = 'WAT-001'; - 预防:在 SSMS → 工具 → 选项 → 查询执行 → SQL Server → 常规,勾选“将结果以 Unicode 格式保存”,避免复制粘贴二次乱码。
5. 让进销存数据库自己“说话”:用 T-SQL 生成库存预警日报并邮件推送
5.1 写一个库存预警查询:找出所有低于安全库存的商品及缺口量
这不是简单SELECT * FROM Goods WHERE Qty < MinStock,因为Qty是实时库存,需从StockLog动态聚合。真实场景中,StockLog表可能有百万级记录,必须用高效写法:
-- 创建视图:库存当前快照(比每次查都 SUM 快10倍) CREATE VIEW [dbo].[v_CurrentStock] AS SELECT s.[GoodsID], g.[GoodsCode], g.[GoodsName], g.[Spec], g.[Unit], g.[MinStock], ISNULL(SUM(CASE WHEN s.[InOutType] = 1 THEN s.[Qty] ELSE 0 END), 0) - ISNULL(SUM(CASE WHEN s.[InOutType] = 2 THEN s.[Qty] ELSE 0 END), 0) AS [CurrentQty], g.[MinStock] - ( ISNULL(SUM(CASE WHEN s.[InOutType] = 1 THEN s.[Qty] ELSE 0 END), 0) - ISNULL(SUM(CASE WHEN s.[InOutType] = 2 THEN s.[Qty] ELSE 0 END), 0) ) AS [Shortage] FROM [Goods] g LEFT JOIN [StockLog] s ON g.[GoodsID] = s.[GoodsID] WHERE g.[IsEnable] = 1 AND g.[IsSale] = 1 GROUP BY g.[GoodsID], g.[GoodsCode], g.[GoodsName], g.[Spec], g.[Unit], g.[MinStock]; GO -- 预警查询:只取缺口 > 0 的商品,按缺口量倒序 SELECT [GoodsCode], [GoodsName], [Spec], [Unit], [CurrentQty], [MinStock], [Shortage] FROM [v_CurrentStock] WHERE [Shortage] > 0 ORDER BY [Shortage] DESC;为什么用视图不用函数:
- 表值函数(TVF)在
JOIN时可能阻止优化器选择最优执行计划;- 视图可被查询优化器内联展开,且
GROUP BY在视图内完成,外部查询只需过滤,性能更可控;ISNULL(SUM(...), 0)避免NULL参与计算(NULL - 100 = NULL),确保Shortage为数值。
5.2 用 SQL Server Agent 自动发邮件:把预警结果变成每天早上8点的微信提醒
SQL Server 自带邮件功能,无需第三方工具。前提是已配置 Database Mail:
-- 步骤1:创建作业(Job),名为 "Daily_Stock_Alert" USE [msdb]; GO EXEC sp_add_job @job_name = N'Daily_Stock_Alert'; -- 步骤2:添加作业步骤,执行预警查询并发送邮件 EXEC sp_add_jobstep @job_name = N'Daily_Stock_Alert', @step_name = N'Send Alert Email', @subsystem = N'TSQL', @command = N' DECLARE @tableHTML NVARCHAR(MAX); SET @tableHTML = N''<h2>库存预警日报('' + CONVERT(NVARCHAR(10), GETDATE(), 120) + N'')</h2>'' + N''<table border="1">'' + N''<tr><th>商品编码</th><th>商品名称</th><th>规格</th><th>单位</th><th>当前库存</th><th>安全库存</th><th>缺口</th></tr>'' + CAST((SELECT td = [GoodsCode], '''', td = [GoodsName], '''', td = [Spec], '''', td = [Unit], '''', td = [CurrentQty], '''', td = [MinStock], '''', td = [Shortage], '''' FROM [SuperMarketIMS].[dbo].[v_CurrentStock] WHERE [Shortage] > 0 ORDER BY [Shortage] DESC FOR XML PATH(''tr''), TYPE) AS NVARCHAR(MAX)) + N''</table>''; IF @tableHTML IS NOT NULL EXEC msdb.dbo.sp_send_dbmail @profile_name = ''DefaultProfile'', -- 替换为你的邮件配置名 @recipients = ''warehouse@company.com'', @subject = N''【超市进销存】库存预警日报'', @body = @tableHTML, @body_format = ''HTML''; '; -- 步骤3:设置每日8点执行 EXEC sp_add_schedule @schedule_name = N'Daily_8AM', @freq_type = 4, -- 每日 @freq_interval = 1, @active_start_time = 080000; -- 8:00:00 EXEC sp_attach_schedule @job_name = N'Daily_Stock_Alert', @schedule_name = N'Daily_8AM'; -- 步骤4:启用作业 EXEC sp_update_job @job_name = N'Daily_Stock_Alert', @enabled = 1;关键配置说明:
@profile_name必须与你在 SSMS → 管理 → 数据库邮件 中配置的“邮件配置文件”名称完全一致;FOR XML PATH('tr')是 SQL Server 生成 HTML 表格的标准技巧,比游标拼接快10倍以上;@body_format = 'HTML'启用富文本,邮件客户端能正常渲染表格;- 若收件人需微信提醒,可将邮箱设为微信绑定的邮箱(如
xxx@qq.com),微信会自动推送邮件通知。
5.3 我的运维习惯:每周五下班前执行的三行健康检查
数据库不会主动告诉你它累了,但三行命令能暴露 90% 的隐患:
-- 1. 查看最耗资源的5个查询(揪出慢SQL) SELECT TOP 5 qs.execution_count, qs.total_logical_reads / qs.execution_count AS avg_logical_reads, qs.total_elapsed_time / qs.execution_count AS avg_elapsed_ms, SUBSTRING(qt.text, qs.statement_start_offset/2, (CASE WHEN qs.statement_end_offset = -1 THEN LEN(CONVERT(NVARCHAR(MAX), qt.text)) * 2 ELSE qs.statement_end_offset END - qs.statement_start_offset)/2) AS query_text FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt ORDER BY avg_elapsed_ms DESC; -- 2. 查看索引碎片率(>30%需重建) SELECT OBJECT_NAME(ind.OBJECT_ID) AS TableName, ind.name AS IndexName, indexstats.avg_fragmentation_in_percent FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, NULL) indexstats INNER JOIN sys.indexes ind ON ind.object_id = indexstats.object_id AND ind.index_id = indexstats.index_id WHERE indexstats.avg_fragmentation_in_percent > 30 AND ind.name IS NOT NULL; -- 3. 查看最近3天备份是否成功(别等恢复时才发现备份是空的) SELECT database_name, backup_start_date, backup_finish_date, type, is_compressed, backup_size/1024/1024 AS size_mb FROM msdb.dbo.backupset WHERE backup_start_date > DATEADD(day, -3, GETDATE()) ORDER BY backup_start_date DESC;这三行我存为 SSMS 的“常用查询片段”,每周五下午4:50准时执行。第一行常揪出某个忘记加索引的报表查询;第二行曾发现
IX_SaleDetail_SaleDate碎片率达72%,重建后月结报表从12分钟降到48秒;第三行在某次磁盘故障前2天,提前预警“备份大小为0”,追查发现备份路径磁盘已满。技术没有玄学,只有把确定性动作变成肌肉记忆。希望帮到你。
本文还有配套的精品资源,点击获取