T-SQL для поиска избыточных индексов - PullRequest
6 голосов
/ 19 июля 2010

Кто-нибудь знает о сценарии T-SQL, который может обнаруживать избыточные индексы по всей базе данных?Пример избыточного индекса в таблице может быть следующим:

Index 1: 'ColumnA', 'ColumnB', 'ColumnC'
Index 2: 'ColumnA', 'ColumnB'

Игнорируя другие соображения, такие как ширина столбцов и покрывающие индексы, индекс 2 будет избыточным.

Спасибо.

Ответы [ 5 ]

11 голосов
/ 19 июля 2010

Бывают ситуации, когда избыточность не сохраняется. Например, скажем, ColumnC было огромным полем, но иногда приходилось быстро его извлекать. Для вашего index 1 не потребуется поиск ключа для:

select ColumnC from YourTable where ColumnnA = 12

С другой стороны, index 2 намного меньше, поэтому его можно прочитать в памяти для запросов, требующих сканирования индекса:

select * from YourTable where ColumnnA like '%hello%'

Так что они на самом деле не избыточны.

Если вы не убеждены моим приведенным выше аргументом, вы можете найти «избыточные» индексы, такие как:

;with ind as (
    select  a.object_id
    ,       a.index_id
    ,       cast(col_list.list as varchar(max)) as list
    from    (
            select  distinct object_id
            ,       index_id
            from    sys.index_columns
            ) a
    cross apply
            (
            select  cast(column_id as varchar(16)) + ',' as [text()]
            from    sys.index_columns b
            where   a.object_id = b.object_id
                    and a.index_id = b.index_id
            for xml path(''), type
            ) col_list (list)
)
select  object_name(a.object_id) as TableName
,       asi.name as FatherIndex
,       bsi.name as RedundantIndex
from    ind a
join    sys.sysindexes asi
on      asi.id = a.object_id
        and asi.indid = a.index_id
join    ind b
on      a.object_id = b.object_id
        and a.object_id = b.object_id
        and len(a.list) > len(b.list)
        and left(a.list, LEN(b.list)) = b.list
join    sys.sysindexes bsi
on      bsi.id = b.object_id
        and bsi.indid = b.index_id

Принесите торт пользователям, если производительность неожиданно снизится: -)

1 голос
/ 23 июня 2016

Попробуйте скрипт ниже, чтобы показать неиспользуемые индексы, надеюсь, это поможет

/****************************************************************
Description: Script to show Unused Indexes using DMVs
****************************************************************/

SELECT TOP 100
o.name AS ObjectName
, i.name AS IndexName
, i.index_id AS IndexID  
, dm_ius.user_seeks AS UserSeek
, dm_ius.user_scans AS UserScans
, dm_ius.user_lookups AS UserLookups
, dm_ius.user_updates AS UserUpdates
, p.TableRows
, 'DROP INDEX ' + QUOTENAME(i.name) 
+ ' ON ' + QUOTENAME(s.name) + '.' + QUOTENAME(OBJECT_NAME(dm_ius.object_id)) as 'drop statement'
FROM sys.dm_db_index_usage_stats dm_ius  
INNER JOIN sys.indexes i ON i.index_id = dm_ius.index_id AND dm_ius.object_id = i.object_id   
INNER JOIN sys.objects o on dm_ius.object_id = o.object_id
INNER JOIN sys.schemas s on o.schema_id = s.schema_id
INNER JOIN (SELECT SUM(p.rows) TableRows, p.index_id, p.object_id 
                FROM sys.partitions p GROUP BY p.index_id, p.object_id) p 
        ON p.index_id = dm_ius.index_id AND dm_ius.object_id = p.object_id
WHERE OBJECTPROPERTY(dm_ius.object_id,'IsUserTable') = 1
AND dm_ius.database_id = DB_ID()   
AND i.type_desc = 'nonclustered'
AND i.is_primary_key = 0
AND i.is_unique_constraint = 0
ORDER BY (dm_ius.user_seeks + dm_ius.user_scans + dm_ius.user_lookups) ASC
GO
1 голос
/ 17 апреля 2013

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

WITH IndexSummary AS
(

SELECT DISTINCT sys.objects.name AS [Table Name],
    sys.indexes.name AS [Index Name],
    SUBSTRING((SELECT ', ' +  sys.columns.Name as [text()]
        FROM sys.columns
            INNER JOIN sys.index_columns
                ON sys.index_columns.column_id = sys.columns.column_id
                AND sys.index_columns.object_id = sys.columns.object_id
        WHERE sys.index_columns.index_id = sys.indexes.index_id
            AND sys.index_columns.object_id = sys.indexes.object_id
            AND sys.index_columns.is_included_column = 0
        ORDER BY sys.columns.name
    FOR XML Path('')), 2, 10000) AS [Indexed Column Names],
    ISNULL(SUBSTRING((SELECT ', ' +  sys.columns.Name as [text()]
        FROM sys.columns
            INNER JOIN sys.index_columns
            ON sys.index_columns.column_id = sys.columns.column_id
            AND sys.index_columns.object_id = sys.columns.object_id
        WHERE sys.index_columns.index_id = sys.indexes.index_id
            AND sys.index_columns.object_id = sys.indexes.object_id
            AND sys.index_columns.is_included_column = 1
        ORDER BY sys.columns.name
        FOR XML Path('')), 2, 10000), '') AS [Included Column Names],
    sys.indexes.index_id, sys.indexes.object_id
FROM sys.indexes
    INNER JOIN SYS.index_columns
        ON sys.indexes.index_id = SYS.index_columns.index_id
            AND sys.indexes.object_id = sys.index_columns.object_id
    INNER JOIN sys.objects
        ON sys.OBJECTS.object_id = SYS.indexES.object_id
WHERE sys.objects.type = 'U'
)

SELECT IndexSummary.[Table Name],
    IndexSummary.[Index Name],
    IndexSummary.[Indexed Column Names],
    IndexSummary.[Included Column Names],
    PhysicalStats.page_count as [Page Count],
    CONVERT(decimal(18,2), PhysicalStats.page_count * 8 / 1024.0) AS [Size (MB)],
    CONVERT(decimal(18,2), PhysicalStats.avg_fragmentation_in_percent) AS [Fragment %]
FROM IndexSummary
    INNER JOIN sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL, NULL, NULL)
       AS PhysicalStats
        ON PhysicalStats.index_id = IndexSummary.index_id
            AND PhysicalStats.object_id = IndexSummary.object_id
WHERE (SELECT COUNT(*) as Computed
        FROM IndexSummary Summary2
        WHERE Summary2.[Table Name] = IndexSummary.[Table Name]
            AND Summary2.[Indexed Column Names] = IndexSummary.[Indexed Column Names]) > 1
ORDER BY [Table Name], [Index Name], [Indexed Column Names], [Included Column Names]

Результаты запроса выглядят так:

Table Name  Index   Indexed Cols    Included Cols   Pages   Size (MB)   Frag %
My_Table    Indx_1     Col1         Col2, Col3       123      0.96       8.94
My_Table    Indx_2     Col1         Col2, Col3       123      0.96       8.94

Полное описание

Полное объяснение см. Определение дублирующих или избыточных индексов в SQL Server .

1 голос
/ 20 марта 2012

Вдохновленный Полом Нильсеном , я написал этот запрос, чтобы найти / отличить:

  • Дубликаты (без учета порядка включения)
  • Избыточный (разные столбцы)
  • Перекрытие (разные столбцы индекса)

А также записать их использование (Можно также использовать is_descending_key, но мне это не нужно.)

WITH IndexColumns AS
(
    SELECT I.object_id AS TableObjectId, OBJECT_SCHEMA_NAME(I.object_id) + '.' + OBJECT_NAME(I.object_id) AS TableName, I.index_id AS IndexId, I.name AS IndexName
        , (IndexUsage.user_seeks + IndexUsage.user_scans + IndexUsage.user_lookups) AS IndexUsage
        , IndexUsage.user_updates AS IndexUpdates

       , (SELECT CASE is_included_column WHEN 1 THEN NULL ELSE column_id END AS [data()]
        FROM sys.index_columns AS IndexColumns
        WHERE IndexColumns.object_id = I.object_id
          AND IndexColumns.index_id = I.index_id
        ORDER BY index_column_id, column_id
        FOR XML PATH('')
       ) AS ConcIndexColumnNrs

       ,(SELECT CASE is_included_column WHEN 1 THEN NULL ELSE COL_NAME(I.object_id, column_id) END AS [data()]
        FROM sys.index_columns AS IndexColumns
        WHERE IndexColumns.object_id = I.object_id
          AND IndexColumns.index_id = I.index_id
        ORDER BY index_column_id, column_id
        FOR XML PATH('')
       ) AS ConcIndexColumnNames

       ,(SELECT CASE is_included_column WHEN 1 THEN column_id ELSE NULL END AS [data()]
        FROM sys.index_columns AS IndexColumns
        WHERE IndexColumns.object_id = I.object_id
        AND IndexColumns.index_id = I.index_id
        ORDER BY column_id
        FOR XML PATH('')
       ) AS ConcIncludeColumnNrs

       ,(SELECT CASE is_included_column WHEN 1 THEN COL_NAME(I.object_id, column_id) ELSE NULL END AS [data()]
        FROM sys.index_columns AS IndexColumns
        WHERE IndexColumns.object_id = I.object_id
          AND IndexColumns.index_id = I.index_id
        ORDER BY column_id
        FOR XML PATH('')
       ) AS ConcIncludeColumnNames
    FROM sys.indexes AS I
       LEFT OUTER JOIN sys.dm_db_index_usage_stats AS IndexUsage
        ON IndexUsage.object_id = I.object_id
          AND IndexUsage.index_id = I.index_id
          AND IndexUsage.Database_id = db_id() 
)
SELECT
  C1.TableName
  , C1.IndexName AS 'Index1'
  , C2.IndexName AS 'Index2'
  , CASE WHEN (C1.ConcIndexColumnNrs = C2.ConcIndexColumnNrs) AND (C1.ConcIncludeColumnNrs = C2.ConcIncludeColumnNrs) THEN 'Exact duplicate'
        WHEN (C1.ConcIndexColumnNrs = C2.ConcIndexColumnNrs) THEN 'Different includes'
        ELSE 'Overlapping columns' END
--  , C1.ConcIndexColumnNrs
--  , C2.ConcIndexColumnNrs
  , C1.ConcIndexColumnNames
  , C2.ConcIndexColumnNames
--  , C1.ConcIncludeColumnNrs
--  , C2.ConcIncludeColumnNrs
  , C1.ConcIncludeColumnNames
  , C2.ConcIncludeColumnNames
  , C1.IndexUsage
  , C2.IndexUsage
  , C1.IndexUpdates
  , C2.IndexUpdates
  , 'DROP INDEX ' + C2.IndexName + ' ON ' + C2.TableName AS Drop2
  , 'DROP INDEX ' + C1.IndexName + ' ON ' + C1.TableName AS Drop1
FROM IndexColumns AS C1
  INNER JOIN IndexColumns AS C2 
    ON (C1.TableObjectId = C2.TableObjectId)
    AND (
         -- exact: show lower IndexId as 1
            (C1.IndexId < C2.IndexId
            AND C1.ConcIndexColumnNrs = C2.ConcIndexColumnNrs
            AND C1.ConcIncludeColumnNrs = C2.ConcIncludeColumnNrs)
         -- different includes: show longer include as 1
         OR (C1.ConcIndexColumnNrs = C2.ConcIndexColumnNrs
            AND LEN(C1.ConcIncludeColumnNrs) > LEN(C2.ConcIncludeColumnNrs))
         -- overlapping: show longer index as 1
         OR (C1.IndexId <> C2.IndexId
            AND C1.ConcIndexColumnNrs <> C2.ConcIndexColumnNrs
            AND C1.ConcIndexColumnNrs like C2.ConcIndexColumnNrs + ' %')
    )
ORDER BY C1.TableName, C1.ConcIndexColumnNrs
0 голосов
/ 17 января 2011

Я просто читал некоторые блоги MSDN, заметил скрипт для этого и запомнил этот вопрос.

Я не удосужился протестировать его рядом с Andomar'ом, чтобы увидеть, имеет ли одно какое-то конкретное преимущество перед другим.

Одна поправка, которую я, вероятно, внесу в оба варианта, будет учитывать размер обоих индексов при оценке избыточности.

Edit:

Также см. Сообщение Кимберли Триппа о Удаление дублирующих индексов

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...