整合 sys.indexes、sys.dm_db_index_usage_stats、sys.dm_db_partition_stats,產出單一查詢掌握所有索引的讀取/寫入次數、最後使用時間、空間佔用與讀寫差異,快速識別未使用、低效、過大索引。
SELECT
sch.name + '.' + t.name AS [Table Name],
i.name AS [Index Name],
i.type_desc,
ISNULL(user_updates, 0) AS [Total Writes],
ISNULL(user_seeks + user_scans + user_lookups, 0) AS [Total Reads],
s.last_user_seek,
s.last_user_scan,
s.last_user_lookup,
ISNULL(user_updates, 0) - ISNULL(user_seeks + user_scans + user_lookups, 0) AS [Difference],
p.reserved_page_count * 8.0 / 1024 AS SpaceInMB
FROM sys.indexes AS i WITH (NOLOCK)
LEFT JOIN sys.dm_db_index_usage_stats AS s WITH (NOLOCK)
ON s.object_id = i.object_id AND i.index_id = s.index_id AND s.database_id = DB_ID()
JOIN sys.tables AS t WITH (NOLOCK) ON i.object_id = t.object_id
JOIN sys.schemas AS sch WITH (NOLOCK) ON t.schema_id = sch.schema_id
LEFT JOIN sys.dm_db_partition_stats AS p WITH (NOLOCK)
ON i.index_id = p.index_id AND i.object_id = p.object_id
WHERE OBJECTPROPERTY(i.object_id, 'IsUserTable') = 1
ORDER BY [Table Name], [Index Name];
| 指標 | 解讀 | 行動建議 |
|---|---|---|
Total Reads = 0 且 Total Writes > 0 | 純寫入負擔,從未被查詢使用 | 刪除候選(確認無外鍵/唯一約束依賴) |
Difference > 0(寫入 > 讀取) | 維護成本高於查詢收益 | 評估移除或合併為覆蓋索引 |
last_user_seek/scan/lookup 為 NULL 或極舊 | 長期未被存取 | 同上,優先檢視 |
SpaceInMB 大且低使用率 | 佔用寶貴緩衝集區與磁碟空間 | 考慮壓縮、分割或移除 |
type_desc = HEAP 且高掃描 | 無叢集索引,全表掃描 | 建立適當叢集索引 |
-- 僅顯示從未使用的索引
-- AND ISNULL(user_updates,0) - ISNULL(user_seeks+user_scans+user_lookups,0) > 0
-- 僅非主鍵索引
-- AND i.index_id > 1 AND i.is_primary_key = 0 AND i.is_unique_constraint = 0
dm_db_index_usage_stats **服務重啟歸零**,長期分析需定期持久化至歷史表。