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 être la cause de requêtes lentes, 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.
Il est aussi possible d'attacher une fonction de support pour l'optimiseur à la fonction sous-jacente d'un opérateur, fournissant une autre façon de dire au système la façon dont se comporte l'opérateur. Voir Section 36.11 pour plus d'informations.
COMMUTATOR
#
La clause COMMUTATOR
, lorsqu'elle est fournie, nomme
l'opérateur qui est le commutateur de l'opérateur en cours de définition.
Nous disons que l'opérateur A est le commutateur de l'opérateur B si
(x A y) est équivalent à (y B x) pour toutes les valeurs x et y possibles
en entrée. Notez que B est aussi le commutateur de A. Par exemple, les
opérateurs <
et >
pour un type
de données particulier sont habituellement des commutateurs entre eux, et
l'opérateur +
est habituellement commutatif avec
lui-même. Mais l'opérateur -
n'est habituellement pas
commutatif avec quoi que ce soit.
Le type de l'opérande gauche d'un opérateur commutable est le même que le
type de l'opérande droit de son commutateur, et vice versa. Donc le nom de
l'opérateur commutateur est tout ce dont
PostgreSQL a besoin pour rechercher le commutateur,
et c'est tout ce qui est nécessaire à fournir à la clause
COMMUTATOR
.
Fournir l'information du commutateur pour les opérateurs est critique pour
leur utilisation dans les index et les clauses de jointure parce que cela
permet à l'optimiseur de requête de « retourner » une telle
clause en des formes nécessaires pour différentes types de plan. Par
exemple, regardez la requête avec une clause WHERE comme tab1.x =
tab2.y
, où tab1.x
et
tab2.y
sont d'un type défini par l'utilisateur et
supposez que tab2.y
est indexé. L'optimiseur ne peut
pas générer un parcours d'index sauf s'il détermine comment retourner la
clause en tab2.y = tab1.x
, car le parcours d'index
s'attend à voir la colonne indexée sur la gauche de l'opérateur.
PostgreSQL ne pensera pas
qu'il s'agit d'une transformation valide -- le créateur de
l'opérateur =
doit préciser que c'est valide en
marquant l'opérateur avec l'information du commutateur.
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.
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.
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 < |
scalarlesel pour <= |
scalargtsel pour > |
scalargesel pour >= |
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
, scalarlesel
,
scalargtsel
et scalargesel
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.
Une autre fonction native d'estimation de sélectivité est
matchingsel
, qui fonctionnera pour pratiquement tous
les opérateurs binaires, si les statistiques MCV ou histogramme sont
calculées pour ce type de données en entrée. Son estimation par défaut est
configurée à deux fois l'estimation par défaut utilisé dans
eqsel
, le rendant convenable pour les opérateurs de
comparaison qui sont un peu moins strictque l'égalité. (Vous pouvez appeler
la fonction sous-jacente
generic_restriction_selectivity
en fournissant une
estimation par défaut différente.)
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).
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 < |
scalarlejoinsel pour <= |
scalargtjoinsel pour > |
scalargejoinsel pour >= |
matchingjoinsel for generic matching operators |
areajoinsel pour des comparaisons basées sur une aire 2D |
positionjoinsel pour des comparaisons basées sur des positions 2D |
contjoinsel pour des comparaisons basées sur un appartenance 2D |
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é doit
représenter l'égalité pour certains types de données ou paire de type 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 une certaine forme d'égalité. Dans la plupart des cas, il
est seulement pratique de supporter le hachage pour les opérateurs qui
prennent le même type de données sur chaque côté. Néanmoins, quelque
fois, il est possible de concevoir des fonctions de hachage compatibles
pour deux type de données, voire plus ; c'est-à-dire pour les
fonctions qui généreront les mêmes codes de hachage pour des valeurs
égales même si elles ont des représentations différentes. Par exemple,
il est assez simple d'arranger cette propriété lors du hachage d'entiers
de largeurs différentes.
Pour être marqué hashes
, l'opérateur de jointure doit
apparaître dans une famille 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 famille d'opérateur n'existe pas. Le
système a besoin de la famille 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 créer des fonctions de découpage appropriées avant de
pouvoir créer la famille 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.
Un opérateur joignable par hachage doit avoir un commutateur (lui-même si les types de données des deux opérandes sont identiques, ou un opérateur d'égalité relatif dans le cas contraire) qui apparaît dans la même famille d'opérateur. Si ce n'est pas le cas, des erreurs du planificateur pourraient apparaître quand l'opérateur est utilisé. De plus, une bonne idée (mais pas obligatoire) est qu'une famille d'opérateur de hachage supporte les tupes de données multiples pour fournir des opérateurs d'égalité pour chaque combinaison des types de données ; cela permet une meilleure optimisation.
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.
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).
MERGES
# 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 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.
Pour être marqué MERGES
, l'opérateur de jointure doit
apparaître en tant que membre d'égalité d'une famille opérateur d'index
btree
. Ceci n'est pas forcé quand vous créez l'opérateur puisque, bien
sûr, la famille d'opérateur référente n'existe pas encore. Mais
l'opérateur ne sera pas utilisé pour les jointures de fusion sauf si
une famille d'opérateur correspondante est trouvée. L'option
MERGES
agit en fait comme une aide pour le planificateur
lui indiquant qu'il est intéressant de chercher une famille d'opérateur
correspondant.
Un opérateur joignable par fusion doit avoir un commutateur (lui-même si les types de données des deux opérateurs sont identiques, ou un opérateur d'égalité en relation dans le cas contraire) qui apparaît dans la même famille d'opérateur. Si ce n'est pas le cas, des erreurs du planificateur pourraient apparaître quand l'opérateur est utilisé. De plus, une bonne idée (mais pas obligatoire) est qu'une famille d'opérateur de hachage supporte les tupes de données multiples pour fournir des opérateurs d'égalité pour chaque combinaison des types de données ; cela permet une meilleure optimisation.
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.