web-dev-qa-db-fra.com

Améliorez les performances des requêtes lors de la sélection de presque toutes les lignes avec de nombreuses colonnes "group by"

J'ai un tableau avec 20 colonnes et environ 600 000 enregistrements. La taille de ligne maximale n'est que d'environ 100 octets. Le tableau est repeuplé tous les quelques jours, mais le nombre d'enregistrements reste à peu près le même.

Pour l'instant, il n'y a qu'un seul index clusterisé: une colonne d'identité int pour la clé primaire.

J'ai plusieurs requêtes et vues qui s'appuient sur cette table, dont l'exécution prend généralement 5 à 10 secondes. Lorsque je sélectionne simplement tous les enregistrements (select * from myTable), il faut environ 4 secondes pour récupérer tous les résultats.

Je n'ai pas pu trouver de repères pertinents pour sélectionner 500 000 enregistrements dans SQL Server. Cette fois est-elle typique?

Voici une requête typique que j'effectue sur la table:

select  CO.Company
    ,CO.Location
    ,CO.Account
    ,CO.SalesRoute
    ,CO.Employee
    ,CO.ProductType
    ,CO.Item
    ,CO.LoadJDate
    ,CO.CommissionRate
    ,SUM(CO.[Extended Sales Price]) AS Sales_Dollars
    ,SUM(CO.[Delivered Qty]) AS Quantity
from    dbo.Commissions_Output CO
where   CO.[Extended Sales Price] <> 0
group by    CO.Company
        ,CO.Location
        ,CO.Account
        ,CO.SalesRoute
        ,CO.Employee
        ,CO.ProductType
        ,CO.Item
        ,CO.LoadJDate
        ,CO.CommissionRate

Lorsque j'ai au moins un index non cluster sur la table, j'obtiens le résultat suivant:

Nombre de balayages 18, lectures logiques 18372; Temps CPU = 24818 ms, temps écoulé = 8614 ms.

J'ai essayé divers indices et combinaisons (index sur la colonne de filtre, incluez les colonnes de regroupement; index sur toutes les colonnes de filtrage/regroupement et incluez les colonnes d'agrégation; etc.). Tous donnent les mêmes performances et utilisent presque toujours le même plan d'exécution.

Lorsque je supprime tout sauf l'index cluster (PK), les performances sont souvent améliorées de 3 à 4 secondes. Les lectures logiques sont réduites tandis que le nombre de scans est divisé par deux.

Quelques remarques sur les données: les résultats de la clause select et where avant le regroupement sont d'environ 500 000 lignes (presque tout le tableau). Seulement environ 10 000 lignes sont combinées via le regroupement, ce qui laisse encore environ 500 000 enregistrements au total après le regroupement.

Le plan d'exécution sans index non clusterisé montre que les opérations les plus coûteuses sont une correspondance de hachage (49%) et une analyse d'index clusterisé (35%) pour la clause where. MSSMS recommande de créer un index non cluster pour [Extended Sales Price]. Le plan d'exécution avec au moins un index non clusterisé montre que l'opération la plus coûteuse est le tri (sur les colonnes de regroupement).

Étant donné que cette requête renvoie presque tous les enregistrements et que le regroupement réduit à peine le nombre de lignes, est-ce aussi rapide que la requête peut l'obtenir? Cela semble si lent, et j'ai lu des articles et SO questions sur les personnes qui retournent des centaines de milliers de lignes en moins de 1000 ms. Suis-je en train de manquer quelque chose, ou est-ce une vitesse assez typique? Normaliser ce tableau n'est actuellement pas une option, et je ne sais pas comment beaucoup qui aiderait.

Une dernière note: j'ai plusieurs vues et autres requêtes qui impliquent de se joindre à cette table (il y a une certaine normalisation). Au début, je pensais que ces vues et requêtes étaient lentes à cause de mauvaises jointures et autres, mais il semble que le véritable coupable soit cette table et les requêtes initiales. La plupart des requêtes et des vues fonctionnent avec presque toutes les données du tableau. Lorsque je sélectionne une seule colonne ou une petite fraction de lignes, le temps d'exécution est très bien, mais c'est rare.

Mise à jour: Voici tous les temps d'exécution, les plans et les statistiques IO. Je n'ai pas exécuté chaque requête des centaines de fois, mais les temps d'exécution ne semblent pas varient de plus de 1000 ms "chaud" vs "froid".

Aucun index non clusterisé, aucun paramètre MAXDOP: nonc_nomaxdop

Tableau 'Commissions_Output'. Nombre de balayages 9, lectures logiques 11263, lectures physiques 0, lectures anticipées 0, lectures logiques 0, lob lectures physiques 0, lob lectures anticipées 0.

Temps CPU = 6690 ms, temps écoulé = 4605 ms. (temps CPU maximum = 7516 ms, temps écoulé min = 3754 ms.)

Avec un index non clusterisé, pas de paramètre MAXDOP: nc_nomaxdop

Tableau 'Commissions_Output'. Nombre de scans 16, lectures logiques 6227

Temps CPU = 6591 ms, temps écoulé = 3717 ms.

Pas d'index non clusterisé, MAXDOP 1: nonc_maxdop

Tableau 'Commissions_Output'. Nombre de numérisations 1, lectures logiques 10278

Temps CPU = 2656 ms, temps écoulé = 4991 ms.

Avec un index non clusterisé, MAXDOP 1: nc_maxdop

Tableau 'Commissions_Output'. Nombre de numérisations 1, lectures logiques 10278

Temps CPU = 2656 ms, temps écoulé = 4991 ms.

Index non cluster utilisé:

create nonclustered index IX_NC_Comm_Output on dbo.Commissions_Output([Extended Sales Price])
include (company, location, account, salesroute, employee, producttype, item, loadjdate, commissionrate, [delivered qty])
5
Zairja

L'index non cluster que vous avez testé n'est pas le meilleur pour cette requête. Il peut être utilisé pour la clause WHERE et pour effectuer une analyse d'index au lieu d'une analyse complète de la table, mais il ne peut pas être utilisé pour la GROUP BY.

Le meilleur index possible devrait être un index partiel (pour filtrer les lignes indésirables de la clause WHERE), puis avoir toutes les colonnes utilisées dans le GROUP BY puis INCLUDE toutes les autres colonnes utilisées dans le SELECT:

CREATE INDEX special_ix 
  ON dbo.Commissions_Output
    ( company, location, account, 
      salesroute, employee, producttype, 
      item, loadjdate, commissionrate ) 
INCLUDE 
  ( [Extended Sales Price], [Delivered Qty] ) 
WHERE 
  ( [Extended Sales Price] <> 0 ) ;
3
ypercubeᵀᴹ

Je voudrais aborder le problème sous un angle différent.

Je suis d'accord avec @ypercube que vous pouvez toujours mettre en place un index pour faciliter les requêtes. Cela dit:

  • vous avez mentionné que le tableau contient une quantité relativement faible de données
  • la table n'est reconstruite qu'une fois tous les quelques jours
  • vous avez montré que l'agrégation sur les colonnes de texte est la partie la plus coûteuse de votre requête typique que vous rencontrerez même après la création d'un index de couverture

Pourquoi ne pas aller plus loin et créer les agrégations à l'avance afin que les requêtes n'aient pas besoin d'être répétées plusieurs fois? On dirait un cas idéal pour un vue indexée , où vous matérialiseriez tôt la sortie de la requête d'agrégation, ou une table dédiée traditionnelle que vous rempliriez lors du chargement des données dans Commissions_Output. Dans tous les cas, vous ne sacrifiez que peu d'espace disque pour des performances nettement améliorées.

Les vues indexées ont un tas de limitations concernant l'environnement dans lequel vous souhaitez les utiliser, mais ont un grand avantage d'être utilisées automatiquement au lieu de la table d'origine dans certaines circonstances .

5
bartover