Yggdrasil peut-il suffire ?
Chaque SGBD dispose d'un optimiseur ou planner lui permettant de déterminer comme exécuter votre requête. Ce n'est pas parce qu'un index peut être utilisé qu'il le sera. Le choix est basé sur une estimation de coût et le but est bien sûr d'améliorer les temps de réponse.
Les optimiseurs d'Oracle et PostgreSQL savent au moment de faire le choix de passer par un index si un critère élimine beaucoup de lignes ou non. Mais ces SGBD ne se limitent pas à la sélectivité. Ils utilisent en plus ce qu'Oracle définit comme le clustering factor et PostgreSQL comme la corrélation. Cela leur permet de déterminer plus précisément le coût de lecture lors de l'exécution si un index est utilisé.
Il existe cependant un cas où l'index est plus souvent bénéfique même si la sélectivité est médiocre. Si toutes les données à retourner sont dans l'index alors il peut permettre de ne pas lire du tout les données de la table.
Le terme employé par PostgreSQL est Index Only Scan, parcours d'index seul. Les Index Only Scan ont été introduits avec la version 9.2 ce qui signifie que TOUTES les versions supportées de PostgreSQL en sont capables en 2018.
Démonstration avec PostgreSQL 11 devel :
Ouille, les conditions semblent réunies pour un parcours d'index seul et pourtant le plan d'exécution ne change pas après la création de l'index composé.
En fait cette situation est liée au fonctionnement de PostgreSQL (MVCC) et à la nature artificielle de mon test. Dans la réalité la table aurait vécu, reçu des AUTOVACUUM etc. Nous allons procéder à un VACUUUM (attention pas un VACUUM FULL !), ce que je vous conseille après un chargement massif décisionnel par exemple, et relancer la requête :
Pas de "heap fetches" (retours vers la table) nécessaires après le VACUUM. Le temps de réponse est meilleur que celui obtenu initialement alors que le parallélisme n'est pas utilisé et que la consommation de ressources est moindre.
Lors de mes tests sommaires avec SQL Server, j'ai constaté qu'avec ce SGBD seule la sélectivité compte lorsqu'il s'agit de déterminer si un index doit être utilisé. L'optimiseur est donc logiquement bien plus conservateur lorsque la sélectivité n'est pas excellente...sauf bien sûr dans le cas où un Index Only Scan est possible.
SQL Server a introduit la possibilité de stocker dans les index les données de colonnes ne participant pas à l'index au lieu d'avoir un simple pointeur vers la table. Dans la terminologie SQL Server, l'index devient alors couvrant. Il ne faut tout de même pas en abuser. Le risque en recopiant N fois la table au niveau de N index est bien sûr d'occasionner une pénalité au niveau des écritures. La RAM n'est pas non extensible à l'infini, il faut bien monter ces pages d'index en cache lorsqu'elles sont lues et cette approche peut au final également pénaliser les lectures.
PostgreSQL dispose des index couvrants depuis la version 11 via le mot clé INCLUDE. Démonstration :
L'index est (très légèrement) plus petit et les performances sont (très légèrement) meilleures que celles obtenues avec un index composé classique.
INCLUDE n'ajoute pas une fonctionnalité essentielle mais, si vous êtes certains qu'une ou plusieurs colonnes apparaissent dans les clauses de projection (SELECT) mais jamais dans les clauses de restriction (WHERE) ET qu'il y a un bénéfice important à réaliser un Index Only Scan alors vous pouvez prendre en compte ces colonnes supplémentaires via la clause INCLUDE plutôt que de créer un index composé classique. Petite caractéristique intéressante qui n'est pas présentée dans l'exemple : si l'index est unique, la contrainte ne concerne pas les colonnes ajoutées via INCLUDE.
Les réserves déjà émises s'appliquent évidemment, n'oubliez pas le coût de maintenance des index et le fait que la mémoire vive n'est pas infinie. Si les requêtes sont variées, recopier les tables dans chaque index composé n'est probablement PAS la bonne approche !