Как отслеживать и находить неиспользуемые индексы в базе данных sql

Я хотел бы отслеживать использование индекса для базы данных sql, чтобы найти неиспользуемые индексы, а затем удалить их. Как я могу лучше контролировать использование индекса? И какие скрипты могут быть полезны?

(я знаю этот вопрос об идентификации неиспользуемых объектов, но это относится только к текущему запуску SQL-сервера. Я хотел бы отслеживать использование индекса в течение определенного периода времени...)

Ответ 1

В настоящее время (с SQL Server 2005 - 2008) информация о статистике индекса SQL хранится только в памяти, поэтому вам нужно выполнить часть работы самостоятельно, если вы хотите, чтобы она сохранялась в перезагрузках и сбоях базы данных.

Что я обычно делаю, я создаю задание, которое выполняется каждый день и принимает моментальный снимок информации, найденной в таблице sys.dm_db_index_usage_stats, в пользовательскую таблицу, которую я создаю для рассматриваемой базы данных.

Это, похоже, работает очень хорошо до будущей версии SQL, которая будет поддерживать статистику постоянных индексов.

Ответ 2

Это интересный вопрос. Я занимаюсь этим вопросом в течение прошлой недели. Существует системная таблица с именем dm_db_index_usage_stats, которая содержит статистику использования индексов.

Индексы, которые никогда не отображаются в таблице статистики использования

Однако многие индексы никогда не появляются в этой таблице вообще. В запросе Дэвида Андреса были перечислены все индексы для этого случая. Я немного обновил его, чтобы игнорировать первичные ключи, которые, вероятно, не следует удалять, даже если они никогда не используются. Я также присоединился к таблице 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 @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

Надеюсь, это поможет.

Заключительная мысль

Конечно, как только индексы идентифицируются как кандидаты на удаление, необходимо тщательно изучить их, чтобы убедиться, что это хорошее решение для этого.

Для получения дополнительной информации см. Идентификация неиспользуемых индексов в базе данных SQL Server

Ответ 3

Вытащил щенка из http://blog.sqlauthority.com/2008/02/11/sql-server-2005-find-unused-indexes-of-current-database/. Обратите внимание, что это работает в 2005 году и выше. Ключ - это JOIN к системной таблице SYS.DM_DB_INDEX_USAGE_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

Ответ 4

Я изменил запросы Джона Паске: Идентификация неиспользуемых индексов в базе данных SQL Server, чтобы возвращать индексы, используемые 10 или менее раз, объединили результаты, которые не входят в таблицы статистики использования, исключают индексы кучи и уникальные ограничения или индексы первичных ключей и, наконец, исключают индексы с нулевыми страницами.

Будьте осторожны с результатами этого запроса - лучше всего использовать в производстве, где индексы фактически используются так, как вы ожидали. Если вы запрашиваете базу данных с перестроенными или упакованными/воссозданными индексами или в недавней резервной копии базы данных, вы можете получить ложные срабатывания (индексы, которые обычно будут использоваться, но не из-за особых обстоятельств). Небезопасно использовать в тестовых или dev средах, чтобы решить, следует ли отказывать индексы. Как говорит Нарнян, этот запрос просто идентифицирует кандидатов на удаление для вашего тщательного рассмотрения.

USE [DatabaseName]

DECLARE @MinimumPageCount int
SET @MinimumPageCount = 500

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

-- GET UNUSED INDEXES THAT APPEAR IN THE INDEX USAGE STATS TABLE

SELECT  
    Databases.name AS [Database]
    ,object_name(Indexes.object_id) AS [Table]
    ,Indexes.name AS [Index]
    ,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 Per Row (Bytes)]
    ,1 AS [Appears In Usage Stats Table]

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.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 <= 10
    AND UsageStats.user_seeks <= 10
    AND UsageStats.user_lookups <= 10

    -- exclude heap indexes
    AND Indexes.name IS NOT NULL

    -- ignore indexes with less than a certain number of pages of memory
    AND PhysicalStats.page_count > @MinimumPageCount

    -- Exclude primary keys, which should not be removed
    AND Indexes.is_primary_key = 0

    -- ignore unique constraints - those shouldn't be removed 
    AND Indexes.is_unique_constraint = 0 
    AND Indexes.is_unique = 0

UNION ALL 
(
    -- GET UNUSED INDEXES THAT DO **NOT** APPEAR IN THE INDEX USAGE STATS TABLE

    SELECT  
        Databases.Name AS [Database]
        ,Objects.NAME AS [Table]
        ,Indexes.NAME AS [Index]
        ,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 (%)]
        ,-1 AS [Row Count]
        ,-1 AS [Index Size Per Row (Bytes)]
        ,0 AS [Appears In Usage Stats Table]

    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 
        Objects.type = 'U' -- Is User Table

        -- exclude heap indexes
        AND Indexes.name IS NOT NULL

        -- exclude empty tables
        AND PhysicalStats.page_count <> 0

        -- Exclude primary keys, which should not be removed
        AND Indexes.is_primary_key = 0

        -- ignore unique constraints - those shouldn't be removed 
        AND Indexes.is_unique_constraint = 0 
        AND Indexes.is_unique = 0

        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 [Table] ASC, [Total Index Size (MB)] DESC

Ответ 5

Вы должны взглянуть на Brent Ozars sp_BlitzIndex. Эта хранимая процедура содержит среди прочего неиспользуемые индексы. В нем перечислены нарушения в отчете. Для каждой записи указывается URL-адрес, который объясняет, что искать и как справиться с проблемой.