Aujourd'hui, nous allons parler des fonctionnalités les plus importantes de PostgreSQL 11. Pourquoi seulement à leur sujet - parce que tout le monde n'a pas besoin de certaines fonctionnalités, nous avons donc choisi les plus populaires.
Table des matières

Compilation Jit
PostgreSQL a enfin introduit la compilation JIT, c'est-Ă -dire la compilation de requĂŞtes en code binaire. Pour ce faire, compilez PostgreSQL avec le support de la compilation JIT
(Compile time 1 (--with-llvm))
. Dans le même temps, la machine doit avoir une version LLVM non inférieure à 3.9.
Qu'est-ce qui peut accélérer JIT?
- Requêtes avec la clause WHERE, c'est-à -dire tout ce qui vient après ce mot-clé. Ce n'est pas toujours nécessaire, mais l'occasion est utile.
- Calcul de la liste cible: dans la terminologie PostgreSQL, c'est tout ce qui est entre select et from.
- Agrégats.
- Convertir des enregistrements d'une vue à une autre (Projection). Par exemple, lorsque vous appliquez la jointure à deux tables, le résultat est un nouveau tuple contenant des champs des deux tables.
- Tuple se déformant. L'un des problèmes de toute base de données, au moins minuscule, relationnel, est de savoir comment obtenir un champ à partir d'un enregistrement sur disque. Après tout, il peut y avoir null, ils ont des enregistrements différents et en général, ce n'est pas l'opération la moins chère.
Compile time 2
signifie que JIT n'est pas utilisé. Dans PostgreSQL, il y a un moment de planification des requêtes, lorsque le système décide ce qui vaut JIT et ce qui ne l'est pas. À ce stade, il exécute les JIT, puis l'exécuteur s'exécute tel quel.
JIT est rendu enfichable. Par défaut, cela fonctionne avec LLVM, mais vous pouvez connecter n'importe quel autre JIT.

Si vous avez compilé PostgreSQL sans prise en charge JIT, la toute première configuration ne fonctionne pas. Options mises en œuvre pour les développeurs, il existe des paramètres pour les fonctions JIT individuelles.
Le prochain point subtil est lié à jit_above_cost. JIT lui-même n'est pas gratuit. Par conséquent, PostgreSQL utilise par défaut l'optimisation JIT si le coût d'une requête dépasse 100 000 perroquets conditionnels, dans lesquels l'explication, l'analyse, etc. sont mesurées. Cette valeur est choisie au hasard, alors faites-y attention.
Mais pas toujours après avoir activé JIT, tout fonctionne immédiatement. Habituellement, tout le monde commence à expérimenter avec JIT en utilisant le tableau select * from où la requête id = 600 et ils échouent. Probablement, il est nécessaire de compliquer en quelque sorte la demande, puis tout le monde génère une base de données géante et compose la demande. En conséquence, PostgreSQL repose sur les capacités du disque, il lui manque la capacité des tampons et caches partagés.
Voici un exemple complètement abstrait. Il y a 9 champs nuls avec des fréquences différentes, de sorte que vous pouvez remarquer l'effet de la déformation du tuple.
select i as x1,
case when i % 2 = 0 then i else null end as x2,
case when i % 3 = 0 then i else null end as x3,
case when i % 4 = 0 then i else null end as x4,
case when i % 5 = 0 then i else null end as x5,
case when i % 6 = 0 then i else null end as x6,
case when i % 7 = 0 then i else null end as x7,
case when i % 8 = 0 then i else null end as x8,
case when i % 9 = 0 then i else null end as x9
into t
from generate_series(0, 10000000) i;
vacuum t;
analyze t;
PostgreSQL a beaucoup de possibilités, et pour voir les avantages de JIT, désactivez les deux premières lignes afin de ne pas interférer et réinitialisez les seuils.
set max_parallel_workers=0;
set max_parallel_workers_per_gather=0;
set jit_above_cost=0;
set jit_inline_above_cost=0;
set jit_optimize_above_cost=0;
Voici la demande elle-mĂŞme:
set jit=off;
explain analyze
select count(*) from t where
sqrt(pow(x9, 2) + pow(x8,2)) < 10000;
set jit=on;
explain analyze
select count(*) from t where
sqrt(pow(x9, 2) + pow(x8,2)) < 10000;
Et voici le résultat:
Planning Time: 0.71 ms
Execution Time: 1986.323 ms
VS
Planning Time: 0.060 ms
JIT:
Functions: 4
Generation Time: 0.911 ms
Inlining: true
Inlining Time: 23.876 ms
Optimization: true
Optimization Time: 41.399 ms
Emission Time: 21.856 ms
Execution Time: 949.112 ms
JIT a contribué à accélérer la demande de moitié. La planification du temps est à peu près la même chose, mais c'est probablement le résultat de la mise en cache de PostgreSQL, alors ignorez-le.
En résumé, il a fallu environ 80 ms à la compilation JIT. Pourquoi JIT n'est-il pas gratuit? Avant d'exécuter la demande, vous devez la compiler, ce qui prend également du temps. Et trois ordres de grandeur de plus que la planification. Pas un plaisir cher, mais il est payant en raison de la durée de l'exécution.
De cette façon, vous pouvez utiliser JIT, bien que ce ne soit pas toujours bénéfique.
Partitionnement
Si vous avez fait attention au partitionnement dans PostgreSQL, vous avez probablement remarqué qu'il a été fait là -bas pour le show. La situation s'est légèrement améliorée dans la version 10, lorsqu'une déclaration déclarative des partitions (sections) est apparue. En revanche, tout est resté le même à l'intérieur et a fonctionné à peu près de la même manière que dans les versions précédentes, c'est-à -dire mauvais.
À bien des égards, ce problème a été résolu par le module pg_pathman, qui a permis de travailler avec des sections et de les couper au moment optimal au moment de l'exécution.
Dans la version 11, le partitionnement est grandement amélioré:
- Tout d'abord, la table de partition peut avoir une clé primaire, qui doit inclure la clé de partition. En fait, il s'agit soit d'une clé semi-primaire, soit d'une demi-clé primaire. Malheureusement, vous ne pouvez pas y créer de clé étrangère. J'espère que cela sera corrigé à l'avenir.
- De plus, il est désormais possible de partitionner non seulement par plage, mais également par liste et par hachage. Le hachage est assez primitif, le reste de l'expression est pris pour lui.
- Lors de la mise à jour, la ligne se déplace entre les sections. Auparavant, vous deviez écrire un déclencheur, mais maintenant cela se fait automatiquement.
La grande question est: combien de sections puis-je avoir? Honnêtement, avec un grand nombre de sections (des milliers et des dizaines de milliers), la fonctionnalité ne fonctionne pas bien. Pg_pathman fait mieux.
Également créé des sections par défaut. Encore une fois, dans pg_pathman, vous pouvez créer automatiquement des sections, ce qui est plus pratique. Ici, tout ce qui n'a pu être poussé quelque part tombe dans la section. Si dans un vrai système pour faire cela par défaut, alors après un certain temps, vous obtenez un tel gâchis, que vous tourmentez pour ratisser.
PostgreSQL 11 est désormais en mesure d'optimiser le partitionnement si deux tables sont jointes par une clé de partition et que les schémas de partitionnement correspondent. Ceci est contrôlé par un paramètre spécial, qui est désactivé par défaut.
Vous pouvez calculer des agrégats pour chaque section séparément, puis additionner. Enfin, vous pouvez créer un index sur la table partitionnée parente, puis des index locaux sur toutes les tables qui y sont connectées seront créés.
Dans la section «Quoi de neuf», une chose merveilleuse est mentionnée - la possibilité de jeter des sections lors de l'exécution d'une demande. Vérifions comment cela fonctionne. Le résultat est un tel tableau:

Nous faisons un type et un tableau de deux colonnes avec une clé primaire, avec une grande colonne série, insérons les données. Nous créons la deuxième table, qui sera partitionnée et sera une copie de la première. Ajoutez la clé primaire à la table partitionnée.

Le tableau comprendra deux types d'entrées: «nounous femmes» et «conducteurs hommes». Et il y aura une conductrice. Nous faisons deux sections, divisons par liste, ajoutons la clé primaire et insérons toutes les données de la table dans laquelle tout cela est généré. Le résultat était complètement inintéressant:

Faites attention à la demande. Nous sélectionnons tout dans une table non partitionnée, nous nous connectons à une table partitionnée. Nous prenons un petit morceau et choisissons un seul type, ils en passent par un. Nous indiquons que la colonne oss doit avoir une valeur. Il s'avère qu'une sélection de pilotes solides.
Lors de l'exécution, nous désactivons spécifiquement la parallélisation, car PostgreSQL 11 par défaut parallélise très activement les requêtes plus ou moins complexes. Si nous regardons le plan d'exécution (expliquer l'analyse), alors on peut voir que le système a ajouté les données dans les deux sections: dans la nounou et dans les chauffeurs, bien que les nounous n'étaient pas là . Il n'y a eu aucun appel au tampon. Temps passé, condition utilisée, bien que PostgreSQL puisse tout comprendre. Autrement dit, la déclaration d'élimination de partition ne fonctionne pas tout de suite. Peut-être que dans les versions suivantes, cela sera corrigé. Dans ce cas, le module pg_pathman dans ce cas fonctionne sans problème.
Indices
- Optimisation des enchères de manière monotone, c'est-à -dire b-tree. Tout le monde sait que lorsque vous insérez des données à croissance monotone, cela ne s'avère pas très rapide. Maintenant, PostgreSQL est capable de mettre en cache la page de fin d'une manière spéciale et de ne pas aller de la racine à l'insertion. Cela accélère considérablement le travail.
- PostgreSQL 10 a permis d'utiliser un index de hachage car il a commencé à utiliser WAL (écriture anticipée du journal). Auparavant, nous avons obtenu la valeur, déverrouillé la page, renvoyé la valeur. Pour la valeur suivante, vous deviez à nouveau bloquer la page, revenir, déverrouiller, etc. Maintenant, le hachage est devenu beaucoup plus rapide. Il vous permet de bloquer une page à la fois pour récupérer un enregistrement à partir d'un index de hachage, retourner toutes les valeurs à partir de là et le déverrouiller. Maintenant, il est implémenté pour HASH, GiST et GIN. À l'avenir, cela sera probablement implémenté pour SP-GiST. Mais pour BRIN avec sa logique min / max cela ne peut pas être fait en principe.
- Si vous aviez l'habitude de créer des index fonctionnels, la mise à jour HOT (Heap Only Tuple) était effectivement désactivée. Lorsqu'un enregistrement est mis à jour dans PostgreSQL, une nouvelle copie est réellement créée, ce qui nécessite de coller dans tous les index qui se trouvent dans la table afin que la nouvelle valeur pointe vers le nouveau tuple. Une telle optimisation a été mise en œuvre depuis longtemps: si la mise à jour ne change pas les champs qui ne sont pas inclus dans les index, et qu'il y a de l'espace libre sur la même page, alors les index ne sont pas mis à jour, et dans l'ancienne version de tuple, un pointeur vers la nouvelle version est placé. Cela vous permet de réduire quelque peu la gravité du problème avec les mises à jour. Cependant, une telle optimisation ne fonctionnait pas du tout si vous aviez des index fonctionnels. Dans PostgreSQL 11, il a commencé à fonctionner. Si vous avez créé un index fonctionnel et mis à jour un tuple qui ne change pas ce dont dépend l'index fonctionnel, la mise à jour HOT fonctionnera.
Indices de couverture
Cette fonctionnalité a été implémentée par PostgresPro il y a trois ans, et pendant tout ce temps, PostgreSQL a essayé de l'ajouter. Les index de couverture signifient que vous pouvez ajouter des colonnes supplémentaires à l'index unique, directement dans le tuple d'index.
Pourquoi? Tout le monde aime la numérisation indexée uniquement pour son travail rapide. Pour cela, des indices «couvrant» conditionnellement sont construits:

Mais en même temps, vous devez maintenir l'unicité. Par conséquent, deux index sont en cours de construction, étroits et larges.
L'inconvénient est que lorsque vous appliquez le vide, l'insertion ou la mise à jour à une table, vous devez mettre à jour les deux index. L'insertion dans un index est donc une opération lente. Et l'indice de couverture ne permettra de gérer qu'un seul indice.
Certes, il a certaines limites. Plus précisément, les avantages qui peuvent ne pas être immédiatement compris. Les colonnes c et d du premier index de création ne doivent pas nécessairement être des types scalaires pour lesquels un index b-tree est défini. Autrement dit, ils n'ont pas nécessairement une comparaison plus ou moins. Il peut s'agir de points ou de polygones. La seule chose est que le tuple doit être inférieur à 2,7 Kb, car il n'y a pas de grillage dans l'index, mais vous pouvez rentrer dans ce qui ne peut pas être comparé.
Cependant, à l'intérieur de l'index avec ces colonnes couvertes garanties, aucun calcul n'est effectué lors de la recherche. Cela devrait être fait par un filtre qui se trouve au-dessus de l'index. D'une part, pourquoi ne pas le calculer à l'intérieur de l'index, d'autre part, il s'agit d'un appel de fonction supplémentaire. Mais tout n'est pas aussi effrayant qu'il n'y paraît.
Eh bien, en plus, vous pouvez ajouter ces colonnes couvertes à la clé primaire.
SP GiST
Peu de gens utilisent cet indice car il est assez spécifique. Néanmoins, il est devenu possible de stocker en elle pas tout à fait ce qui a été inséré. Cela fait référence à l'index avec perte, la compression. Prenons l'exemple des polygones. Au lieu de cela, une boîte englobante est placée dans l'index, c'est-à -dire le rectangle minimum qui contient le polygone souhaité. Dans ce cas, nous représentons le rectangle comme un point dans un espace à quatre dimensions, puis nous travaillons avec le quad3 classique, dans un espace à quatre dimensions.
Le SP-GiST a également introduit l'opération "recherche de préfixe". Il retourne vrai si une ligne est le préfixe d'une autre. Ils l'ont présenté non seulement comme ça, mais pour le bien d'une telle demande avec le support de SP-GiST.
SELECT * FROM table WHERE c ^@ „abc“
Dans b-tree, il y a une limite de 2,7 Ko par ligne, mais pas SP-GiST. Certes, PostgreSQL a une limitation: une seule valeur ne peut pas dépasser 1 Go.
Performances
- Un scan d'index bitmap uniquement est apparu . Il fonctionne de la même manière que l'analyse d'index classique, sauf qu'il ne peut garantir aucune commande. Par conséquent, il n'est applicable que pour certains agrégats comme count (*), car le bitmap n'est pas en mesure de transférer des champs de l'index vers l'exécuteur. Il ne peut que signaler le fait d'un dossier qui remplit les conditions.
- La prochaine innovation est la mise à jour de la carte de l'espace libre lors de l'application du vide . Malheureusement, aucun des développeurs de systèmes travaillant avec PostgreSQL ne pense qu'il est nécessaire de supprimer à la fin du tableau, sinon des trous, de l'espace non alloué apparaissent. Pour suivre cela, nous avons implémenté FSM, ce qui nous permet de ne pas agrandir la table, mais d'insérer le tuple dans les vides. Auparavant, cela se faisait avec le vide, mais à la fin. Et maintenant, le vide est capable de le faire dans le processus, et dans les systèmes fortement chargés, il aide à garder la taille de la table sous contrôle.
- Possibilité d'ignorer l'analyse d'index lors de l'exécution sous vide . Le fait est que tous les index PostgreSQL, selon la théorie des bases de données, sont appelés secondaires. Cela signifie que les index sont stockés loin de la table; les pointeurs y mènent. Le scan d'index uniquement vous permet de ne pas faire ce saut sur des pointeurs, mais de le prendre directement depuis l'index. Mais le vide, qui supprime les enregistrements, ne peut pas les consulter dans l'index et décider de les supprimer ou non, simplement parce qu'il n'y a pas de telles données dans l'index. Par conséquent, le vide est toujours effectué en deux passes. Tout d'abord, il parcourt la table et découvre ce qu'il doit supprimer. Ensuite, il va aux index attachés à cette table, supprime les enregistrements qui se réfèrent aux trouvés, revient à la table et supprime ce qu'il allait faire. Et l'étape de passage aux indices n'est pas toujours requise.
Si, depuis le dernier vide, il n'y a pas eu de suppression ou de mise à jour, vous n'avez aucun enregistrement mort, vous n'avez pas besoin de les supprimer. Dans ce cas, vous ne pouvez pas accéder à l'index. Il existe des subtilités supplémentaires, b-tree ne supprime pas ses pages immédiatement, mais en deux passes. Par conséquent, si vous avez supprimé un grand nombre de données dans le tableau, vous devez effectuer un vide. Mais si vous voulez libérer de l'espace dans les indices, passez l'aspirateur deux fois.
Quelqu'un sera surpris, quel est ce tableau dans lequel il n'y a pas eu de suppression ou de mise à jour? En fait, beaucoup y font face, ne pense tout simplement pas. Ce ne sont que des tableaux à ajouter, où, par exemple, des journaux sont ajoutés. Chez eux, l'enlèvement est extrêmement rare. Et cela économise considérablement la durée de vide / autovacuum, réduit la charge sur le disque, l'utilisation de caches et ainsi de suite. - Engagez simultanément des transactions concurrentielles . Ce n'est pas une innovation, mais une amélioration. Maintenant, PostgreSQL détecte qu'il va valider maintenant et retarde la validation de la transaction en cours, attendant le reste des validations. Veuillez noter que cette fonction a peu d'effet si vous avez un petit serveur avec 2 à 4 cœurs.
- postgres_fdw (wrappers de données étrangères) . FDW est un moyen de connecter une source de données externe pour qu'elle ressemble à un véritable post-Congrès. postgres_fdw vous permet de connecter une table d'une instance voisine à votre instance, et elle ressemblera presque à une vraie. Désormais, l'une des restrictions de mise à jour et de suppression a été supprimée. PostgreSQL peut souvent deviner que vous devez envoyer des données brutes. La façon d'exécuter la demande de jointure est assez simple: nous l'exécutons sur notre machine, nous retirons la table de l'instance à l'aide de FDW, découvrons la clé primaire id que nous devons supprimer, puis appliquons la mise à jour et / ou la suppression, c'est-à -dire les données que nous allons et venons . Maintenant, c'est possible. Bien sûr, si les tables sont sur des machines différentes, ce n'est pas si facile, mais FDW vous permet de faire effectuer des opérations à la machine distante, et nous avons juste attendu.
- toast_tuple_target . Il y a des situations où les données dépassent légèrement les limites après lesquelles il est nécessaire de griller, mais en même temps, le grillage de ces valeurs n'est pas toujours agréable. Supposons que vous ayez une limite de 90 octets et que vous en ayez besoin de 100. Vous devez démarrer le toast pour 10 octets, les ajouter séparément, puis lorsque vous sélectionnez ce champ, vous devez accéder à l'index de toast, savoir où se trouvent les données nécessaires, aller à la table de toasts, recueillir et donner.
Maintenant, avec l'aide d'un réglage fin, vous pouvez modifier ce comportement pour la base de données entière ou une table distincte afin que ces petites sorties ne nécessitent pas l'utilisation de toast. Mais vous devez comprendre ce que vous faites, sans cela, rien ne fonctionnera.
WAL
- WAL (Write ahead log) est un journal d'écriture anticipée. La taille du segment WAL est désormais définie dans initdb. Dieu merci, pas lors de la compilation.
- La logique a également changé. Auparavant, l'ensemble des segments WAL était enregistré à partir de l'avant-dernier point de contrôle, et maintenant à partir du dernier. Cela peut réduire considérablement la quantité de données stockées. Mais si vous avez une base de données de 1 To et TPS = 1, c'est-à -dire une demande par seconde, alors vous ne verrez pas la différence.
Sauvegarde et réplication
- Tronquer est apparu dans la réplication logique . C'était la dernière des opérations DML qui ne se reflétait pas dans la réplication logique. Maintenant réfléchi.
- Un message sur la préparation est apparu dans la réplication logique . Vous pouvez maintenant intercepter la transaction de préparation, une validation en deux phases dans la réplication logique. Ceci est mis en œuvre pour la construction de clusters - hétérogènes, homogènes, éclatés et non ombrés, multimaître et ainsi de suite.
- Exception des tables temporaires et non enregistrées de pg_basebackup . Beaucoup se sont plaints que pg_basebackup inclut les tables listées. Et en les excluant, nous réduisons la taille de la sauvegarde. Mais à condition que vous utilisiez des tables temporaires et non enregistrées, sinon cette option vous sera inutile.
- Contrôle Checksumma dans la réplication en streaming (pour les tables) . Cela vous permet de comprendre ce qui est arrivé à votre réplique. Jusqu'à présent, la fonction n'est implémentée que pour les tables.
- Il y a eu une promotion des positions des emplacements de réplication . Comme toujours, vous ne pouvez avancer que vers l'arrière, uniquement s'il y a un WAL. De plus, vous devez très bien comprendre ce que vous en faites et pourquoi. À mon avis, il s'agit davantage d'une option de développement, mais ceux qui utilisent la réplication logique pour certaines applications exotiques peuvent en profiter.
Pour dba
- Modifier la table, ajouter une colonne, non null par défaut X , écrire la table entière. Il y a un petit supplément pour cela: la valeur par défaut est stockée séparément. Si vous choisissez un tuple et avez besoin de cette colonne, PostgreSQL est alors obligé de suivre un chemin de codage supplémentaire pour extraire une valeur temporaire, la remplacer par tuple et vous la donner. Néanmoins, on peut vivre avec.
- Vide / analyse . Auparavant, vous ne pouviez appliquer le vide ou analyser qu'à une base de données entière ou à une seule table. Maintenant, il est possible de le faire sur plusieurs tables, avec une seule commande.
Exécution parallèle
- Construction parallèle d'index b-tree . Dans la version 11, il est devenu possible d'intégrer des index b-tree dans plusieurs travailleurs. Si vous avez une très bonne machine, de nombreux disques et de nombreux cœurs, vous pouvez créer des index en parallèle, ce qui promet une augmentation notable des performances.
- Hachage d'une connexion parallèle à l'aide d'une table de hachage partagée pour les exécuteurs . , -. , . - , . .
- , union, create table as, select create materialized view!
- - (limit) . .
:
alter table usr reset (parallel_workers)
create index on usr(lower((so).occ)) — 2
alter table usr set (parallel_workers=2)
create index on usr(upper((so).occ)) — 1.8
parallel worker. . 16 4 ( ) 2 ., — 1,8 . , , . , .
:
explain analyze
select u1.* from usr u, usr1 u1 where
u.id=u1.id+0
, . , user — , . . , , .
, PostgreSQL 11 .

1425 , 1,5 . 1,4 . 2 . , 9.6 : 1 — 1 ., 2 1 . , 10 tuple. 11 . : user, batch, x-scan append .
:

. 211 , 702 . , 510 1473. , 2 .
parallel hash join. . — 4. , .
parallel index scan . batch . Qu'est-ce que cela signifie? hash join, . user . , parallel hash, .
1 . , OLAP-, OLTP . OLTP , .
.
- . , . , «» «», index scan, . (highly skewed data), , . . , , .
- «», .
Window-
SQL:2011, .
, , . , , , , , .
websearch, . , . , .
# select websearch_to_tsquery('dog or cat');
----------------------
'dor' | 'cat'
# select websearch_to_tsquery('dog -cat');
----------------------
'dor' & !'cat'
# select websearch_to_tsquery('or cat');
----------------------
'cat'
— dog or cat — . Websearch . | , . “or cat”. , . websearch “or” . , -, .
Websearch — . : , . , .
Json(b)
10- , 11- . json json(b), tsvector. ( json(b)) - . , , , bull, numeric, string, . .
# select jsonb_to_tsvector
('{"a":"texts", "b":12}', '"string"');
-------------------
'text':1
# select jsonb_to_tsvector
('{"a":"texts", "b":12}', '["string", "numeric"]');
-------------------
'12':3 'text':1
json(b), . , , , .
PL/*
.
CREATE PROCEDURE transaction_test1()
LANGUAGE plpgsql
AS $$
BEGIN
FOR i IN 0..9 LOOP
INSERT INTO test1 (a) VALUES (i);
IF i % 2 = 0 THEN
COMMIT;
ELSE
ROLLBACK;
END IF;
END LOOP;
END
$$;
CALL transaction_test1();
call, , . . . select, insert .
, , PostgreSQL . Perl, Python, TL PL/pgSQL. Perl sp begin, .
PL/pgSQL : , .
pgbench
pgbench ICSB bench — , , . if, , . case, - .
--init-steps
, , .
random-seed. zipfian- . / — , . - , , - , .
, , - .
PSQL
, PSQL, . exit quit.
- — copy, 2 32 . copy : 2 32 - . , 2 31 2 32 copy . 64- , 2 64 .
- POSIX : NaN 0 = 1 1 NaN = 1.