SQL Server存储过程本身不产生碎片,真正产生碎片的是其操作的表和索引;需通过sys.dm_db_index_physical_stats定位高碎片索引,按10%、30%阈值选择REORGANIZE或REBUILD,并及时更新统计信息。

SQL Server 存储过程中不会直接“产生碎片”,真正产生碎片的是底层表和索引——尤其是当存储过程频繁执行 INSERT、UPDATE、DELETE(特别是大批次删除或随机更新)时,会加剧对应表的索引碎片。所以清理目标不是存储过程本身,而是它操作的那些对象。
如何确认是哪个索引被存储过程反复修改导致碎片升高
运行以下查询,重点关注你存储过程常操作的表(比如 Orders、Logs 等):
SELECT
OBJECT_NAME(ps.object_id) AS TableName,
i.name AS IndexName,
ps.avg_fragmentation_in_percent AS FragPct,
ps.page_count
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') ps
INNER JOIN sys.indexes i ON ps.object_id = i.object_id AND ps.index_id = i.index_id
WHERE ps.avg_fragmentation_in_percent > 10
AND ps.page_count > 1000
ORDER BY ps.avg_fragmentation_in_percent DESC;-
'LIMITED'模式快但不精确;若需更准(如判断是否要重建),可换'DETAILED',但会加锁且耗时 - 只查
page_count > 1000的索引,避免小索引干扰判断 - 如果某索引碎片长期 >30%,大概率就是被你的存储过程高频修改的热点对象
重建 vs 重组:选哪个取决于碎片程度和业务窗口
ALTER INDEX ... REBUILD 和 ALTER INDEX ... REORGANIZE 不是二选一,是按条件切换:
- 碎片 < 10%:不用处理,统计信息更新即可
- 碎片在 10%–30%:用
REORGANIZE,在线、低开销、不阻塞读写(但会短暂持有意向锁) - 碎片 > 30%:用
REBUILD,彻底重写索引页,能顺便压缩 LOB 数据、重置填充因子,但默认会锁表(除非加ONLINE = ON,且企业版才支持)
示例:
-- 重组(适合白天维护窗口) ALTER INDEX IX_Orders_Status ON Orders REORGANIZE; <p>-- 重建(适合夜间或停机窗口) ALTER INDEX PK_Orders ON Orders REBUILD WITH (ONLINE = ON, FILLFACTOR = 90);
-
FILLFACTOR = 90预留 10% 空间,缓解后续插入分裂(但别设太低,否则浪费空间、增加 I/O) -
ONLINE = ON在标准版不可用,强行使用会报错Msg 155
更新统计信息不能省,尤其在重建/重组后
索引重建或重组不会自动更新统计信息(除非显式加 STATISTICS_NORECOMPUTE = OFF,但默认是 ON)。如果统计信息过旧,查询优化器仍可能生成低效执行计划,让碎片清理白做。
推荐做法:
- 对刚处理过的表,立即更新统计信息:
UPDATE STATISTICS Orders WITH FULLSCAN;
- 或只更新关键列(更快):
UPDATE STATISTICS Orders (IX_Orders_CreatedTime) WITH SAMPLE 50 PERCENT;
- 避免全库跑
sp_updatestats,它对未改动的表也扫一遍,纯属浪费资源
容易被忽略的关键点
- 存储过程里如果用了临时表(
#temp),它们的索引也会碎片化,但生命周期短,通常不用管;不过若长期驻留(如用##global),就得单独查tempdb.sys.dm_db_index_physical_stats - 分区表要注意:
REORGANIZE可针对单个分区,REBUILD默认整表;误操作可能把冷数据也重刷一遍 -
REBUILD会重置索引的last_user_seek/scan/lookup时间戳,后续再看sys.dm_db_index_usage_stats就失真了,排查问题时得心里有数 - 如果存储过程大量使用表变量(
@table),它们不走统计信息,也不产碎片,但可能因缺少行数预估导致嵌套循环失控——这是另一类性能病,别和索引碎片混为一谈

















