网上关于「索引碎片到多少该重建」的说法从 10% 到 30% 都有,我一开始照着某篇博客设了个 30% 的阈值,结果半夜被报警叫起来。这里把我按自己库的情况记下来的经验值写一下,不确定换到别的场景还成不成立。
怎么看碎片率
核心就是 sys.dm_db_index_physical_stats 这个动态管理函数,它返回的 avg_fragmentation_in_percent 就是碎片率。我平时跑下面这段,把超过一定比例的索引捞出来看:
SELECT
OBJECT_NAME(ips.object_id) AS 表名,
i.name AS 索引名,
ips.avg_fragmentation_in_percent AS 碎片率
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') AS ips
JOIN sys.indexes AS i
ON ips.object_id = i.object_id AND ips.index_id = i.index_id
WHERE ips.avg_fragmentation_in_percent > 10
ORDER BY ips.avg_fragmentation_in_percent DESC;
我的经验阈值
微软官方文档里给的是:5% 以下不用管;5%–30% 之间用 REORGANIZE(在线整理,不锁表,慢但温和);超过 30% 才考虑 REBUILD。我基本按这个来,只是把「要动手」的下限设到了 10%,因为低于 10% 时整理带来的提升肉眼看不见,白跑一遍 IO 不划算。
REBUILD 的代价明显更大:它会重建整个索引,默认会锁表(除非企业版开 ONLINE),而且会撑大事务日志。所以我只在周末维护窗口、且确认碎片率确实很高的时候才跑 REBUILD,平时能 REORGANIZE 就 REORGANIZE。
我现在的做法是每周日凌晨跑一次维护计划,只 REORGANIZE 碎片率在 10%–30% 之间的索引,超过 30% 的单独挑出来在维护窗口里 REBUILD,并且把每次的结果记到一张日志表,方便回头看哪些表碎片长得快。跑了一两个月,发现增长快的就那两三张经常批量写入的表,针对性地调高了它们的填充因子,碎片率明显平稳了。这套节奏我想了挺久才定下来,不敢说适合所有人,小库可能根本不用这么勤。
一个我踩过的坑
那次半夜报警,监控显示某张大表的索引碎片率一下冲到了 40%,我本能地想去 REBUILD,后来被同事拦住——看了下写入曲线才发现,那是每天凌晨的批量导入刚跑完,碎片本来就是这一波高频写入堆出来的,等导入结束、后续的查询把页重新整理过,碎片率自己就落回去了。也就是说,看起来「该重建」,其实只是写入高峰的副产物,重建反而会和白天业务抢资源。
所以我现在多了一个习惯:看到碎片率突然升高,先看是不是有定时的大批量写入或删除,有的话等它跑完再复查一次,别急着动手。这个判断我现在也不算有把握,只是目前没再因此误报过。
补充一句,碎片率也不是越低越好,太频繁地整理本身也是开销,我宁可它偶尔高一点,也不想每天跑维护把 IO 占满。维护窗口排在什么时段,比阈值本身更值得花心思,这个道理虽然简单,我第一次就是把维护排在了白天业务高峰,被同事提醒才改过来。