唯一可靠方法是用sys.indexes与sys.dm_db_index_usage_stats做差集。因后者不记录未使用索引,仅显示自重启后被访问过的索引,直接查零值会漏判或误判,且需过滤系统对象、堆和主键约束。

直接查 sys.dm_db_index_usage_stats,但必须结合 sys.indexes 做差集——这是唯一可靠、全版本兼容(包括 SQL Server 2022)的方法。
为什么不能只看 sys.dm_db_index_usage_stats?
这个 DMV 只记录「自上次 SQL Server 重启 / 数据库脱机 / 统计被重置后」被实际用过的索引。它不存“未使用”记录,而是压根不出现——所以你得反向推:所有在 sys.indexes 里存在、却没出现在 sys.dm_db_index_usage_stats 中的索引,才可能是未使用的。
常见误操作是直接 WHERE user_seeks = 0 AND user_scans = 0 AND user_lookups = 0,这会漏掉那些根本没进过该 DMV 的索引(比如刚建完还没被任何查询碰过),也包含大量只用于维护操作(如 UPDATE STATISTICS)的假阴性条目。
怎么写安全可用的未使用索引查询?
核心逻辑是:从 sys.indexes 中排除掉所有在 sys.dm_db_index_usage_stats 中出现过的 [object_id] + index_id 组合。注意三点:
-
sys.dm_db_index_usage_stats是实例级视图,必须显式过滤database_id = DB_ID(),否则可能混入其他库的统计 - 要排除系统对象(
sys.objects.type NOT IN ('S', 'IT'))和堆(sys.indexes.type = 0) - 主键约束对应的索引(
is_primary_key = 1)不能直接删,得转成ALTER TABLE ... DROP CONSTRAINT语句
示例片段(适用于当前数据库):
DECLARE @dbid INT = DB_ID();
WITH unused AS (
SELECT i.[object_id], i.index_id
FROM sys.indexes i
WHERE i.object_id > 100 AND i.type > 0
EXCEPT
SELECT [object_id], index_id
FROM sys.dm_db_index_usage_stats
WHERE database_id = @dbid
)
SELECT
OBJECT_NAME(i.[object_id]) AS table_name,
i.name AS index_name,
CASE WHEN i.is_primary_key = 1
THEN 'ALTER TABLE ' + QUOTENAME(OBJECT_NAME(i.[object_id])) + ' DROP CONSTRAINT ' + QUOTENAME(i.name)
ELSE 'DROP INDEX ' + QUOTENAME(i.name) + ' ON ' + QUOTENAME(OBJECT_NAME(i.[object_id]))
END AS drop_statement
FROM sys.indexes i
INNER JOIN unused u ON i.[object_id] = u.[object_id] AND i.index_id = u.index_id
INNER JOIN sys.objects o ON i.[object_id] = o.[object_id]
WHERE o.type IN ('U', 'V');
执行前必须人工核验的四个关键点
SQL Server 2022 不会替你判断业务逻辑,以下四类索引即使“未使用”,也不能直接删:
- 外键列上的索引——哪怕没被查询用过,
INSERT/UPDATE时仍需检查引用完整性,删了会触发阻塞或失败 - 被
ORDER BY或GROUP BY隐式利用的索引(例如覆盖排序需求但没走user_seeks) - 报表类或低频批处理作业专用索引——它们可能每天只跑一次,而你的统计窗口只有几小时
- 刚上线的新表或新索引——SQL Server 2022 默认不自动清空统计,但如果你近期重启过实例,统计期就极短
监控周期建议设为至少 4 周
不是因为 SQL Server 2022 有延迟,而是业务本身有节奏:周报、月结、季度审计这些操作往往集中在固定时间点。只看 3 天或 1 周的 sys.dm_db_index_usage_stats,大概率把真正有用的索引标成“未使用”。另外,别依赖单次快照——建议每周跑一次上述查询,连续比对四周结果,只对始终未出现的索引动刀。











