2009-09-21 11 views
9

Je souhaite surveiller l'utilisation d'index pour une base de données SQL, afin de trouver des index inutilisés, puis de les supprimer. Comment puis-je surveiller l'utilisation de l'index le plus efficacement? Et quels scripts pourraient être utiles?Comment surveiller et trouver des index inutilisés dans la base de données SQL

(je suis au courant de this question about identifying unused objects, mais cela ne vaut que pour l'exécution en cours du serveur SQL. Je voudrais surveiller l'utilisation de l'indice sur une période de temps ...)

Répondre

7

Actuellement (SQL Server 2005 - 2008) les informations sur les statistiques de l'index SQL ne sont conservées qu'en mémoire et vous devez donc effectuer une partie du travail vous-même si vous souhaitez que cela persiste dans les redémarrages et les détachages de base de données. En règle générale, je crée un travail qui s'exécute tous les jours et prend un instantané des informations contenues dans la table sys.dm_db_index_usage_stats, dans une table personnalisée que je crée pour la base de données en question.

Cela semble fonctionner plutôt bien jusqu'à une future version de SQL qui prendra en charge les statistiques d'utilisation d'index persistantes.

3

Tiré ce chiot de http://blog.sqlauthority.com/2008/02/11/sql-server-2005-find-unused-indexes-of-current-database/. Notez que cela fonctionne pour 2005 et au-dessus. La clé est la JOIN de la table système 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

Ceci est une question intéressante. J'ai travaillé sur cette même question au cours de la dernière semaine. Il existe une table système appelée dm_db_index_usage_stats qui contient des statistiques d'utilisation sur les index.

index Jamais apparaissons dans les statistiques d'utilisation Tableau

Cependant, de nombreux indices apparaissent jamais dans ce tableau du tout. La requête affichée par David Andres répertorie tous les index pour ce cas. Je l'ai mis à jour un peu pour ignorer les clés primaires, qui ne devraient probablement pas être supprimées, même si elles ne sont jamais utilisées. J'ai également joint la table dm_db_index_physical_stats pour obtenir d'autres informations, notamment le nombre de pages, la taille de l'index total et le pourcentage de fragmentation. Une remarque intéressante est que les index renvoyés par cette requête ne semblent pas apparaître dans le rapport SQL pour les statistiques d'utilisation de l'index.

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 

index apparaissent dans les statistiques de __gVirt_NP_NN_NNPS<__ Utilisation table, mais ne sont jamais utilisés

Il existe d'autres indices qui apparaissent dans la table dm_db_index_usage_stats, mais qui ont jamais été utilisés pour cherche utilisateur, scans , ou des recherches. Cette requête identifiera les index qui entrent dans cette catégorie. Incidemment, contrairement aux index renvoyés par l'autre requête, les index renvoyés dans cette requête peuvent être vérifiés sur le rapport SQL par les statistiques d'utilisation de l'index.

J'ai ajouté un nombre minimal de pages qui me permet de me concentrer sur les index inutilisés qui occupent beaucoup de mémoire.

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 

J'espère que cela aide.

Pensée finale

Bien sûr, une fois que les index sont identifiés comme candidats pour le retrait, une attention particulière doit être employée encore pour vous assurer qu'il est une bonne décision de le faire.

Pour plus d'informations, consultez Identifying Unused Indexes in a SQL Server Database

3

J'ai modifié les requêtes de John Pasquet ici: Identifying Unused Indexes in a SQL Server Database pour retourner les index utilisés 10 fois ou moins, filles fusionnées les résultats qui ne sont pas dans l'utilisation de STATS tables, ne comprennent pas les index de tas et des contraintes uniques ou des index de clé primaire, et enfin d'exclure des index avec zéro page. Soyez prudent avec les résultats de cette requête - il est préférable d'utiliser dans la production où les index sont réellement utilisés comme vous l'attendez. Si vous interrogez sur une base de données avec des index reconstruits ou supprimés/recréés ou sur une sauvegarde de base de données récente, vous pouvez obtenir des faux positifs (index qui normalement seraient utilisés mais ne le sont pas en raison de circonstances particulières). Pas sûr à utiliser dans les environnements de test ou de développement pour décider de supprimer ou non les index. Comme le dit Narnian, cette requête identifie simplement les candidats à retirer pour votre attention.

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

Vous devriez jeter un oeil à Brent Ozars sp_BlitzIndex. Cette procédure stockée répertorie entre autres les index non utilisés. Il répertorie les troubles dans un rapport. Pour chaque entrée, une URL est fournie qui explique ce qu'il faut rechercher et comment gérer le problème.