Index et performances multicolonnes

31

J'ai une table avec un index multicolonne, et j'ai des doutes sur le bon tri des index pour obtenir les performances maximales sur les requêtes.

Le scénario:

  • PostgreSQL 8.4, table avec environ un million de lignes

  • Les valeurs de la colonne c1 peuvent avoir environ 100 valeurs différentes . Nous pouvons supposer que les valeurs sont réparties uniformément, nous avons donc environ 10000 lignes pour chaque valeur possible.

  • La colonne c2 peut avoir 1000 valeurs différentes . Nous avons 1000 lignes pour chaque valeur possible.

Lors de la recherche de données, la condition inclut toujours des valeurs pour ces deux colonnes, de sorte que la table a un index multicolonne combinant c1 et c2. J'ai lu l'importance de bien ordonner les colonnes dans un index multicolonne si vous avez des requêtes utilisant une seule colonne pour le filtrage. Ce n'est pas le cas dans notre scénario.

Ma question est celle-ci:

Étant donné que l'un des filtres sélectionne un ensemble de données beaucoup plus petit, pourrais-je améliorer les performances si le premier index est le plus sélectif (celui qui autorise un ensemble plus petit)? Je n'avais jamais réfléchi à cette question avant d'avoir vu les graphiques de l'article référencé:

entrez la description de l'image ici

Image tirée de l'article référencé sur les index multicolonnes .

Les requêtes utilisent les valeurs des deux colonnes pour le filtrage. Je n'ai aucune requête utilisant une seule colonne pour le filtrage. Tous sont: WHERE c1=@ParameterA AND c2=@ParameterB. Il existe également des conditions comme celle-ci:WHERE c1 = "abc" AND c2 LIKE "ab%"

jap1968
la source

Réponses:

36

Répondre

Puisque vous vous référez au site Web use-the-index-luke.com, considérez le chapitre:

Utilisez l'index, Luke ›La clause Where› Recherche de plages › Greater, Less and BETWEEN

Il a un exemple qui correspond parfaitement à votre situation (index à deux colonnes, l'un est testé pour l' égalité , l'autre pour la plage ), explique (avec plus de ces beaux graphiques d'index) pourquoi les conseils de @ ypercube sont précis et le résument :

Rule of thumb: index for equality first  then for ranges.

Aussi bon pour une seule colonne?

Que faire pour les requêtes sur une seule colonne semble être claire. Plus de détails et de références concernant cela sous ces questions connexes:

Colonne moins sélective en premier?

En dehors de cela, que se passe-t-il si vous n'avez que des conditions d'égalité pour les deux colonnes ?

Ça n'a pas d'importance . Mettez la colonne en premier qui est plus susceptible de recevoir ses propres conditions, ce qui compte réellement.

Considérez cette démo ou reproduisez-la vous-même. Je crée un simple tableau de deux colonnes avec 100k lignes. L'un avec très peu , l'autre avec beaucoup de valeurs distinctes:

CREATE TEMP TABLE t AS
SELECT (random() * 10000)::int AS lots
     , (random() * 4)::int     AS few
FROM generate_series (1, 100000);

DELETE FROM t WHERE random() > 0.9;  -- create some dead tuples, more "real-life"

ANALYZE t;

SELECT count(distinct lots)   -- 9999
     , count(distinct few)    --    5
FROM   t;

Question:

SELECT *
FROM   t
WHERE  lots = 2345
AND    few = 2;

EXPLAIN ANALYZE sortie (meilleur de 10 pour exclure les effets de mise en cache):

Scan Seq sur t (coût = 0,00..5840,84 lignes = 2 largeur = 8)
               (temps réel = 5,646..15,535 lignes = 2 boucles = 1)
  Filtre: ((lots = 2345) ET (quelques = 2))
  Tampons: hit local = 443
Durée totale: 15,557 ms

Ajouter un index, retester:

CREATE INDEX t_lf_idx ON t(lots, few);
Scan d'index en utilisant t_lf_idx sur t (coût = 0,00..3,76 lignes = 2 largeur = 8)
                                (temps réel = 0,008 à 0,011 ligne = 2 boucles = 1)
  Index Cond: ((lots = 2345) ET (peu = 2))
  Tampons: hit local = 4
Durée d'exécution totale: 0,027 ms

Ajouter un autre index, retester:

DROP INDEX t_lf_idx;
CREATE INDEX t_fl_idx  ON t(few, lots);
Scan d'index en utilisant t_fl_idx sur t (coût = 0,00..3,74 lignes = 2 largeur = 8)
                                (temps réel = 0,007..0,011 lignes = 2 boucles = 1)
  Index Cond: ((quelques = 2) ET (lots = 2345))
  Tampons: hit local = 4
Durée d'exécution totale: 0,027 ms
Erwin Brandstetter
la source
Est-ce également le cas pour 3 (ou plus) colonnes de l'index?
hayd
@hayd: Je ne sais pas à quoi "cela" fait référence. Vous pourriez poser une nouvelle question . Vous pouvez toujours référencer celui-ci pour le contexte. (Et déposez un commentaire ici pour créer un lien.)
Erwin Brandstetter
Par "ceci", je veux dire "le classement de la définition de l'index importe-t-il s'il y a plus de 2 colonnes dans la définition de l'index"
hayd
@hayd: Point le plus important: un index btree est bon pour les requêtes avec des conditions d'égalité sur les expressions d'index principales . L'ordre parmi ceux-ci est généralement hors de propos. Beaucoup d'autres détails qui ne
Erwin Brandstetter
Merci, je vais essayer d'écrire une question cohérente et un lien vers elle.
hayd
11

Si, comme vous le dites, les requêtes impliquant ces 2 colonnes, sont toutes des vérifications d'égalité des deux colonnes, par exemple:

WHERE c1=@ParameterA AND c2=@ParameterB

ne vous embêtez pas avec cela. Je doute qu'il y ait une différence et s'il y en a une, elle sera négligeable. Vous pouvez toujours tester bien sûr, avec vos données et les paramètres de votre serveur. Différentes versions d'un SGBD peuvent se comporter légèrement différemment en ce qui concerne l'optimisation.

L'ordre à l'intérieur de l'index importerait pour d'autres types de requêtes, ayant des vérifications d'une seule colonne, ou des conditions d'inégalité, ou des conditions sur une colonne et un regroupement dans l'autre, etc.

Si je devais choisir l'une des deux commandes, je choisirais de mettre la colonne la moins sélective en premier. Considérons un tableau avec des colonnes yearet month. Il est plus probable que vous ayez besoin d'une WHERE year = 2000condition ou d'une WHERE year BETWEEN 2000 AND 2013ou d'une WHERE (year, month) BETWEEN (1999, 6) AND (2000, 5).

Une requête du type WHERE month = 7 GROUP BY yearpeut être souhaitée (Rechercher les personnes nées en juillet), mais elle le serait moins souvent. Cela dépend bien sûr des données réelles stockées dans votre table. Choisissez une commande pour l'instant, dites le (c1, c2)et vous pourrez toujours ajouter un autre index plus tard (c2, c1).


Mise à jour, après le commentaire du PO:

Il existe également des conditions comme celle-ci: WHERE c1 = 'abc' AND c2 LIKE 'ab%'

Ce type de requête est exactement une condition de plage sur la c2colonne et aurait besoin d'un (c1, c2)index. Si vous avez également des requêtes de type inverse:

WHERE c2 = 'abc' AND c1 LIKE 'ab%'

alors ce serait bien si vous aviez aussi un (c2, c1)indice.

ypercubeᵀᴹ
la source