PostgreSQL inclut une implémentation de la structure standard d'index btree (multi-way balanced tree) N'importe quel type de données pouvant être trié dans un ordre linéaire clairement défini peut être indexé par un index btree. La seule limitation est qu'une entrée d'index ne peut dépasser approximativement un tiers de page (après la compression TOAST si cela est possible).
Puisque chaque classe d'opérateur btree impose un ordre de tri sur son type de données, les classes d'opérateur btree (ou, en réalité, les familles d'opérateur) ont finies par être utilisées par PostgreSQL comme représentation et connaissance générale des sémantiques de tri. En conséquence, elles ont acquis certaines fonctionnalités qui vont au delà de ce qui serait nécessaire pour simplement supporter les index btree, et des parties du systèmes qui sont éloignées des méthodes d'accès (AM) btree les utilisant.
Comme montré dans Tableau 36.3, une classe
d'opérateur btree doit fournir cinq opérateurs de comparaison,
<
,
<=
,
=
,
>=
et
>
.
On pourrait supposer que <>
devraient également
faire partie de la classe d'opérateur, mais ce n'est pas le cas car cela ne
serait presque jamais utile d'utiliser une clause WHERE
<>
dans une recherche d'index. (Dans certains
cas, le planificateur traite <>
comme s'il était
associé avec une classe d'opérateur btree ; mais il trouve cet
opérateur via le lien du négateur de l'opérateur =
,
plutôt que depuis pg_amop
.)
Quand plusieurs types de données partagent des sémantiques de tri presque identiques, leurs classes d'opérateurs peuvent être regroupées dans une famille d'opérateur. Il est avantageux de procéder ainsi car cela permet au planificateur de faire des déductions quant aux comparaisons entre plusieurs types. Chaque classe d'opérateur au sein d'une famille devrait contenir les opérateurs concernant un seul type (et les fonctions de support associées), tandis que les opérateurs de comparaison inter-types et les fonctions de support sont « libres » dans la famille. Il est recommandé qu'un ensemble complet d'opérateurs inter-types soit inclus dans la famille, afin d'assurer que le planificateur puisse représenter n'importe quelle condition de comparaison qu'il pourrait déduire depuis la transitivité.
Il y a des supposition basiques qu'une famille d'opérateur btree doit satisfaire :
Un opérateur =
doit être une relation
d'équivalence ; c'est-à-dire que pour toutes les valeurs non nulles
A
, B
,
C
du type de données :
A
=
A
est vrai
(loi de réflexivité)
si A
=
B
,
alors B
=
A
(loi de symétrie)
si A
=
B
et B
=
C
,
alors A
=
C
(loi de transitivité)
Un opérateur <
doit être une relation de tri
forte ; c'est-à-dire, pour toutes les valeurs non nulles
A
, B
,
C
:
A
<
A
est faux
(loi d'antiréflexivité)
si A
<
B
et B
<
C
,
alors A
<
C
(loi de transitivité)
De plus, le tri est total ; c'est-à-dire, pour toutes les valeurs
non nulles A
,
B
:
exactement une seule des expressions
A
<
B
, A
=
B
, et
B
<
A
est vraie
(loi de trichotomie)
(Bien entendu, la loi de trichotomie justifie la définition de la fonction de support de comparaison).
Les trois autres opérateurs sont définis avec =
et
<
de manière évidente, et doivent se comporter de
manière cohérentes avec ceux-ci.
Pour une famille d'opérateur supportant plusieurs types de données, les
lois définies auparavant doivent continuer à s'appliquer quand
A
, B
,
C
sont pris de n'importe quel type de données de
la famille. Les lois de transitivité sont les plus délicates à garantir,
car, dans des situations inter-types, elles représentent des déclarations
comme quoi les comportements de deux ou trois différents opérateurs sont
cohérents. Comme exemple, mettre float8
et
numeric
dans la même famille d'opérateur ne fonctionnerait
pas, du moins pas avec les sémantiques actuelles qui définissent que les
valeurs de type numeric
sont converties en float8
pour la comparaison vers un float8
. Du fait de la précision
limitée du type float8
, cela veut dire que des valeurs
numeric
distinctes seraient considérées par la comparaison
comme égales à la même valeur float8
, et par conséquent la loi
de transitivé échouerait.
Une autre exigence pour les familles contenant plusieurs types est que les transtypages implicites ou de coercition binaire qui sont définis entre les types de données inclus dans la famille d'opérateur ne doivent pas changer l'ordre de tri associé.
La raison pour laquelle les index btree nécessitent que ces lois soient vérifiées pour un même type de données devraient être tout à fait claires : sans celles-ci, il n'y a pas d'ordre avec lequel organiser les clés. En outre, les recherches d'index utilisant une clé de comparaison d'un type de données différent nécessitent que la comparaison se comporte sainement à travers deux types de données. Les extensions à trois types de données ou plus au sein d'une famille ne sont pas strictement requis par le mécanisme d'index btree lui-même, mais le planificateur se repose sur eux pour des besoins d'optimisation.
Comme montré dans Tableau 36.9, btree définit une fonction de support obligatoire et quatre facultatives. Les cinq méthodes définies par l'utilisateur sont :
order
Pour chaque combinaison de types de données pour laquelle une famille
d'opérateur btree fournit des opérateurs de comparaison, elle doit
fournir une fonction de support de comparaison inscrite dans
pg_amproc
avec la fonction de support 1 et
amproclefttype
/amprocrighttype
égaux aux types de données gauche et droit pour la comparaison
(c'est-à-dire les même types de données que l'opérateur correspondant a
inscrit dans pg_amop
). La fonction de
comparaison doit prendre en entrée deux valeurs non nulles
A
et B
et
retourner une valeur int32
qui est <
0
, 0
, ou >
0
quand, respectivement A
<
B
,
A
=
B
, ou A
>
B
. Une valeur de
retour NULL est également interdite : toutes les valeurs du type de
données doivent être comparables. Voir
src/backend/access/nbtree/nbtcompare.c
pour plus
d'exemples.
Si les valeurs comparées sont d'un type avec collation, l'identifiant de
collation approprié sera passé à la fonction de support de comparaison,
en utilisant le mécanisme standard
PG_GET_COLLATION()
.
sortsupport
De manière facultative, une famille d'opérateur btree peut fournir une
ou plusieurs fonctions sort support, inscrites
comme fonctions de support numéro 2. Ces fonctions permettent
d'implémenter des comparaisons dans l'optique de tri de manière plus
efficace qu'appeler naivement la fonction de support de comparaison.
Les API impliquées pour cela sont définies dans
src/include/utils/sortsupport.h
.
in_range
De manière facultative, une famille d'opérateur btree peut fournir une
ou plusieurs fonctions de support in_range
inscrites comme fonction de support numéro 3. Celles-ci ne sont pas
utilisées durant les opérations d'index btree ; mais plutôt, elles
étendent les sémantiques de la famille d'opérateur de telle manière
qu'elles puissent supporter les clauses de fenêtrage contenant les types
de limite de cadre RANGE
décalage
PRECEDING
et
RANGE
décalage
FOLLOWING
(voir Section 4.2.8). Fondamentalement, les
informations supplémentaires fournies sont comment additionner et
soustraire une valeur d'un décalage
d'une
manière qui est compatible avec le tri de données de la famille.
Une fonction in_range
doit avoir la signature
in_range(val
type1,base
type1,offset
type2,sub
bool,less
bool) returns bool
val
et base
doivent être du même type, qui est un des types supportés par la famille
d'opérateur (c'est-à-dire un type pour lequel elle fournit un tri).
Cependant, offset
peut être d'un type de
données différent, qui peut par ailleurs ne pas être supporté par la
famille. Un exemple est que la famille time_ops
incluse par défaut fournit une fonction in_range
qui a un offset
de type
interval
. Une famille peut fournir des fonctions
in_range
pour n'importe lesquels des types de
données qu'elle supporte, et un ou plusieurs types
offset
. Chaque fonction
in_range
devrait être inscrite dans
pg_amproc
avec
amproclefttype
égal à type1
et
amprocrighttype
égal à type2
.
Les sémantiques essentielles pour une fonction
in_range
dépendent des deux paramètres de drapeau
booléens. Elle devrait ajouter ou soustraire
base
et offset
,
puis comparer val
au résultat, comme
ceci :
si !
sub
et
!
less
,
renvoyer val
>=
(base
+
offset
)
si !
sub
et less
,
renvoyer val
<=
(base
+
offset
)
si sub
et !
less
,
renvoyer val
>=
(base
-
offset
)
si sub
et less
,
renvoyer val
<=
(base
-
offset
)
Avant de procéder, la fonction devrait vérifier le signe d'
offset
: s'il est inférieur à zéro, lever l'erreur
ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE
(22013)
avec un message d'erreur tel que « taille précédente ou suivante
invalide dans la fonction de fenêtrage ». (Cela est requis par le
standard SQL, bien que des familles d'opérateur non standards pourraient
peut être choisir d'ignorer cette restriction, puisqu'il n'y a pas
vraiment de nécessité de sémantique dans ce cas.) Cette exigence est
déléguée à la fonction in_range
si bien que le code
du moteur n'a pas besoin de comprendre ce que « inférieur à
zéro » signifie pour un type de données particulier.
Une autre attente est que les fonctions in_range
devraient, si applicable, éviter de générer une erreur si
base
+
offset
ou base
-
offset
devait causer un
débordement. Le résultat de comparaison correct peut être déterminé même
si cette valeur devait être en dehors de l'intervalle des valeurs du
type de données. Notez que si le type de données inclut des concepts
tels que « infinity » ou « NaN », des précautions
supplémentaires pourraient être nécessaires pour s'assurer que les
résultats de in_range
soient en accord avec l'ordre
de tri normal de la famille d'opérateur.
Les résultats de la fonction in_range
doivent être
cohérents avec l'ordre de tri imposé par la famille d'opérateur. Pour
être précis, pour n'importe quelles valeurs fixées de
offset
et sub
,
alors :
Si in_range
avec less
=
true est vrai pour certains val1
et base
, il doit être vrai pour chaque
val2
<=
val1
avec le même
base
.
Si in_range
avec less
=
true est faux pour certains val1
et base
, il doit être faux pour chaque
val2
>=
val1
avec le même
base
.
Si in_range
avec less
=
true est vrai pour certains val
et base1
, il doit être vrai pour chaque
base2
>=
base1
avec le même
val
.
Si in_range
avec less
=
true est faux pour certains val
et base1
, il doit être faux pour chaque
base2
<=
base1
avec le même
val
.
Des déclarations similaires avec des conditions inversées continuent à
s'appliquer quand less
= false.
Si le type est trié (type1
) par rapport à une collation,
l'OID de collation approprié sera passé à la fonction
in_range
en utilisant le mécanisme standard
PG_GET_COLLATION()
.
Les fonctions in_range
n'ont pas besoin de gérer
les valeurs en entrée NULL, et typiquement elles seront marquées comme
strict.
equalimage
Une famille d'opérateurs btree facultative peut fournir les fonctions de
support equalimage
(« l'égalité implique une
égalité d'image »), inscrites comme fonctions de support numéro 4.
Ces fonctions permettent au code du moteur de déterminer quand il est
sûr d'appliquer l'optimisation de dédoublonnage btree. Actuellement, les
fonctions equalimage
sont seulement appelées lors
de la construction ou reconstruction d'un index.
Une fonction equalimage
doit avoir comme signature
equalimage(opcintype
oid
) returns bool
La valeur retournée est une information statique relative à une classe
d'opérateur et une collation. Retourner true
indique
que la fonction order
pour la classe d'opérateur
est garantie de retourner seulement 0
(« les
arguments sont égaux ») quand ses arguments
A
et B
sont aussi
interchangeables sans aucune perte d'information sémantique. Ne pas
inscrire une fonction equalimage
ou retourner
false
indique que cette condition ne peut être tenue.
L'argument opcintype
est le
du type de
données que la classe d'opérateur indexe. Ceci est une commodité qui
permet de réutiliser la même fonction pg_type
.oidequalimage
sous-jacente entre plusieurs classes d'opérateurs. Si
opcintype
est un type de données collationné,
l'identifiant de collation appropriée sera passé à la fonction
equalimage
, par le mécanisme standard
PG_GET_COLLATION()
.
Tant que la classe d'opérateur est concernée, retourner
true
indique que le dédoublement est sûr (ou sûr pour
la collation dont l'identifiant a été passé à sa fonction
equalimage
). Cependant, le code du moteur
considérera le dédoublonnage sécurisé pour un index, si
chaque colonne indexée utilise une classe
d'opérateur ayant inscrit une fonction equalimage
,
et si chaque fonction retourne true
par appel.
L'égalité d'image est presque la même condition
qu'une simple égalité bit à bit. Il n'y a qu'une seule et subtile
différence : en indexant un type de données « varlena »,
la représentation sur disque de deux images de données égales peuvent ne
pas être identiques bit à bit, à cause des incohérences lors de
l'application de la compression TOAST sur les données
en entrée. Dans les règles, quand une fonction
equalimage
d'une classe d'opérateur retourne
true
, on peut présumer sans se tromper que la
fonction C datum_image_eq()
correspondra avec la
fonction order
de la classe d'opérateur (sous
réserve que le même identifiant de collation soit passé aux fonctions
equalimage
et order
).
Le code du moteur est fondamentalement incapable de déduire quoi que ce
soit au sujet du statut « l'égalité implique l'égalité
d'image » d'une classe d'opérateur incluse dans une famille de
types de données multiples en se basant sur les détails d'autres classes
d'opérateur de la même famille. Aussi, il n'est pas pertinent pour une
famille d'opérateurs d'inscrire une fonction
equalimage
inter-type, et essayer déclenchera une
error. En effet, le statut de « l'égalité implique l'égalité
d'image » ne dépend pas juste de la sémantique de l'ordre/égalité,
qui est plus ou moins définie au niveau de la famille d'opérateur. En
général, les sémantiques d'un type particulier de données doivent être
considérées séparément.
La convention suivie par les classes d'opérateur incluses avec la
distribution principale PostgreSQL est
d'inscrire une fonction générique equalimage
. La
plupart des classes d'opérateur inscrivent
btequalimage()
, qui indique que le dédoublonnage
est sécurisé sans conditions. Les classes d'opérateur pour les types de
données collationnés comme text
inscrivent
btvarstrequalimage()
, qui indique que le
dédoublonnage est sécurisé avec les collations déterministes. La bonne
pratique pour une extension tierce est d'inscrire leur propre fonction
personnalisée pour garder le contrôle.
options
En option, une famille d'opérateur B-tree peut fournir des fonctions de
support des options
(« options spécifiques à
la classe d'opérateur »), enregistrées sous le numéro 5 des
fonctions de support. Ces fonctions définissent un ensemble de
paramètres visibles à l'utilisateur et contrôlant le comportement de la
classe d'opérateur.
Une fonction de support options
doit avoir cette
signature
options(relopts
local_relopts *
) returns void
La fonction se voit fournie un pointeur vers une structure
local_relopts
qui doit être remplie avec un
ensemble d'options spécifiques à une classe d'opérateur. Les options
sont accessibles à partir des autres fonctions de support en utilisant
les macros PG_HAS_OPCLASS_OPTIONS()
et
PG_GET_OPCLASS_OPTIONS()
.
Actuellement, aucune classe d'opérateur B-Tree n'a de fonction de
support options
. B-tree n'autorise pas une
représentation flexible des clés comme GiST, SP-GiST, GIN et BRIN le
font. Donc, options
n'a probablement pas beaucoup
d'intérêt pour la méthode d'accès aux index B-tree actuellement.
Néanmoins, cette fonction de support a été ajouté au B-tree par
cohérence, et trouvera probablement son utilité lors des prochaines
évolutions du B-tree dans PostgreSQL.
Cette section couvre les détails de l'implémentation des index B-Tree qui
peuvent être utiles pour les utilisateurs avancés. Voir
src/backend/access/nbtree/README
dans les sources de
la distribution pour une description plus en détails de l'implémentation du
B-Tree.
Les index B-Tree de PostgreSQL sont des structures arborescentes multi-niveaux, où chaque niveau de l'arbre peut être utilisé comme une liste doublement chaînée de pages. Une seule métapage est stockée à une position fixe au début du premier segment de fichier de l'index. Toutes les autres pages sont soit des pages feuilles, soit des pages internes. Les pages feuilles sont les pages de plus bas niveau de l'arbre. Tous les autres niveaux consistent en des pages internes. Chaque page feuille contient des tuples qui pointent sur les enregistrements en table. Chaque page interne contient des tuples qui pointent vers le niveau inférieur suivant dans l'arbre. En général, 99% des pages sont des pages feuilles. Aussi bien les pages internes que les pages feuilles emploient le format standard de page décrit dans Section 65.6.
Des nouvelles pages feuilles sont ajoutées à un index B-Tree quand un tuple entrant ne peut pas tenir dans une page feuille existante. Une opération de fractionnement de page a alors lieu et libère de la place pour les éléments qui appartiennent à la page surchargée en déplaçant une portion de ces éléments dans une nouvelle page. Le fractionnement de page doit aussi insérer un lien descendant vers la nouvelle page dans la page parente, ce qui peut causer à son tour le fractionnement du parent. Le fractionnement de page se produit en « cascade vers les niveaux supérieurs » de façon récursive. Si la page racine ne peut finalement pas porter le lien descendant, une opération de fractionnement de page racine se produit. Elle ajoute un nouveau niveau dans la structure arborescente en créant une nouvelle page racine un niveau au dessus de la page racine d'origine.
Les index B-Tree n'ont pas directement connaissance que sous MVCC, il peut
y avoir plusieurs versions existantes de la même ligne logique d'une
table ; pour un index, chaque ligne est un objet indépendant qui a
besoin de sa propre entrée d'index. Le « renouvellement des
versions » (version churn dans la
version originale) des lignes peut parfois s'accumuler et nuire à la
latence et au débit des requêtes. Ceci se produit typiquement avec des
charges élevées en UPDATE
où la plupart des mises à
jour individuelles ne peuvent appliquer
l'optimisation HOT.
Changer la valeur de seulement une colonne
couverte par un index durant un UPDATE
nécessite
toujours un nouveau ensemble d'entrées
d'index -- un pour chaque et tous les index sur
la table. Notons en particulier que cela inclut les index qui ne sont pas
« modifiés logiquement » par la commande
UPDATE
. Tous les index nécessiteront une entrée
d'index physique successeur qui pointe vers la dernière version en table.
Chaque nouvelle entrée à l'intérieur de chaque index aura besoin, en
général, de coexister avec l'entrée « modifiée » originale
pour une courte période de temps (typiquement jusque peu après que la
transaction de l'UPDATE
soit validée).
Les index B-Tree suppriment de manière incrémentielle les entrées d'index
de renouvellement de version en effectuant des passes de
suppression ascendante de l'index. Chaque passe de
suppression est déclenchée en réaction à un « fractionnement de page
de renouvellement de version » anticipée. Ceci se produit avec les
index qui ne sont pas modifiés logiquement par les déclarations
UPDATE
, dans lesquels des accumulations concentrées de
versions obsolètes dans des blocs particuliers auraient lieu autrement.
Un fractionnement de bloc sera normalement évité, bien qu'il est possible
que certains niveaux d'implémentation d'heuristique échoueront même à
identifier et à supprimer une ligne à renouveller
(garbage tuple dans la version originale)
d'index (dans ce cas, un fractionnement de bloc ou une passe de
dédoublement résout le problème de l'entrée d'une nouvelle ligne qui ne
rentre pas dans le bloc feuille). Le pire nombre de versions que
n'importe quel parcours d'index doit traverser (pour n'importe quel
enregistrement unique logique) est un contributeur important pour la
réactivité et le débit globaux du système. Une passe de suppression
ascendante d'index cible les lignes à renouveller supposées dans un bloc
feuille unique par des distinctions qualitatives
impliquant enregistrements logiques et versions. Ceci diffère des
nettoyages d'index « descendants » effectués par les processus
de l'autovacuum, qui sont déclenchés quand certains seuils
quantitatifs au niveau table sont dépassés
(voir Section 24.1.6).
Les opérations de suppression effectuées à l'intérieur des index B-Tree
ne sont pas toutes des opérations de suppression ascendantes. Il y a une
catégorie de suppression d'entrées d'index : la
suppression d'entrée d'index simple
(simple index tuple deletion en version
originale). C'est une opération de maintenance différée qui supprime les
entrées d'index en toute sécurité (ceux dont le bit
LP_DEAD
de son élément identifiant est déjà affecté).
Comme les suppressions ascendantes d'index, la suppression d'index
simple a lieu au moment où un fractionnement de bloc est attendu comme
moyen d'éviter ce fractionnement.
La suppression simple est opportuniste dans le sens où elle peut
seulement s'effectuer quand les parcours récents d'index mettent à jour
les bits LP_DEAD
des éléments affectés lors d'une
passe. Avant PostgreSQL 14, la seule
catégorie de suppression B-Tree était la suppression simple. Les
principales différences entre elle et les suppressions ascendantes sont
que seule la première est dirigée de manière opportuniste par l'activité
des passes de parcours d'index, tandis que la nouvelle ne cible
spécifiquement que le renouvellement de version des
UPDATE
qui ne modifient pas logiquement les colonnes
indexées.
Les suppressions ascendantes d'index effectuent la grande majorité des
nettoyages des entrées à renouveller d'index pour certains index et
certaines charges de travail. C'est le cas pour les index B-Tree sujets à
un renouvellement de version significatif par les
UPDATE
qui ne modifient logiquement que rarement ou
jamais les colonnes couvertes par l'index. La valeur moyenne et la pire
valeur possible de versions par enregistrement logique peuvent être
maintenues basses grâce aux passes incrémentales de suppression ciblée.
Il est possible que la taille sur disque de certains index n'augmentera
jamais même d'un simple bloc malgré un renouvellement de version
continu par les UPDATE
. Même si
cela était le cas, un « nettoyage » exhaustif par une
opération VACUUM
(typiquement exécutée par un
processus autovacuum), sera éventuellement requis comme une partie de
nettoyage collectif de la table et chacun de ses
index.
À la différence du VACUUM
, la suppression ascendante
d'index ne fournit pas de solides garanties pour déterminer quel peut
être la plus ancienne entrée à renouveller dans l'index. Aucun index ne
peut permettre de conserver des entrées d'index « à renouvellement
flottant » qui seront morts avant le moment de conservation limite
partagé collectivement par la table et tous ses index. Cette constante
fondamentale au niveau table implique qu'il est sans danger de recycler
les TID d'une table. C'est ainsi qu'il est possible
pour les enregistrements logiques distincts de réutiliser les mêmes
TID dans une table au cours du temps (bien que cela ne
peut jamais se produire avec deux enregistrements logiques dont
l'espérance de vie couvre le même cycle VACUUM
).
Un doublon est un tuple de page feuille (un tuple qui pointe sur un enregistrement en table) où toutes les valeurs des colonnes clés de l'index correspondent aux valeurs de colonnes respectives d'au moins un autre tuple de page feuille dans le même index. Les tuples doublons sont assez communs en pratique. Les index B-Tree peuvent utiliser une représentation spéciale gérant efficacement l'espace pour les doublons lorsqu'une fonctionnalité est activée : le dédoublement.
Le dédoublement fonctionne en fusionnant périodiquement les groupes d'enregistrements doublons ensemble, formant une liste d'affectation unique pour chaque groupe. Le ou les valeurs de colonnes clés n'apparaissent qu'une fois dans cette représentation. Elles sont suivies par un tableau trié des TID pointant sur les lignes en table. Ceci réduit significativement la taille de stockage des index où chaque valeur (ou chaque combinaison distincte de valeur de colonne) apparait plusieurs fois en moyenne. La latence des requêtes peut sensiblement diminuer. Le débit général des requêtes peut augmenter sensiblement. Le coût supplémentaire de la routine de vacuum d'index peut aussi être notablement réduite.
Le dédoublement B-Tree est tout aussi efficace avec des
« duplicats » contenant une valeur NULL, même si les valeurs
NULL ne sont jamais égales d'après l'opérateur =
de
toute classe d'opérateur B-Tree. Pour toute implémentation comprenant la
structure disque B-Tree, NULL est simplement une autre valeur du domaine
des valeurs indexées.
Le processus de dédoublement se déroule avec le moins d'effort possible, quand un nouveau élément est inséré et ne peut rentrer dans une page feuille existante, mais seulement quand la suppression d'entrée d'index ne peut pas libérer suffisamment d'espace pour le nouvel élément (typiquement la suppression est brièvement considérée puis ignorée). Contrairement à la liste chainée d'enregistrements GIN, la liste chainée d'enregistrements B-Tree n'a pas besoin de s'étendre à chaque fois qu'un nouveau doublon est inséré ; ils sont simplement une représentation physique différente du contenu logique de la page feuille. Ce concept priorise l'uniformité des performances sur des charges de travail mixte en lecture-écriture. La plupart des applications clientes verront un bénéfice modéré sur les performances en utilisant le dédoublement. Le dédoublement est activé par défaut.
CREATE INDEX
et REINDEX
appliquent
la déduplication pour créer les listes de lignes, bien que la stratégie
utilisée soit un peu différente. Chaque groupe de lignes ordinaires
dupliquées rencontré dans l'entrée triée prise à partir de la table est
assemblé en une liste avant d'être ajouté à la page
feuille en cours. Les listes individuelles sont assemblées avec autant de
TID que possible. Les pages feuilles sont écrites de la
façon habituelle, sans passe de déduplication séparée. Cette stratégie
convient bien à CREATE INDEX
et
REINDEX
car ce sont des opérations de groupe en lot
unique.
Les charges de travail majoritaires en écriture et qui ne bénéficient pas
du dédoublement du fait qu'il y a peu ou pas de doublons dans les index,
encoureront une pénalité stable et légère de performance (sauf si le
dédoublement est explicitement désactivé). Le paramètre de stockage
deduplicate_items
peut être utilisé pour désactiver le
dédoublement au niveau de chaque index. Il n'y a jamais de pénalité de
performance avec des charges de travail en lecture seule, puisque la
lecture de liste chainée des tuples est au moins aussi efficace que la
lecture de la représentation standard des tuples. Désactiver le
dédoublement n'est en général pas utile.
Il est parfois possible pour des index uniques (autant que pour des contraintes uniques) d'utiliser le dédoublement. Cela permet aux blocs feuilles d'« absorber » temporairement les doublons supplémentaires des renouvellement de version. Le dédoublement des index uniques augmente les suppressions ascendantes d'index, spécialement dans les cas où une longue transaction garde un instantané qui bloque la collecte des éléments à nettoyer. Le but est de gagner du temps pour que la stratégie de suppression ascendante d'index devienne encore efficace. Retarder les fractionnements de blocs jusqu'à ce qu'une transaction longue finisse naturellement peut permettre à une passe de suppression ascendante de réussir là où une passe de suppression précédente a échoué.
Une heuristique particulière est utilisée pour déterminer si une passe de
dédoublement peut prendre place dans un index unique. Elle peut souvent
directement passer au fractionnement de page feuille, évitant ainsi une
pénalité de performance par gaspillage de cycles de dédoublement
inutiles. Si vous êtes préoccupés par le coût additionnel du
dédoublement, veuillez considérer le paramètre deduplicate_items
= off
de manière sélective. Conserver le dédoublement activé
par index distinct n'a guère d'impacts uniques.
Le dédoublement ne peut pas être utilisé dans tous les cas à cause des
restrictions au niveau de l'implémentation. L'innocuité du dédoublement
est déterminé quand CREATE INDEX
ou
REINDEX
est exécutée.
Notez que le dédoublement est considéré comme non sécurisé et ne peut être utilisé dans les cas suivants qui impliquent des différences significatives au niveau sémantique parmi des données identiques :
text
, varchar
, et char
ne
peuvent être dédoublonnés quand une collation non
déterministique est utilisée. La différence de casse et des
accents doit être préservée parmi les données égales.
numeric
ne peut pas utilisé le dédoublonnement. La
précision des nombres doit être préservée parmi les données identiques.
jsonb
ne peut être dédoublonné, depuis que la classe
d'opérateur B-Tree pour le type jsonb
utilise en interne
un type numeric
.
float4
et float8
ne peuvent être
dédoublonnés. Ces types ont une représentation distincte pour
-0
et 0
, qui sont cependant
considérés égaux. Cette différence doit être préservée.
Une autre restriction au niveau de l'implémentation existe et pourra être levée dans une version future de PostgreSQL:
Les types conteneur (tel que les types compostes, tableaux ou intervalle) ne peuvent être dédoublonnés.
Une autre restriction au niveau de l'implémentation s'applique quel que soient les classes d'opérateur ou collations employées :
Les index INCLUDE
ne peuvent pas être dédoublonnés.