33.13. Informations sur l'optimisation d'un opérateur
Une définition d'opérateur postgresql™ peut inclure plusieurs clauses
optionnelles qui donnent au système des informations utiles sur le
comportement de l'opérateur. Ces clauses devraient être fournies
chaque fois que c'est utile car elles peuvent considérablement
accélérer l'exécution des requêtes utilisant cet opérateur. Mais si
vous le faites, vous devez être sûr de leur justesse ! L'usage
incorrect d'une clause d'optimisation peut entraîner un arrêt brutal
du processus serveur, des sorties subtilement fausses ou d'autres
effets pervers. Vous pouvez toujours abandonner une clause
d'optimisation si vous n'êtes pas sûr d'elle ; la seule conséquence
est un possible ralentissement des requêtes.
Des clauses additionnelles d'optimisation pourront être ajoutées dans
les futures versions de postgresql™. celles décrites ici sont
toutes celles que cette version comprend.
33.13.1. commutator
Si elle est fournie, la clause commutator
désigne un opérateur qui est le commutateur de l'opérateur en cours
de définition. Nous disons qu'un opérateur A est le commutateur de
l'opérateur B si (x A y) est égal à (y B x) pour toute valeur
possible de x, y. Notez que B est aussi le commutateur de A. Par
exemple, les opérateurs < et > pour un type particulier de données sont
habituellement des commutateurs l'un pour l'autre, et l'opérateur
+ est habituellement commutatif avec
lui-même. Mais l'opérateur - n'est
habituellement commutatif avec rien.
Le type de l'opérande gauche d'un opérateur commuté est le même que
l'opérande droit de son commutateur, et vice versa. Aussi
postgresql™ n'a besoin que
du nom de l'opérateur commutateur pour consulter le commutateur, et
c'est tout ce qui doit être fourni à la clause commutator .
Vous avez juste à définir un opérateur auto-commutateur. Mais les
choses sont un peu plus compliquées quand vous définissez une paire
de commutateurs : comment peut-on définir la référence du premier
au second alors que ce dernier n'est pas encore défini ? Il y a
deux solutions à ce problème :
-
Une façon d'opérer est d'omettre la clause commutator dans le premier opérateur que vous
définissez et ensuite d'en insérer une dans la définition du
second opérateur. Puisque postgresql™ sait que les
opérateurs commutatifs vont par paire, quand il voit la
seconde définition, il retourne instantanément remplir la
clause commutator manquante dans la
première définition.
-
L'autre façon, plus directe, est de simplement inclure les
clauses commutator dans les deux
définitions. quand postgresql™ traite la première
définition et réalise que la clause commutator se réfère à un opérateur
inexistant, le système va créer une entrée provisoire pour
cet opérateur dans le catalogue système. Cette entrée sera
pourvue seulement de données valides pour le nom de
l'opérateur, les types d'opérande droit et gauche et le type
du résultat, puisque c'est tout ce que postgresql™ peut déduire à ce
point. la première entrée du catalogue pour l'opérateur sera
liée à cette entrée provisoire. Plus tard, quand vous
définirez le second opérateur, le système mettra à jour
l'entrée provisoire avec les informations additionnelles
fournies par la seconde définition. Si vous essayez
d'utiliser l'opérateur provisoire avant qu'il ne soit
complété, vous aurez juste un message d'erreur.
33.13.2. negator
La clause negator dénomme un opérateur qui
est l'opérateur de négation de l'opérateur en cours de définition.
Nous disons qu'un opérateur A est l'opérateur de négation de
l'opérateur B si tous les deux renvoient des résultats booléens et
si (x A y) est égal à NOT (x B y) pour toutes les entrées possible
x, y. Notez que B est aussi l'opérateur de négation de A. Par
exemple, < et >= forment une paire d'opérateurs de négation
pour la plupart des types de données. Un opérateur ne peut jamais
être validé comme son propre opérateur de négation .
Au contraire des commutateurs, une paire d'opérateurs unaires peut
être validée comme une paire d'opérateurs de négation réciproques ;
ce qui signifie que (A x) est égal à NOT (B x) pour tout x ou
l'équivalent pour les opérateurs unaires à droite.
L'opérateur de négation d'un opérateur doit avoir les mêmes types
d'opérandes gauche et/ou droit que l'opérateur à définir comme avec
commutator. seul le nom de l'opérateur
doit être donné dans la clause negator.
Définir un opérateur de négation est très utile pour l'optimiseur
de requêtes car il permet de simplifier des expressions telles que
not (x = y) en x
<> y. ceci arrive souvent parce que les opérations
not peuvent être insérées à la suite
d'autres réarrangements.
Des paires d'opérateurs de négation peuvent être définies en
utilisant la même méthode que pour les commutateurs.
33.13.3. restrict
La clause restrict, si elle est invoquée,
nomme une fonction d'estimation de sélectivité de restriction pour
cet opérateur (notez que c'est un nom de fonction, et non pas un
nom d'opérateur). Les clauses restrict
n'ont de sens que pour les opérateurs binaires qui renvoient un
type boolean. un estimateur de
sélectivité de restriction repose sur l'idée de prévoir quelle
fraction des lignes dans une table satisfera une condition de
clause where de la forme
colonne OP constante
pour l'opérateur courant et une valeur constante particulière. Ceci
aide l'optimiseur en lui donnant une idée du nombre de lignes qui
sera éliminé par les clauses where qui ont
cette forme (vous pouvez vous demander, qu'arrivera-t-il si la
constante est à gauche ? hé bien, c'est une des choses à laquelle
sert le commutator...).
L'écriture de nouvelles fonctions d'estimation de restriction de
sélectivité est éloignée des objectifs de ce chapitre mais,
heureusement, vous pouvez habituellement utiliser un des
estimateurs standards du système pour beaucoup de vos propres
opérateurs. Voici les estimateurs standards de restriction :
|
eqsel pour =
|
|
neqsel pour <>
|
|
scalarltsel pour < ou <=
|
|
scalargtsel pour > ou >=
|
Ces catégories peuvent sembler un peu curieuses mais cela prend un
sens si vous y réfléchissez. = acceptera
typiquement une petite fraction des lignes d'une table ; <> rejettera typiquement seulement une petite
fraction des lignes de la table. <
acceptera une fraction des lignes en fonction de la situation de la
constante donnée dans la gamme de valeurs de la colonne pour cette
table (ce qui est justement l'information collectée par la commande
analyze
et rendue
disponible pour l'estimateur de sélectivité). <= acceptera une fraction légèrement plus grande
que < pour la même constante de
comparaison mais elles sont assez proches pour ne pas valoir la
peine d'être distinguées puisque nous ne risquons pas de toute
façon de faire mieux qu'une grossière estimation. La même remarque
s'applique à > et >=.
Vous pouvez fréquemment vous en sortir à bon compte en utilisant
soit eqsel ou neqsel pour des opérateurs qui ont une très grande
ou une très faible sélectivité, même s'ils ne sont pas réellement
égalité ou inégalité. Par exemple, les opérateurs géométriques
d'égalité approchée utilisent eqsel en
supposant habituellement qu'ils ne correspondent qu'à une petite
fraction des entrées dans une table.
Vous pouvez utiliser scalarltsel et
scalargtsel pour des comparaisons de
types de données qui possèdent un moyen de conversion en scalaires
numériques pour les comparaisons de rang. Si possible, ajoutez le
type de données à ceux acceptés par la fonction convert_to_scalar() dans src/backend/utils/adt/selfuncs.c (finalement, cette
fonction devrait être remplacée par des fonctions pour chaque type
de données identifié grâce à une colonne du catalogue système
pg_type ; mais cela n'a pas encore été
fait). si vous ne faites pas ceci, les choses fonctionneront mais
les estimations de l'optimiseur ne seront pas aussi bonnes qu'elles
pourraient l'être.
D'autres fonctions d'estimation de sélectivité conçues pour les
opérateurs géométriques sont placées dans src/backend/utils/adt/geo_selfuncs.c : areasel, positionsel et
contsel. lors de cette rédaction, ce sont
seulement des fragments mais vous pouvez vouloir les utiliser (ou
mieux les améliorer).
33.13.4. join
La clause join, si elle est invoquée,
nomme une fonction d'estimation de sélectivité de jointure pour
l'opérateur (notez que c'est un nom de fonction, et non pas un nom
d'opérateur). Les clauses join n'ont de
sens que pour les opérateurs binaires qui renvoient un type
boolean. un estimateur de sélectivité de
jointure repose sur l'idée de prévoir quelle fraction des lignes
dans une paire de tables satisfera une condition de clause
where de la forme
table1.colonne1 OP table2.colonne2
pour l'opérateur courant. Comme pour la clause restrict, ceci aide considérablement l'optimiseur en
lui indiquant parmi plusieurs séquences de jointure possibles
laquelle prendra vraisemblablement le moins de travail.
Comme précédemment, ce chapitre n'essaiera pas d'expliquer comment
écrire une fonction d'estimation de sélectivité de jointure mais
suggérera simplement d'utiliser un des estimateurs standard s'il
est applicable :
|
eqjoinsel pour =
|
|
neqjoinsel pour <>
|
|
scalarltjoinsel pour < ou <=
|
|
scalargtjoinsel pour > ou >=
|
|
areajoinsel pour des comparaisons
basées sur une aire 2d
|
|
positionjoinsel pour des comparaisons
basées sur une position 2d
|
|
contjoinsel pour des comparaisons
basées sur un appartenance 2d
|
33.13.5. hashes
La clause hashes indique au système qu'il
est permis d'utiliser la méthode de jointure-découpage pour une
jointure basée sur cet opérateur. hashes
n'a de sens que pour un opérateur binaire qui renvoie un boolean et en pratique l'opérateur égalité serait
mieux approprié pour certains types de données
La jointure-découpage repose sur l'hypothèse que l'opérateur de
jointure peut seulement renvoyer la valeur vrai pour des paires de
valeurs droite et gauche qui correspondent au même code de
découpage. Si deux valeurs sont placées dans deux différents
paquets (« buckets »), la
jointure ne pourra jamais les comparer avec la supposition
implicite que le résultat de l'opérateur de jointure doit être
faux. Ainsi, il n'y a aucun sens à spécifier hashes pour des opérateurs qui ne représentent pas
l'égalité.
Pour être marqué hashes, l'opérateur de
jointure doit apparaître dans une classe d'opérateurs d'index de
découpage. Ceci n'est pas rendu obligatoire quand vous créez
l'opérateur, puisque évidemment la classe référençant l'opérateur
peut ne pas encore exister. Mais les tentatives d'utilisation de
l'opérateur dans les jointure-découpage échoueront à l'exécution si
une telle classe d'opérateur n'existe pas. Le système a besoin de
la classe d'opérateur pour définir la fonction de découpage
spécifique au type de données d'entrée de l'opérateur. Bien sûr,
vous devez également fournir une fonction de découpage appropriée
avant de pouvoir créer la classe d'opérateur.
On doit apporter une grande attention à la préparation des
fonctions de découpage parce qu'il y a des processus dépendants de
la machine qui peuvent ne pas faire les choses correctement. Par
exemple, si votre type de données est une structure dans laquelle
peuvent se trouver des bits de remplissage sans intérêt, vous ne
pouvez pas simplement passer la structure complète à la fonction
hash_any (à moins d'écrire vos autres
opérateurs et fonctions de façon à s'assurer que les bits
inutilisés sont toujours zéro, ce qui est la stratégie
recommandée). Un autre exemple est fourni sur les machines qui
respectent le standard de virgule-flottante ieee, le zéro négatif et le zéro positif sont
des valeurs différentes (les motifs de bit sont différents) mais
ils sont définis pour être égaux. Si une valeur flottante peut
contenir un zéro négatif, alors une étape supplémentaire est
nécessaire pour s'assurer qu'elle génère la même valeur de
découpage qu'un zéro positif.
Note
La fonction sous-jacente à un opérateur de jointure-découpage
doit être marquée immuable ou stable. Si elle est volatile, le
système n'essaiera jamais d'utiliser l'opérateur pour une
jointure hachage.
Note
Si un opérateur de jointure-hachage a une fonction sous-jacente
marquée stricte, la fonction doit également être complète :
cela signifie qu'elle doit renvoyer TRUE ou FALSE, jamais NULL,
pour n'importe quelle double entrée non NULL. Si cette règle
n'est pas respectée, l'optimisation de découpage des opérations
in peut générer des résultats faux
(spécifiquement, in devrait renvoyer
false quand la réponse correcte devrait être NULL ; ou bien il
devrait renvoyer une erreur indiquant qu'il ne s'attendait pas
à un résultat NULL).
33.13.6. merges (sort1, sort2, ltcmp, gtcmp)
la clause merges, si elle est présente,
indique au système qu'il est permis d'utiliser la méthode de
jointure-union pour une jointure basée sur cet opérateur.
merges n'a de sens que pour un opérateur
binaire qui renvoie un boolean et, en
pratique, cet opérateur doit représenter l'égalité pour des types
de données ou des paires de types de données.
la jointure-union est fondée sur le principe d'ordonner les tables
gauche et droite et ensuite de les comparer en parallèle. Ainsi,
les deux types de données doivent être capable d'être pleinement
ordonnées, et l'opérateur de jointure doit pouvoir réussir
seulement pour des paires de valeurs tombant à la
« même place » dans l'ordre de
tri. en pratique, cela signifie que l'opérateur de jointure doit se
comporter comme l'opérateur égalité. Mais contrairement à la
jointure-hachage, où il vaut mieux que les types de données droite
et gauche sont les mêmes (ou au moins soient bitwise équivalent),
il est possible de faire une jointure-union sur deux types de
données distincts tant qu'ils sont logiquement compatibles. Par
exemple, l'opérateur d'égalité smallint-contre-integer est
susceptible d'opérer une jointure-union. Nous avons seulement
besoin d'opérateurs de tri qui organisent les deux types de données
en séquences logiquement comparables.
L'exécution d'une jointure-union exige que le système soit capable
d'identifier quatre opérateurs rattachés à l'opérateur de
jointure-union : la comparaison less-than pour le type de donnée de
l'opérande gauche, la comparaison less-than pour le type de donnée
de l'opérande droit, la comparaison less-than entre les deux types
de donnée et la comparaison greater-than entre les deux types de
donnée (il y a en fait quatre opérateurs distincts si l'opérateur
de jointure-union a deux types de données d'opérande différents ;
mais quand les types d'opérande sont les mêmes, les trois
opérateurs less-than sont tous le même opérateur). Il est possible
de spécifier ces opérateurs individuellement par leur nom, comme
les options respectives sort1, sort2, ltcmp et gtcmp. le système remplira respectivement par défaut
les noms <, <, <, > si n'importe lequel d'entre eux est omis quand
merges est spécifié. de même, merges sera supposé être indiqué si n'importe
laquelle de ces quatre options apparaît, il est donc possible de
seulement spécifier quelques-unes de ces options et de laisser le
système compléter le reste.
Les types de données des opérandes des quatre opérateurs de
comparaison peuvent être déduits des types d'opérandes de
l'opérateur de jointure-union, aussi, exactement comme avec
commutator, seuls les noms d'opérateurs
ont besoin d'être donnés dans ces clauses. À moins que vous ne
fassiez des choix particuliers de noms d'opérateurs, il suffit
d'écrire merges et laisser le système
remplir les détails (comme avec commutator
et negator, le système est capable de
faire des entrées d'opérateur fictives si il vous arrive de définir
l'opérateur égalité avant les autres).
Il existe des restrictions additionnelles sur les opérateurs que
vous marquez comme jointure-union. Ces restrictions ne sont pas
actuellement contrôlées par la commande
create operator
mais des erreurs
peuvent intervenir lors de l'utilisation de l'opérateur si un des
points suivants n'est pas vérifié :
-
Un opérateur d'égalité capable de jointure-union doit avoir
un commutateur capable de jointure-union (qui peut être
lui-même si les deux types de donnée d'opérande sont les
mêmes, ou un opérateur d'égalité apparenté si ils sont
différents).
-
S'il existe un opérateur capable de jointure-union reliant
deux types de données A et B, et un autre opérateur capable
de jointure-union reliant B à un troisième type de donnée C,
alors A et C doivent aussi avoir un opérateur capable de
jointure-union ; en d'autres mots, avoir un opérateur de
jointure-union doit être une propriété transitive.
-
Des résultats bizarres apparaîtront lors de l'exécution si
les quatre opérateurs de comparaison que vous nommez ne
trient pas les valeurs de façon compatible.
Note
La fonction sous-jacente à un opérateur de jointure-union doit
être marquée immuable ou stable. Si elle est volatile, le
système n'essaiera jamais d'utiliser l'opérateur pour une
jointure union.
Note
Dans les versions de postgresql™ antérieures à la 7.3,
merges n'était pas disponible : pour
faire un opérateur de jointure union, on devait explicitement
écrire sort1 et sort2. de plus, les options ltcmp et gtcmp
n'existaient pas ; les noms de ces opérateurs ont été rattachés
respectivement à < et >.