2009-09-21 1 views
9

SQL 데이터베이스에 대한 인덱스 사용을 모니터링하여 사용하지 않는 인덱스를 찾은 다음 드롭합니다. 인덱스 사용을 가장 효율적으로 모니터링하려면 어떻게합니까? 그리고 어떤 스크립트가 유용할까요?SQL 데이터베이스에서 사용되지 않는 인덱스를 모니터하고 찾으려면 어떻게해야합니까?

는 (나는 this question about identifying unused objects 알고 있어요, 그러나 이것은 SQL 서버의 현재 실행에만 적용됩니다. 내가 일정 기간 동안 인덱스 사용을 모니터링하고 싶습니다 ...) 현재

답변

7

(SQL의로 Server 2005 - 2008) SQL 인덱스 통계 정보는 메모리에만 저장되므로 재시작 및 데이터베이스 분리시에도 지속되도록하려는 경우 작업을 직접 수행해야합니다.

보통 나는 매일 실행되는 작업을 만들고 sys.dm_db_index_usage_stats 테이블에있는 정보의 스냅 샷을 해당 데이터베이스에 대해 만드는 사용자 지정 테이블로 가져옵니다.

영구 인덱스 사용 통계를 지원하는 SQL의 차후 버전까지는 꽤 잘 작동하는 것 같습니다.

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 
7

이것은 흥미로운 질문입니다. 나는 지난 주 동안이 같은 질문에 대해 노력해 왔습니다. 인덱스에 대한 사용 통계를 포함하는 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 @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 

이 정보가 도움이되기를 바랍니다. 인덱스가 후보자 제거로 확인되면 물론

최종 생각

은, 신중하게 고려해야은 여전히 ​​그렇게 할 수있는 좋은 결정입니다 있는지 확인하기 위해 사용되어야한다.자세한 내용은

참조 Identifying Unused Indexes in a SQL Server Database

3

내가 여기 존 Pasquet의 쿼리를 쥐게 : Identifying Unused Indexes in a SQL Server Database은, 인덱스가 10 이하 시간을 사용 반환 테이블, 힙 인덱스를 제외 페이지의 통계 사용에없는 결과를 UNION을하고 고유 제한 조건 또는 1 차 키 색인, 마지막으로 제로 페이지 색인을 제외시킵니다.

이 쿼리의 결과에주의하십시오. 인덱스가 실제로 예상대로 사용되는 프로덕션 환경에서 사용하는 것이 가장 좋습니다. 데이터베이스를 다시 작성하거나 삭제/다시 작성한 인덱스로 쿼리하거나 최근 데이터베이스 백업에서 오탐 (인덱스가 정상적으로 사용되지만 특수한 상황으로 인해 생성되지는 않음)이 발생할 수 있습니다. 테스트 환경이나 개발 환경에서 인덱스 삭제 여부를 결정하는 것은 안전하지 않습니다. Narnian이 말했듯이이 쿼리는 신중하게 고려해야 할 후보를 식별합니다.

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 
0

당신은 브렌트 Ozars sp_BlitzIndex 살펴 보셔야합니다. 이 저장 프로시 저는 사용되지 않은 색인을 나열합니다. 그것은 보고서의 장애를 나열합니다. 각 항목에 대해 찾아야 할 항목과 문제를 처리하는 방법을 설명하는 URL이 제공됩니다.