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é:
Image tirée de l'article référencé sur 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. Ils sont tous: WHERE c1=@ParameterA AND c2=@ParameterB
. Il existe également des conditions comme celle-ci: WHERE c1 = "abc" AND c2 LIKE "ab%"
Puisque vous faites référence au site Web use-the-index-luke.com
, Considérez le chapitre:
tilisez l'index, Luke ›La clause Where› Recherche de plages › supérieur, inférieur et ENTRE
Il a un exemple qui correspond parfaitement à votre situation (index à deux colonnes, l'un est testé pour égalité, l'autre pour plage), explique (avec plus de ces jolis graphiques d'index) pourquoi les conseils de @ ypercube est précis et le résume:
Rule of thumb: index for equality first — then for ranges.
Que faire pour les requêtes sur une seule colonne semble clair. Plus de détails et de références concernant cela sous ces questions connexes:
En dehors de cela, que se passe-t-il si vous avez uniquement des conditions d'égalité pour les deux colonnes?
Peu importe . 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 lots 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;
Requete:
SELECT *
FROM t
WHERE lots = 2345
AND few = 2;
Sortie EXPLAIN ANALYZE
(Le meilleur des 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 (peu = 2)) Tampons: hit local = 443 Durée d'exécution totale: 15,557 ms
Ajouter un index, retester:
CREATE INDEX t_lf_idx ON t(lots, few);
Index Scan en utilisant t_lf_idx sur t (coût = 0,00..3,76 lignes = 2 largeur = 8) (Temps réel = 0,008..0,011 lignes = 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);
Index Scan 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
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 sélective moins en premier. Prenons un tableau avec les colonnes year
et month
. Il est plus probable que vous ayez besoin d'une condition WHERE year = 2000
Ou WHERE year BETWEEN 2000 AND 2013
Ou WHERE (year, month) BETWEEN (1999, 6) AND (2000, 5)
.
Une requête du type WHERE month = 7 GROUP BY year
Peut être sûre (Rechercher les personnes nées en juillet), mais elle serait moins fréquente. 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)
.
pdate, 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 colonne c2
Et aurait besoin d'un index (c1, c2)
. 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 index (c2, c1)
.