Gestion des index manquants et inutiles

Voici deux requêtes permettant de créer les index manquants ou de supprimer les index inutiles dans les bases SQL Server

— mise en place de tous les index manquants :


SELECT 'CREATE INDEX X_' + REPLACE(CAST(NEWID() AS VARCHAR(50)), '-', '') + ' ON ' + statement + ' ('
       + CASE  
            WHEN equality_columns IS NULL THEN ''
            WHEN inequality_columns IS NULL THEN equality_columns
            ELSE equality_columns + ', '
         END
       + CASE  
            WHEN inequality_columns IS NULL THEN ''
            ELSE inequality_columns  
         END    
       + ')'
       + CASE  
            WHEN included_columns IS NULL THEN ''
            ELSE ' INCLUDE (' + included_columns +')'
         END
       + ' WITH (FILLFACTOR = 90);'    
FROM   sys.dm_db_missing_index_details

— suppression des index inutiles dans une base


USE <MaBase>;
 
SELECT 'DROP INDEX [' + SCHEMA_NAME(schema_id)  
               +'].['+o.name +'].[' + i.name +'];' AS SQL_STATEMENT,
       user_seeks, user_scans, user_lookups, user_updates
FROM   sys.dm_db_index_usage_stats AS ius  
       INNER JOIN sys.indexes AS i
             ON ius.object_id = i.object_id
             AND ius.index_id = i.index_id
       INNER JOIN sys.objects AS o
             ON ius.object_id = o.object_id
WHERE  database_id = DB_ID()  
  AND  is_unique = 0
  AND  is_primary_key = 0
  AND  is_unique_constraint = 0
  AND  user_seeks = 0
  AND  user_updates > 0
ORDER BY COALESCE(CAST(user_updates AS FLOAT) / NULLIF(user_scans + user_lookups, 0), 2000000000) DESC;

— NOTA : Remplacer <MaBase> par le nom de votre base


--------
Frédéric Brouard, SQLpro - ARCHITECTE DE DONNÉES, http://sqlpro.developpez.com/
Expert bases de données relationnelles et langage SQL. MVP Microsoft SQL Server
www.sqlspot.com : modélisation, conseil, audit, optimisation, tuning, formation
* * * * *  Enseignant CNAM PACA - ISEN Toulon - CESI Aix en Provence  * * * * *

MVP Microsoft SQL Server

5 réflexions au sujet de « Gestion des index manquants et inutiles »

  1. Avatar de BourguiBourgui

    Merci pour tout.

    Les champs ntext son ici pour des raison plus historique, j’essaie de les chasser au fur et a mesure (d’ailleur la requête m’a permis d’en trouver un autre). Mais c’est pas évident évident sur une base qui a déjà quelques année et un bon paquet de tables.

    Encore merci

  2. Avatar de sqlprosqlpro Auteur de l’article

    1) les types TEXT, NTEXT et IMAGE sont obsolète depuis la version 2005. Il est donc urgent de les transformer en VARCHAR(max), NVARCHAR(max) et VARBINARY(max). Une prochaine version ne les supportera plus.

    2) si vous avez une seule colonne de ce type dans la clause INCLUDE, alors vous pouvez utiliser cette requête :

    WITH &nbsp;<br />
    T01 AS&nbsp;<br />
    (SELECT '[' + TABLE_CATALOG +'].[' + TABLE_SCHEMA +'].[' + TABLE_NAME + ']' AS TABLE_FULL_NAME, &nbsp;<br />
    &nbsp;       COLUMN_NAME&nbsp;<br />
    &nbsp;FROM   INFORMATION_SCHEMA.COLUMNS&nbsp;<br />
    &nbsp;WHERE  DATA_TYPE IN ('text', 'ntext', 'image')),&nbsp;<br />
    T02 AS &nbsp;<br />
    (SELECT statement, equality_columns, inequality_columns, included_columns, COLUMN_NAME,&nbsp;<br />
    &nbsp;       PATINDEX( '%' + COLUMN_NAME + '%', included_columns) AS COL_NON_INCLUDE&nbsp;<br />
    &nbsp;FROM   sys.dm_db_missing_index_details AS mid&nbsp;<br />
    &nbsp;       LEFT OUTER JOIN T01&nbsp;<br />
    &nbsp;            ON mid.statement = T01.TABLE_FULL_NAME),&nbsp;<br />
    T03 AS&nbsp;<br />
    (SELECT  statement, equality_columns, inequality_columns, COL_NON_INCLUDE,&nbsp;<br />
    &nbsp;        CASE WHEN COL_NON_INCLUDE IS NULL -- pas de colonnes BLOB obsolète&nbsp;<br />
    &nbsp;                  THEN included_columns&nbsp;<br />
    &nbsp;             WHEN COL_NON_INCLUDE = 1 AND LEN(included_columns) = LEN(COLUMN_NAME) -- colonne BLOB obsolète seule dans l'include&nbsp;<br />
    &nbsp;                  THEN NULL&nbsp;<br />
    &nbsp;             WHEN COL_NON_INCLUDE = 1 -- colonne BLOB obsolète en tête&nbsp;<br />
    &nbsp;                  THEN SUBSTRING(included_columns, LEN(COLUMN_NAME) + 3, LEN(included_columns) - LEN(COLUMN_NAME) - 2)&nbsp;<br />
    &nbsp;             WHEN COL_NON_INCLUDE &gt; 1 AND COL_NON_INCLUDE + LEN(COLUMN_NAME) - 1 &lt; LEN(included_columns) -- colonne BLOB obsolète au milieu&nbsp;<br />
    &nbsp;                  THEN SUBSTRING(included_columns, 1, COL_NON_INCLUDE - 1) &nbsp;<br />
    &nbsp;                     + SUBSTRING(included_columns, COL_NON_INCLUDE + LEN(COLUMN_NAME) + 2, LEN(included_columns) - (COL_NON_INCLUDE + LEN(COLUMN_NAME) ))&nbsp;<br />
    &nbsp;             WHEN COL_NON_INCLUDE &gt; 1 AND COL_NON_INCLUDE + LEN(COLUMN_NAME) - 1 = LEN(included_columns) -- colonne BLOB obsolète en fin  &nbsp;<br />
    &nbsp;                  THEN SUBSTRING(included_columns, 1, COL_NON_INCLUDE - 3)        &nbsp;<br />
    &nbsp;             ELSE included_columns&nbsp;<br />
    &nbsp;        END AS INCLUSIVE_COLS&nbsp;<br />
    &nbsp;FROM T02),&nbsp;<br />
    T04 AS &nbsp;<br />
    (SELECT statement, equality_columns, inequality_columns, INCLUSIVE_COLS, LEN(INCLUSIVE_COLS) AS LN,&nbsp;<br />
    &nbsp;       MIN(LEN(INCLUSIVE_COLS)) OVER(PARTITION BY statement, equality_columns, inequality_columns) AS MLN&nbsp;<br />
    &nbsp;FROM   T03),&nbsp;<br />
    T05 AS&nbsp;<br />
    &nbsp;(SELECT statement, equality_columns, inequality_columns, INCLUSIVE_COLS AS included_columns&nbsp;<br />
    &nbsp; FROM   T04 &nbsp;<br />
    &nbsp; WHERE  LN = MLN)&nbsp;<br />
    SELECT 'CREATE INDEX X_' + REPLACE(CAST(NEWID() AS VARCHAR(50)), '-', '') + ' ON ' + statement + ' (' &nbsp;<br />
    &nbsp;      + COALESCE(equality_columns +', ' + inequality_columns, equality_columns, inequality_columns) + ') '&nbsp;<br />
    &nbsp;      + COALESCE(' INCLUDE (' + included_columns +') ', '')&nbsp;<br />
    &nbsp;      + ' WITH (FILLFACTOR = 90);'    &nbsp;<br />
    FROM   T05;

    3) Si vous avez plusieurs colonnes de type BLOB obsolète, alors il faut faire une requête récursive qui dépile la chaine included_columns en colonne, supprime de cette colonne les références aux colonnes BLOB obsolètes puis remettre cela sous forme de chaine. Voici un exemple qui utilise la fonction de dépliage F_MULTIPARSE dont j’ai donné le code ici : http://blog.developpez.com/sqlpro/p7509/langage-sql-norme/fonction-de-decoupage-a-cesure-multiple/

    WITH &nbsp;<br />
    T01 AS&nbsp;<br />
    (SELECT ROW_NUMBER() OVER(ORDER BY statement, equality_columns, inequality_columns, included_columns) AS NID,&nbsp;<br />
    &nbsp;       statement, equality_columns, inequality_columns, included_columns&nbsp;<br />
    &nbsp;FROM   sys.dm_db_missing_index_details),&nbsp;<br />
    T02 AS&nbsp;<br />
    (SELECT  T01.*, LTRIM(MOT) AS COL, COLUMN_NAME AS COL_TO_DELETE&nbsp;<br />
    &nbsp;FROM    T01&nbsp;<br />
    &nbsp;        OUTER APPLY dbo.F_MULTIPARSE(included_columns, ',')&nbsp;<br />
    &nbsp;        LEFT OUTER JOIN INFORMATION_SCHEMA.COLUMNS&nbsp;<br />
    &nbsp;             ON T01.statement = '[' + TABLE_CATALOG +'].[' + TABLE_SCHEMA +'].[' + TABLE_NAME + ']' &nbsp;<br />
    &nbsp;                AND LTRIM(MOT) = COLUMN_NAME &nbsp;<br />
    &nbsp;                AND DATA_TYPE IN ('text', 'ntext', 'image')),&nbsp;<br />
    T03 AS&nbsp;<br />
    (SELECT DISTINCT NID, statement, equality_columns, inequality_columns, &nbsp;<br />
    &nbsp;       CASE WHEN COL_TO_DELETE IS NULL THEN COL ELSE NULL END AS COL_TO_INCLUDE&nbsp;<br />
    &nbsp;FROM   T02),&nbsp;<br />
    T04 AS&nbsp;<br />
    (SELECT *, ROW_NUMBER() OVER(PARTITION BY NID ORDER BY COL_TO_INCLUDE) AS N&nbsp;<br />
    &nbsp;FROM   T03 AS T1&nbsp;<br />
    &nbsp;WHERE  COL_TO_INCLUDE IS NOT NULL&nbsp;<br />
    &nbsp;  OR   (SELECT COUNT(*)&nbsp;<br />
    &nbsp;        FROM   T03 AS T2&nbsp;<br />
    &nbsp;        WHERE  T1.NID = T2.NID) = 1),&nbsp;<br />
    T05 AS         &nbsp;<br />
    (SELECT NID, statement, equality_columns, inequality_columns, N,&nbsp;<br />
    &nbsp;       CAST(COL_TO_INCLUDE AS NVARCHAR(max)) AS included_columns&nbsp;<br />
    &nbsp;FROM   T04&nbsp;<br />
    &nbsp;WHERE  N = 1&nbsp;<br />
    &nbsp;UNION ALL&nbsp;<br />
    &nbsp;SELECT T05.NID, T05.statement, T05.equality_columns, T05.inequality_columns, T04.N,&nbsp;<br />
    &nbsp;       T05.included_columns + ', ' + T04.COL_TO_INCLUDE&nbsp;<br />
    &nbsp;FROM   T05 &nbsp;<br />
    &nbsp;       INNER JOIN T04&nbsp;<br />
    &nbsp;             ON T04.NID = T05.NID AND T05.N + 1 = T04.N),&nbsp;<br />
    T06 AS&nbsp;<br />
    (SELECT *, ROW_NUMBER() OVER(PARTITION BY NID ORDER BY N DESC) AS M         &nbsp;<br />
    &nbsp;FROM T05)&nbsp;<br />
    &nbsp;<br />
    SELECT 'CREATE INDEX X_' + REPLACE(CAST(NEWID() AS VARCHAR(50)), '-', '') + ' ON ' + statement + ' (' &nbsp;<br />
    &nbsp;      + COALESCE(equality_columns +', ' + inequality_columns, equality_columns, inequality_columns) + ') '&nbsp;<br />
    &nbsp;      + COALESCE(' INCLUDE (' + included_columns +') ', '')&nbsp;<br />
    &nbsp;      + ' WITH (FILLFACTOR = 90);'  &nbsp;<br />
    FROM    T06&nbsp;<br />
    WHERE   M = 1;

    4) FILLFACTOR est le facteur de remplissage des pages d’index. A défaut la plupart du temps (sauf configuration particulière du serveur via sp_configure, option « fill factor (%) »), les pages des index sont construite avec un remplissage de 100%, provoquant une importante fragmentation des pages d’index à la moindre insertion ou modification (voir : http://sqlpro.developpez.com/optimisation/MaintenanceIndex/ pour une explication). Le fait de mettre un facteur de remplissage moins élevé réserve un peu de place pour les nouvelles insertions ou modifications au prix d’une légère augmentation de la taille de l’index. Ainsi, avec un FILLFACTOR DE 90% l’index grossit de 11,11 % exactement.

  3. Avatar de BourguiBourgui

    coallesce est effectivement une solution.

    il y a aussi un bug, mais je sais pas si il a une solution : lorsque une des colonnes incluse dans l’index est un ntext alors l’execution de la commande échoue (on ne peut inclure de ntextd ans un index car les donnée d’un ntext).

    Est ce que vous pouvez expliquer le FILLFactor = 90 par défaut ?

    Merci

  4. Avatar de sqlprosqlpro Auteur de l’article

    Bonne remarque, mais plutôt que d’utiliser le ISNULL qui est une fonction spécifique à SQL Server et limitée à deux paramètres, mieux vaut utiliser la fonction normative COALESCE…

    SELECT 'CREATE INDEX X_' + REPLACE(CAST(NEWID() AS VARCHAR(50)), '-', '') + ' ON ' + statement + ' (' &nbsp;<br />
    &nbsp;      + COALESCE(equality_columns +', ' + inequality_columns, equality_columns, inequality_columns) + ') '&nbsp;<br />
    &nbsp;      + COALESCE(' INCLUDE (' + included_columns +') ', '')&nbsp;<br />
    &nbsp;      + ' WITH (FILLFACTOR = 90);'    &nbsp;<br />
    FROM   sys.dm_db_missing_index_details

Laisser un commentaire