Sql server 如何在sql数据库中监视和查找未使用的索引

Sql server 如何在sql数据库中监视和查找未使用的索引,sql-server,indexing,maintenance,Sql Server,Indexing,Maintenance,我想监控sql数据库的索引使用情况,以便找到未使用的索引,然后删除它们。如何最有效地监控索引使用情况?哪些脚本可能有用 (我知道,但这仅适用于sql server的当前运行。我希望在一段时间内监视索引使用情况…)当前(从sql server 2005-2008开始)SQL索引统计信息只保存在内存中,因此如果希望在重新启动和数据库分离期间保持这些信息,则必须自己完成一些工作 我通常做的是创建一个每天运行的作业,并将sys.dm\u db\u index\u usage\u stats表中的信息截取

我想监控sql数据库的索引使用情况,以便找到未使用的索引,然后删除它们。如何最有效地监控索引使用情况?哪些脚本可能有用

(我知道,但这仅适用于sql server的当前运行。我希望在一段时间内监视索引使用情况…)

当前(从sql server 2005-2008开始)SQL索引统计信息只保存在内存中,因此如果希望在重新启动和数据库分离期间保持这些信息,则必须自己完成一些工作

我通常做的是创建一个每天运行的作业,并将
sys.dm\u db\u index\u usage\u stats
表中的信息截取到我为相关数据库创建的自定义表中

在SQL的未来版本支持持久索引使用统计数据之前,这似乎工作得很好。

将这只小狗从数据库中删除。请注意,这适用于2005年及以上。关键是
JOIN
SYS.DM\u DB\u INDEX\u USAGE\u STATS
系统表

USE AdventureWorks
GO
DECLARE @dbid INT
SELECT @dbid = DB_ID(DB_NAME())
SELECT OBJECTNAME = OBJECT_NAME(I.OBJECT_ID),
                    INDEXNAME = I.NAME,
                    I.INDEX_ID
FROM SYS.INDEXES I
JOIN SYS.OBJECTS O ON I.OBJECT_ID = O.OBJECT_ID
WHERE OBJECTPROPERTY(O.OBJECT_ID,'IsUserTable') = 1
AND   I.INDEX_ID NOT IN (

SELECT S.INDEX_ID
FROM SYS.DM_DB_INDEX_USAGE_STATS S
WHERE S.OBJECT_ID = I.OBJECT_ID
AND   I.INDEX_ID = S.INDEX_ID
AND   DATABASE_ID = @dbid)
ORDER BY OBJECTNAME,
         I.INDEX_ID,
         INDEXNAME ASC
GO

这是一个有趣的问题。在过去的一周里,我一直在研究这个问题。有一个名为dm_db_index_usage_stats的系统表,其中包含索引的使用统计信息

从未出现在使用情况统计表中的索引

但是,许多索引根本没有出现在这个表中。David Andres发布的查询列出了该案例的所有索引。我对它做了一点更新,忽略了主键,主键可能不应该被删除,即使它们从未被使用过。我还加入了dm_db_index_physical_stats表,以获取其他信息,包括页面计数、总索引大小和碎片百分比。一个有趣的注意事项是,此查询返回的索引似乎没有显示在用于索引使用统计的SQL报告中

DECLARE @dbid INT
SELECT @dbid = DB_ID(DB_NAME())

SELECT  Databases.Name AS [Database],
        Objects.NAME AS [Table],
        Indexes.NAME AS [Index],
        Indexes.INDEX_ID,
        PhysicalStats.page_count as [Page Count],
        CONVERT(decimal(18,2), PhysicalStats.page_count * 8 / 1024.0) AS [Total Index Size (MB)],
        CONVERT(decimal(18,2), PhysicalStats.avg_fragmentation_in_percent) AS [Fragmentation (%)]
FROM SYS.INDEXES Indexes
    INNER JOIN SYS.OBJECTS Objects ON Indexes.OBJECT_ID = Objects.OBJECT_ID
    LEFT JOIN sys.dm_db_index_physical_stats(@dbid, null, null, null, null) PhysicalStats
        on PhysicalStats.object_id = Indexes.object_id and PhysicalStats.index_id = indexes.index_id
    INNER JOIN sys.databases Databases
        ON Databases.database_id = PhysicalStats.database_id
WHERE OBJECTPROPERTY(Objects.OBJECT_ID,'IsUserTable') = 1
    AND Indexes.type = 2    -- Nonclustered indexes
    AND   Indexes.INDEX_ID NOT IN (
            SELECT UsageStats.INDEX_ID
            FROM SYS.DM_DB_INDEX_USAGE_STATS UsageStats
            WHERE UsageStats.OBJECT_ID = Indexes.OBJECT_ID
                AND   Indexes.INDEX_ID = UsageStats.INDEX_ID
                AND   DATABASE_ID = @dbid)
ORDER BY PhysicalStats.page_count DESC,
         Objects.NAME,
         Indexes.INDEX_ID,
         Indexes.NAME ASC
确实出现在使用情况统计表中但从未使用过的索引

dm_db_index_usage_stats表中确实出现了其他索引,但从未用于用户搜索、扫描或查找。此查询将标识属于此类别的索引。顺便说一句,与从另一个查询返回的索引不同,此查询中返回的索引可以通过索引使用统计信息在SQL报表上进行验证

DECLARE @dbid INT
SELECT @dbid = DB_ID(DB_NAME())

SELECT  Databases.Name AS [Database],
        Objects.NAME AS [Table],
        Indexes.NAME AS [Index],
        Indexes.INDEX_ID,
        PhysicalStats.page_count as [Page Count],
        CONVERT(decimal(18,2), PhysicalStats.page_count * 8 / 1024.0) AS [Total Index Size (MB)],
        CONVERT(decimal(18,2), PhysicalStats.avg_fragmentation_in_percent) AS [Fragmentation (%)]
FROM SYS.INDEXES Indexes
    INNER JOIN SYS.OBJECTS Objects ON Indexes.OBJECT_ID = Objects.OBJECT_ID
    LEFT JOIN sys.dm_db_index_physical_stats(@dbid, null, null, null, null) PhysicalStats
        on PhysicalStats.object_id = Indexes.object_id and PhysicalStats.index_id = indexes.index_id
    INNER JOIN sys.databases Databases
        ON Databases.database_id = PhysicalStats.database_id
WHERE OBJECTPROPERTY(Objects.OBJECT_ID,'IsUserTable') = 1
    AND Indexes.type = 2    -- Nonclustered indexes
    AND   Indexes.INDEX_ID NOT IN (
            SELECT UsageStats.INDEX_ID
            FROM SYS.DM_DB_INDEX_USAGE_STATS UsageStats
            WHERE UsageStats.OBJECT_ID = Indexes.OBJECT_ID
                AND   Indexes.INDEX_ID = UsageStats.INDEX_ID
                AND   DATABASE_ID = @dbid)
ORDER BY PhysicalStats.page_count DESC,
         Objects.NAME,
         Indexes.INDEX_ID,
         Indexes.NAME ASC
我添加了一个最小页数,允许我最初关注并删除占用大量存储空间的未使用索引

DECLARE @MinimumPageCount int
SET @MinimumPageCount = 500

SELECT  Databases.name AS [Database], 
        Indexes.name AS [Index],
        Objects.Name AS [Table],                    
        PhysicalStats.page_count as [Page Count],
        CONVERT(decimal(18,2), PhysicalStats.page_count * 8 / 1024.0) AS [Total Index Size (MB)],
        CONVERT(decimal(18,2), PhysicalStats.avg_fragmentation_in_percent) AS [Fragmentation (%)],
        ParititionStats.row_count AS [Row Count],
        CONVERT(decimal(18,2), (PhysicalStats.page_count * 8.0 * 1024) / ParititionStats.row_count) AS [Index Size/Row (Bytes)]
FROM sys.dm_db_index_usage_stats UsageStats
    INNER JOIN sys.indexes Indexes
        ON Indexes.index_id = UsageStats.index_id
            AND Indexes.object_id = UsageStats.object_id
    INNER JOIN sys.objects Objects
        ON Objects.object_id = UsageStats.object_id
    INNER JOIN SYS.databases Databases
        ON Databases.database_id = UsageStats.database_id       
    INNER JOIN sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL, NULL, NULL) AS PhysicalStats
        ON PhysicalStats.index_id = UsageStats.Index_id 
            and PhysicalStats.object_id = UsageStats.object_id
    INNER JOIN SYS.dm_db_partition_stats ParititionStats
        ON ParititionStats.index_id = UsageStats.index_id
            and ParititionStats.object_id = UsageStats.object_id        
WHERE UsageStats.user_scans = 0
    AND UsageStats.user_seeks = 0
    AND UsageStats.user_lookups = 0
    AND PhysicalStats.page_count > @MinimumPageCount    -- ignore indexes with less than 500 pages of memory
    AND Indexes.type_desc != 'CLUSTERED'                -- Exclude primary keys, which should not be removed    
ORDER BY [Page Count] DESC
我希望这有帮助

最后的想法

当然,一旦索引被确定为要删除的候选索引,仍然应该仔细考虑,以确保这样做是一个好的决定


有关更多信息,请参见

我在此处调整了John Pasquet的查询:返回使用次数不超过10次的索引,联合不在usage stats表中的结果,排除堆索引和唯一约束或主键索引,最后排除页面为零的索引

请注意此查询的结果–最好在生产环境中使用,因为实际使用索引的方式与预期相同。如果查询具有重建或删除/重新创建索引的数据库,或者查询最近的数据库备份,则可能会得到误报(通常会使用但不是因为特殊情况而使用的索引)。在测试或开发环境中使用以决定是否删除索引是不安全的。正如Narnian所说,这个查询只是确定了要删除的候选项,供您仔细考虑

使用[DatabaseName]
声明@MinimumPageCount int
设置@MinimumPageCount=500
声明@dbid INT
选择@dbid=DB\u ID(DB\u NAME())
--获取索引使用情况统计表中显示的未使用索引
挑选
Databases.name为[数据库]
,对象名称(index.object\u id)为[表]
,Index.name为[Index]
,PhysicalStats.page_计数为[页数]
,将(十进制(18,2),PhysicalStats.page_count*8/1024.0)转换为[总索引大小(MB)]
,将(十进制(18,2),PhysicalStats.avg_fragmentation_(单位:百分比))转换为[fragmentation(%)]
,ParititionStats.row_count AS[行计数]
,将(十进制(18,2),(PhysicalStats.page_count*8.0*1024)/ParititionStats.row_count)转换为[每行索引大小(字节)]
,1为[出现在使用情况统计表中]
从sys.dm\u db\u index\u usage\u stats UsageStats
内部联接系统索引
在index.index\u id=UsageStats.index\u id和index.object\u id=UsageStats.object\u id上
内部联接SYS.databases数据库
在Databases.database\u id=UsageStats.database\u id上
内部连接sys.dm_db_index_physical_stats(db_ID(),NULL,NULL,NULL,NULL)作为PhysicalStats
在PhysicalStats.index\u id=UsageStats.index\u id和PhysicalStats.object\u id=UsageStats.object\u id上
内部联接SYS.dm_db_partition_stats ParititionStats
在PARTITIONSTATS.index\U id=UsageStats.index\U id和PARTITITIONSTATS.object\U id=UsageStats.object\U id上
哪里

UsageStats.user_扫描你应该看看Brent Ozars。此存储过程列出了其他未启用的索引。它在一份报告中列出了这些疾病。对于每个条目,都会给出一个URL,其中解释了要查找的内容以及如何处理问题