PostgreSQL: programmation côté serveur en langage humain (PL / Perl, PL / Python, PL / v8)

Postgres est connu pour son extensibilité, qui s'applique également à la prise en charge des langages procéduraux (PL). Personne ne peut se vanter d'une langue avec une liste de langues de cette longueur, et potentiellement cette liste n'est pas du tout limitée: pour connecter la langue au serveur, aucun effort supplémentaire n'est requis. Vous pouvez même créer votre propre langue et en faire un langage procédural pour le serveur. Les modifications dans le SGBD ne l'exigeront pas. Comme tant d'autres, cette extensibilité a été intégrée à l'architecture Postgres dès le début.

Il est possible et parfois nécessaire d'écrire des langages PL pour les tâches. Mieux encore, si quelqu'un écrit un tel cadre pour écrire des langages afin que vous puissiez écrire non pas en C, mais pour choisir un langage plus confortable pour un développeur de langage. Comme avec FDW, qui peut être écrit en Python .

Cet article a été rédigé sur la base d'un certain nombre de rapports et de master classes sur ce sujet réalisés par l'auteur lors des conférences PgConf.Russia 2019 , PgConf.Russia 2018 et DevConf 2017 .

Il ne s'agit pas d'exotisme, mais des langages procéduraux les plus courants PL / Perl, PL / Python et PL / V8 (c'est-à-dire JavaScript) et de la comparaison de leurs capacités avec PL / pgSQL.

Quand ces langues valent-elles la peine d'être utilisées? Quand SQL et PL / pgSQL sont-ils manquants?

  • Ensuite, lorsque vous devez travailler avec des structures complexes, avec des algorithmes: traverser des arbres, par exemple, ou quand une analyse HTML ou XML est requise, en particulier lors de leur extraction à partir d'archives;
  • Lorsque vous devez générer dynamiquement du SQL complexe (rapports, ORM). Sur PL / pgSQL, ce n'est pas seulement gênant, mais cela fonctionnera aussi plus lentement dans certains cas;
  • Perl Python, C/C++, Perl Python . . , Oracle. , Postgres . Perl Python .
  • — . , , untrusted- ( — . ), Perlu Python(3)u, PL/V8. Postgres , , FDW, , . . !
  • Et encore une chose: si vous allez écrire quelque chose en C, vous pouvez faire un prototype dans ces langages plus adaptés à un développement rapide.

Comment intégrer une langue dans Postgres


Pour implémenter le langage dont vous avez besoin: écrivez en C de une à trois fonctions:

  • HANDLER - un gestionnaire d'appels qui exécutera une fonction dans le langage (c'est une partie obligatoire);
  • INLINE - gestionnaire de blocs anonymes (si vous voulez que la langue prenne en charge les blocs anonymes);
  • VALIDATOR - fonction de vérification de code lors de la création d'une fonction (si vous souhaitez que cette vérification soit effectuée).

Ceci est décrit en détail dans la documentation ici et ici .

«Langues prêtes à l'emploi» et autres langues


Il n'y a que quatre langues prises en charge « prêtes à l' emploi »: PL / pgSQL , PL / Perl , PL / Python et PL / Tcl , mais le chatouillement est plutôt un hommage à l'histoire: peu de gens l'utilisent maintenant, nous n'en parlerons plus.
PL / Perl, PL / Python et, bien sûr, PL / pgSQL sont pris en charge par la communauté Postgres. La prise en charge d'autres langues autres que celles de la boîte incombe à leurs responsables - entreprises, communautés ou développeurs spécifiques intéressés à faire fonctionner la langue à l'intérieur du SGBD. PL / V8 fait la promotion de Google. Mais de temps en temps, il y a des raisonsdoute de l'avenir sans nuage du PL / V8. Le responsable actuel du projet PL / V8 de Google, Jerry Sievert, envisage la prise en charge JS sur serveur postgres basée sur un moteur différent (tel que QuickJS), car PL / V8 est difficile à construire et nécessite 3 à 5 Go toutes sortes de choses sur Linux lors de la construction, ce qui entraîne souvent des problèmes sur différents systèmes d'exploitation. Mais le PL / V8 est largement utilisé et soigneusement testé. Il est possible que PL / JS apparaisse comme une alternative à un autre moteur JS, ou pour l'instant juste comme un nom, auquel nous nous habituerons pendant la période de transition.

PL / Java est rarement utilisé. Personnellement, je n'avais pas besoin d'écrire en PL / Java car en PL / Perl et en PL / V8 il y a suffisamment de fonctionnalités pour presque toutes les tâches. Même Python n'ajoute pas particulièrement de fonctionnalités. PL / RUtile pour ceux qui aiment les statistiques et aiment cette langue. Nous ne parlerons pas de lui ici non plus.

Les langages populaires ne sont pas nécessairement populaires avec les stockages d'écriture: il y a PL / PHP, mais maintenant il n'est pratiquement pris en charge par personne - rares sont ceux qui veulent y écrire des procédures serveur. Pour le langage PL / Ruby, l'image est en quelque sorte la même, bien que le langage semble être plus moderne.

Un langage procédural basé sur Go est en cours de développement, voir PL / Go , et aussi, semble-t-il, PL / Lua . Il faudra les étudier. Pour les fans obstinés de la coque, il y a même PL / Sh , il est même difficile d'imaginer à quoi cela pourrait servir.

Il existe au moins un langage procédural spécifique au domaine (DSL) qui est étroitement spécialisé pour sa tâche - PL / Proxy, qui était très populaire pour le proxy et l'équilibrage de la charge du serveur.

Dans cet article, nous couvrirons les principales langues les plus utilisées. Il s'agit bien sûr de PL / PgSQL, PL / Perl, PL / Python et PL / V8, nous les appellerons PL / * ci-dessous .

Les langues «prêtes à l'emploi» sont vraiment installées presque littéralement hors de la boîte - généralement l'installation est indolore. Mais pour installer PL / V8, si vous n'avez pas trouvé de package avec la version nécessaire dans le référentiel de votre système d'exploitation, c'est presque un exploit, car pour cela, vous devrez réellement construire l'intégralité du V8, ou, en d'autres termes, Chromium. Dans le même temps, l'intégralité de l'infrastructure de développement sera téléchargée sur google.com avec la V8 elle-même - comptez sur quelques gigaoctets de trafic. Pour Postgres 11 sous Ubuntu, le package PL / V8 n'est pas encore apparu, seul V8 pour PG 10 est disponible dans le référentiel jusqu'à présent. Si vous le souhaitez, assemblez-le à la main. Il est également important que la version que vous trouverez dans le référentiel soit probablement assez ancienne. Au moment de la publication de l'article, la dernière version est la 2.3.14.

Une fois la langue installée, vous devez également «créer» la langue - enregistrez-la dans le répertoire système. Cela devrait être fait par l'équipe.

CREATE EXTENSION plperl;

(au lieu de plperl, vous pouvez remplacer le nom d'une autre langue, il y a certaines nuances, voir ci-dessous).
Nous regardons ce qui s'est passé:

test_langs=# \x
test_langs=# \dL+
List of languages
-[ RECORD 1 ]-----+---------------------------------
Name              | plperl
Owner             | postgres
Trusted           | t
Internal language | f
Call handler      | plperl_call_handler()
Validator         | plperl_validator(oid)
Inline handler    | plperl_inline_handler(internal)
Access privileges |
Description       | PL/Perl procedural language
-[ RECORD 2 ]-----+---------------------------------
Name              | plpgsql
Owner             | postgres
Trusted           | t
Internal language | f
Call handler      | plpgsql_call_handler()
Validator         | plpgsql_validator(oid)
Inline handler    | plpgsql_inline_handler(internal)
Access privileges |
Description       | PL/pgSQL procedural language
[ RECORD 3 ]-----+---------------------------------
Name              | plv8
Owner             | postgres
Trusted           | t
Internal language | f
Call handler      | plv8_call_handler()
Validator         | plv8_call_validator(oid)
Inline handler    | plv8_inline_handler(internal)
Access privileges |
Description       |

PL / pgSQL n'a pas besoin d'être spécialement créé, il est toujours déjà dans la base de données.

Attention! PL / pgSQL ne doit pas être confondu avec SQL. Ceci est une langue différente. Cependant, Postgres peut également écrire des fonctions en SQL simple.

Normes


Dans le monde du SGBD, ils parlent souvent de la conformité aux normes SQL. Les langages procéduraux ont également des normes, même si on n'en parle pas si souvent. La norme SQL / PSM est hautement compatible avec le langage procédural DB2. Son implémentation est loin d'être PL / pgSQL, bien que conceptuellement elles soient proches.

SQL / JRT est la norme pour les procédures Java et PL / Java est une bonne correspondance.

Langues fiables et non fiables


Les langages procéduraux PostgreSQL sont approuvés (TRUSTED) et non fiables (UNTRUSTED).
Dans les langages de CONFIANCE, il n'y a aucune possibilité de travail direct avec les E / S, y compris le réseau, et même avec les ressources système. Par conséquent, de telles fonctions peuvent être créées par n'importe quel utilisateur de base de données, gâcher quelque chose et il ne pourra pas en apprendre trop. Les fonctions dans les langues NON CONTRÔLÉES ne peuvent être créées que par un superviseur.

Si l'interpréteur de langue prend en charge de telles restrictions, il peut être utilisé pour créer des langues TRUSTED et UNTRUSTED. Donc, avec Perl, il y a donc différentes langues plperlet plperlu. Lettre uà la fin, il donne le caractère non fiable de la langue. Python n'existe que dans une version non fiable. PL / v8 - au contraire, uniquement en confiance. Par conséquent, PL / v8 ne peut charger aucun module ou bibliothèque à partir du disque, uniquement à partir de la base de données.

Une fonction dans le langage UNTRUSTED peut tout faire: envoyer un e-mail, envoyer une requête ping à un site, se connecter à une base de données étrangère et exécuter une requête HTTP. Les langues de confiance sont limitées au traitement des données de la base de données.

En comprennent TRUSTED: plpgsql, plperl, plv8, pljava.

En UNTRUSTED comprennent: plperlu, pljavau, plpython2u, plpython3u.

Remarque: il n'y a pas de PL / Python comme TRUSTED (car vous ne pouvez pas y définir de restrictions d'accès aux ressources), et PLpgSQL et PL / V8 sont l'inverse: ils ne sont pas INTRUSTRÉS.

Mais Perl et Java sont disponibles dans les deux versions.

PL / pgSQL vs PL / *


Le code PL / pgSQL fonctionne nativement avec tous les types de données de Postgres. Les autres langues n'ont pas beaucoup de types Postgres, et l'interprète de langue se charge de convertir les données en une représentation interne de la langue, en remplaçant les types obscurs par du texte. Cependant, il peut être aidé avec l'aide de TRANSFORM, dont je parlerai plus près de la fin de l'article.

Les appels de fonction en PL / pgSQL sont souvent plus chers. Les fonctions dans d'autres langues peuvent accéder à leurs bibliothèques sans consulter le catalogue système. PL / pgSQL ne peut pas fonctionner comme ça. Certaines requêtes en PL / pgSQL fonctionnent depuis longtemps en raison du fait que de nombreux types sont pris en charge: pour ajouter deux entiers, l'interpréteur doit se rendre compte qu'il traite des entiers et non d'autres types exotiques, puis décider comment les plier, et seulement après cela, les plier.

Étant donné que PL / pgSQL est TRUSTED, vous ne pouvez pas travailler avec le réseau et les disques de celui-ci.

Lorsqu'il s'agit de travailler avec des structures de données imbriquées, PL / pgSQL ne dispose que d'outils Postgres pour travailler avec JSON, qui sont très encombrants et improductifs, dans d'autres langues, travailler avec des structures imbriquées est beaucoup plus simple et plus économique.

PL / * possède sa propre gestion de la mémoire, et vous devez surveiller la mémoire, ou peut-être la limiter.

Vous devez surveiller attentivement la gestion des erreurs, qui est également différente pour tout le monde.

Mais dans PL / *, il existe un contexte d'interpréteur global, et il peut être utilisé, par exemple, pour mettre en cache des données, y compris des plans de requête. Si la langue est NON CONFIÉE, le réseau et les lecteurs sont disponibles. Toutes ces langues fonctionnent avec la base de données, en règle générale, via le SPI, mais plus à ce sujet plus tard.

Examinons plus en détail les fonctionnalités des langages PL / *.

PL / Perl


L'interpréteur Perl est un morceau de code lourd en mémoire, mais heureusement, il n'est pas créé lorsque la connexion est ouverte, mais uniquement lorsque la première procédure / fonction stockée PL / Perl est lancée. Une fois initialisé, le code spécifié dans les paramètres de configuration Postgres est exécuté. Habituellement, les modules sont chargés et des précalculs sont effectués. Si vous avez ajouté au fichier de configuration pendant que la base de données est en cours d'exécution, demandez à Postgres de relire la configuration. Dans cet article, les exemples utilisent un module pour visualiser les structures de données. Il existe des paramètres pour l'initialisation séparée de Perl TRUSTED et UNTRUSTED et, bien sûr, un paramètre . Ceux qui programment en Perl savent que sans lui ce n'est pas une langue, mais un malentendu.

plperl.on_init= 'use Data::Dumper;'
plperl.on_plperl_init= ' ... '
plperl.on_plperlu_init= ' ... '
plperl.use_strict= on


Data::Dumper

use_strict=onstrict

PL / Python


Dans celui-ci, l'interpréteur est créé de la même manière lors du premier accès. Et ici, il est important de décider immédiatement quel python vous voulez: deuxième ou troisième. Comme vous le savez, Python existe en deux versions populaires (Python 2 et Python 3), mais le problème est que leurs so-shki ne s'entendent pas en un seul processus: il y a un conflit de nom. Si vous avez travaillé avec v2 en une seule session puis appelé v3, Postgres se bloquera et pour le processus serveur (backend), ce sera une erreur fatale. Pour accéder à une autre version, vous devez ouvrir une autre session.

Contrairement à Perl, python ne peut pas savoir quoi faire pendant l'initialisation. Autre inconvénient: les lignes simples ne sont pas pratiques à faire.

Dans toutes les fonctions Python, deux dictionnaires sont définis - statique SDet global GD. Global permetéchanger des données avec toutes les fonctions dans un seul backend - ce qui est à la fois attrayant et dangereux. Chaque fonction possède un dictionnaire statique.

En PL / Python, vous pouvez effectuer des sous-transactions, dont nous discuterons ci-dessous.

PL / V8


Ce n'est que de la CONFIANCE.

De manière pratique, les données JSON sont automatiquement converties en une structure JS. En PL / V8, comme en PL / Python, vous pouvez effectuer des sous-transactions. Il existe une interface pour les appels de fonction simplifiés. C'est le seul langage procédural en question dans lequel des fonctions de fenêtre peuvent être définies . Ils suggèrent qu'ils peuvent être définis sur PL / R , mais ce langage sort du cadre de cet article.

Et ce n'est que dans PL / V8 qu'il y a un délai d'exécution. Certes, il n'est pas activé par défaut, et si vous construisez PL / V8 à la main, vous devez dire qu'il a été activé lors de l'assemblage, puis vous pouvez définir des délais d'expiration pour les appels de fonction avec le paramètre de configuration.

L'initialisation dans PL / V8 semble intéressante: puisqu'elle est fiable, elle ne peut pas lire la bibliothèque à partir du disque, elle ne peut rien charger de n'importe où. Il ne peut prendre tout ce dont il a besoin qu'à la base. Par conséquent, une fonction d'initialisation stockée est définie et appelée au démarrage de l'interpréteur de langue. Le nom de la fonction est spécifié dans un paramètre de configuration spécial:

plv8.start_proc=my_init # ( PL/V8-)

Pendant l'initialisation, des variables et fonctions globales peuvent être créées en affectant leurs valeurs aux attributs de cette variable. Par exemple, comme ceci:

CREATE OR REPLACE FUNCTION my_init()
RETURNS void LANGUAGE plv8 AS $$
     this.get_57 = function() { return 57; }; //   
     this.pi_square = 9.8696044;  //   
$$;
SET plv8.start_proc = 'my_init';
DO LANGUAGE plv8 $$
     plv8.elog(NOTICE, pi_square, get_57() );
$$;

Comparaison de PL / Perl vs PL / Python vs PL / V8 en pratique


Bonjour le monde!


Faisons un exercice simple avec la sortie de cette phrase dans les trois langues, d'abord en PL / Perl . Et laissez-le faire autre chose d'utile, par exemple, raconte sa version:

DO $$
     elog(NOTICE,"Hello World! $]");
$$ LANGUAGE plperl;

NOTICE:  Hello World!
DO

Vous pouvez également utiliser les fonctions Perl habituelles warnet die.

Maintenant en PL / Python . Plus précisément sur PL / Python3u (non approuvé) - pour plus de précision.

DO $$
     import sys
     plpy.notice('Hello World! ' , hint=" ", detail=sys.version_info)
$$ LANGUAGE plpython3u;


NOTICE:  Hello World! 
DETAIL:  sys.version_info(major=3, minor=6, micro=9, releaselevel='final', serial=0)
HINT:   
DO

Peut utiliser throw 'Errmsg'. Il y a beaucoup de choses que vous pouvez extraire des messages Postgres: ils contiennent Hint, Details, numéro de ligne et de nombreux autres paramètres. En PL / Python, ils peuvent être passés, mais pas dans les autres langages considérés: leurs moyens ne peuvent être maudits qu'avec une ligne de texte brut.

En PL / Python, chaque niveau de journalisation postgres a sa propre fonction: AVIS, AVERTISSEMENT, DEBUG, LOG, INFO, FATAL. Si c'est ERREUR, alors la transaction a chuté, si FATAL, le backend entier est tombé. Heureusement, l'affaire n'a pas atteint la PANIQUE. Vous pouvez lire ici .

PL / V8

Dans cette langue, Hello world est très similaire à la perle. Vous pouvez arrêter d' exceptionutiliser throw, et ce sera également la gestion des erreurs, bien que les outils ne soient pas aussi avancés qu'en Python. Si vous écrivezplv8.elog(ERROR), l'effet sera d'ailleurs le même.

DO $$
     plv8.elog(NOTICE, 'Hello World!', plv8.version);
$$ LANGUAGE plv8;

NOTICE:  Hello World! 2.3.14
DO

Travailler avec la base


Voyons maintenant comment travailler avec une base de données à partir de procédures stockées. Postgres dispose d'une SPI (Server Programming Interface). Il s'agit d'un ensemble de fonctions C disponibles pour tous les auteurs d'extensions. Presque tous les langages PL fournissent des wrappers pour SPI, mais chaque langue le fait un peu différemment.

Une fonction écrite en C mais utilisant SPI est peu susceptible de donner un gain significatif par rapport à PL / PgSQL et à d'autres langages procéduraux. Mais une fonction C qui contourne SPI et fonctionne avec des données sans intermédiaires (par exemple table_beginscan/heap_getnext) fonctionnera beaucoup plus rapidement.

PL / Java utilise également SPI. Mais travailler avec la base de données se fait toujours dans le style de JDBC et de la norme JDBC. Pour le créateur de code en PL / Java, tout se passe comme si vous travailliez à partir d'une application cliente, mais JNI (Java Native Interface) traduit les appels à la base de données dans les mêmes fonctions SPI. C'est pratique, et il n'y a pas d'obstacles fondamentaux pour traduire ce principe en PL / Perl et PL / Python, mais pour une raison quelconque, cela n'a pas été fait, et jusqu'à présent, il n'est pas visible dans les plans.

Bien sûr, si vous le souhaitez, vous pouvez vous rendre dans des bases étrangères de la manière habituelle - via DBI ou Psycopg . Il est possible de créer une base de données locale, mais pourquoi.

Si vous n'entrez pas dans le sujet holistique «processus dans la base vs processus sur le client», et passez immédiatement du traitement maximal au plus près des données (au moins afin de ne pas conduire d'échantillons géants sur le réseau), alors la solution pour utiliser les fonctions stockées sur le serveur semble naturellement.

Performances : gardez à l'esprit que SPI a des frais généraux et que les requêtes SQL dans les fonctions peuvent être plus lentes que sans fonctions. Le 13e postgres comprenait un patch de Konstantin Knizhnik , ce qui réduit ces coûts. Mais, bien sûr, le traitement des résultats de la requête dans une fonction stockée ne nécessite pas le transfert du résultat au client, et peut donc être bénéfique en termes de performances.

sécurité: un ensemble de fonctions déboguées et testées isole la structure de la base de données de l'utilisateur, protège contre les injections SQL et autres méfaits. Sinon, cela restera un casse-tête pour tous les développeurs d'applications. Réutilisation du

code : si un grand nombre d'applications complexes fonctionnent avec la base de données, il est pratique de stocker des fonctions utiles sur le serveur, plutôt que de les réécrire dans chaque application.

Comment et sous quelle forme obtenons-nous les données de la base de données


En Perl , tout est simple et clair. L'appel spi_exec_queryrenvoie le nombre de lignes traitées, l'état et le tableau de lignes sélectionnés par la requête SQL:

DO $$ 
     warn Data::Dumper::Dumper(
          spi_exec_query('SELECT 57 AS x')
     )
$$ LANGUAGE plperl;

WARNING:  $VAR1 = {
          'rows' => [
                    {
                      'x' => '57'
                    }
                  ],
          'processed' => 1,
          'status' => 'SPI_OK_SELECT'
        };

En Python, la requête et le résultat ressemblent à ceci, mais ici la fonction ne renvoie pas une structure de données, mais un objet spécial avec lequel vous pouvez travailler de différentes manières. Habituellement, il prétend être un tableau et, en conséquence, vous pouvez en extraire des chaînes.

DO $$ 
     plpy.notice(
          plpy.execute('SELECT 57 AS x')
     )
$$ LANGUAGE plpython3u;

NOTICE:  <PLyResult status=5 nrows=1 rows=[{'x': 57}]>
DO

Et maintenant, nous prenons la 1ère ligne, sortons de là X et obtenons la valeur - le nombre.

DO $$ 
     plpy.notice(
          plpy.execute('SELECT 57 AS x')[0]['x']
      )
$$ LANGUAGE plpython3u;

NOTICE:  57
DO

En PL / V8 :

DO $$ 
     plv8.elog(NOTICE, JSON.stringify(
          plv8.execute('SELECT 57 as x'))
     );
$$ LANGUAGE plv8;

NOTICE:  [{"x":57}]
DO

Pour voir la structure, nous avons utilisé la fonction de bibliothèque JSON.stringify, qui n'a pas besoin d'être chargée spécifiquement, elle est déjà prête à être utilisée par défaut dans PL / v8.

Blindage


Pour éviter les injections SQL malveillantes, certains caractères des requêtes doivent être échappés. Pour ce faire, tout d'abord, il existe des fonctions SPI et des fonctions correspondantes (écrites en C) dans des langages qui fonctionnent comme des wrappers SPI. Par exemple, en PL / Perl:

quote_literal- prend les apostrophes et les doubles 'et \. Conçu pour filtrer les données de texte.
quote_nullable- identique, mais undefconverti en NULL.
quote_ident- cite le nom de la table ou du champ, si nécessaire. Utile dans le cas où vous créez une requête SQL et remplacez-y les noms des objets de base de données.

PL / Perl

DO $$
     warn "macy's";
     warn quote_literal("macy's");
$$ LANGUAGE plperl;

WARNING:  macy's at line 2.
WARNING:  'macy''s' at line 3.
DO

Gardez à l'esprit: le nom de la table ne doit pas être échappé comme une ligne de texte. C'est pourquoi il y a une fonction quote_ident.

Mais dans PL / Perl, il existe d'autres fonctions pour le blindage des données de types post-gres individuels: Une fonction doit accepter n'importe quel type et transformer les caractères douteux atypiques en quelque chose de évidemment sûr. Il fonctionne avec un grand nombre de types, mais pas avec tous. Elle, par exemple, ne comprend pas les types de plage et ne les perçoit que comme des chaînes de texte.

encode_bytea
decode_bytea
encode_array_literal
encode_typed_literal
encode_array_constructor


quote_typed_literal

DO $$
     warn encode_typed_literal(
          ["", " "], "text[]"
     );
$$ LANGUAGE plperl;

WARNING:  {," "} at line 2.
DO

Il existe trois fonctions similaires dans PL / Python , et elles fonctionnent de la même manière:

plpy.quote_literal
plpy.quote_nullable
plpy.quote_ident


DO $$ plpy.notice(
     plpy.quote_literal("Macy's"));
$$ LANGUAGE plpython3u;
NOTICE:  'Macy''s'
DO

Les fonctions de PL / V8 sont-elles les mêmes ?

Bien sûr! Tout est le même jusqu'aux fonctionnalités syntaxiques.

plv8.quote_literal
plv8.quote_nullable
plv8.quote_ident


DO $$
    plv8.elog(NOTICE, plv8.quote_nullable("Macy's"));
$$ LANGUAGE plv8;

NOTICE:  'Macy''s'

Performance


Quelle langue est la plus rapide? Habituellement, ils répondent: C. Mais la bonne réponse est C ou SQL. Pourquoi SQL? Le fait est qu'une fonction dans ce langage n'est pas toujours exécutée explicitement. Il peut être intégré dans la requête (le planificateur incorporera la fonction dans le corps de la requête principale), s'optimisera bien avec la requête et le résultat sera plus rapide. Mais dans quelles conditions le code peut-il être intégré dans une requête? Il y a quelques conditions simples que vous pouvez lire, par exemple, ici . Par exemple, une fonction ne doit pas être exécutée avec les droits du propriétaire (être SECURITY DEFINER). La plupart des fonctions simples répondent à ces conditions.

Dans cet article nous mesurerons "sur le genou", pas sérieusement. Nous avons besoin d'une comparaison grossière. Allumez d'abord le timing:

\timing

Essayons SQL (Les temps d'exécution des commandes ci-dessous sont les valeurs moyennes arrondies que l'auteur a reçues sur un PC déchargé de six ans. Ils peuvent être comparés entre eux, mais ils ne prétendent pas être scientifiques):

SELECT count(*) FROM pg_class;
0.5 ms

Cela fonctionne très rapidement. Dans d'autres langues, le temps est perdu à appeler des fonctions de la langue. Bien sûr, la première fois que la requête s'exécutera plus lentement en raison de l'initialisation de l'interpréteur. Ensuite, il se stabilise.

Essayons PL / pgSQL :

DO $$
     DECLARE a int;
     BEGIN
          SELECT count(*) INTO a FROM pg_class;
     END;
$$ LANGUAGE plpgsql;
0.7 ms

PL / Perl :

DO $$
     my $x = spi_exec_query('SELECT count(*) FROM pg_class');
$$ LANGUAGE plperl;
0.7 ms

PL / Python:

DO $$
     x = plpy.execute('SELECT count(*) FROM pg_class');
$$ LANGUAGE plpythonu;
0.8 ms

C'était Python 2. Maintenant Python 3 (rappel: Python2 et Python3 ne vivent pas paisiblement dans la même session, un conflit de noms est possible):

DO $$
     x = plpy.execute('SELECT count(*) FROM pg_class');
$$ LANGUAGE plpython3u;
0.9ms

Et enfin, PL / V8 :

DO $$
     var x = plv8.execute('SELECT count(*) FROM pg_class');
$$ LANGUAGE plv8 ;
0.9 ms

Mais c'est en quelque sorte très rapide. Essayons d'exécuter la requête 1000 fois ou 1 million de fois, soudain la différence sera plus sensible:

PL / pgSQL :

DO $$
     DECLARE a int; i int;
     BEGIN FOR i IN 0..999999 LOOP
          SELECT count(*) INTO a FROM pg_class;
    END LOOP;
END;
$$ LANGUAGE plpgsql;
53s

PL / Perl :

DO $$
     for (0..999999) {
          spi_exec_query('SELECT count(*) FROM pg_class');
     }
$$ LANGUAGE plperl;
102s

PL / Python 3 :

DO $$
     for i in range (0,1000000) :
          plpy.execute('SELECT count(*) FROM pg_class')
$$ LANGUAGE plpython3u;
98s

PL / V8 :

DO $$
     for(var i=0;i<1000;i++)
          plv8.execute('SELECT count(*) FROM pg_class');
$$ LANGUAGE plv8;
100ms

Notez qu'avec PL / V8, l'expérience a été faite avec mille, pas un million d'itérations. Avec des ressources modérées, le PL / V8 dans un cycle de 1 million d'opérations mangera toute la mémoire et accrochera complètement la voiture. Déjà à mille itérations, le processus postgres sélectionne 3,5 Go de mémoire et 100% d'écriture sur le disque. En fait, postgres lance l'environnement V8, et bien sûr, il mange de la mémoire. Après avoir exécuté la requête, ce monstre turbo ne va pas rendre de mémoire. Pour libérer de la mémoire, vous devez mettre fin à la session.

On voit que PL / pgSQL est déjà 2 fois plus rapide que PL / Perl et PL / Python. PL / V8 est encore légèrement derrière eux, mais vers la fin de l'article, il est partiellement réhabilité.

En général, Perl avec Python dans ces expériences montre approximativement les mêmes résultats. Perl était légèrement inférieur à Python; dans les versions modernes, il est légèrement plus rapide. Le troisième python est légèrement plus lent que le second. La différence totale se situe à moins de 15%.

Performance avec PREPARE


Les gens qui savent comprendront: quelque chose ne va pas. PL / pgSQL peut automatiquement mettre en cache les plans de requête , et dans PL / *, chaque fois que la requête a été planifiée à nouveau. Dans le bon sens, vous devez préparer des demandes, créer un plan de demande, puis selon ce plan, elles doivent être exécutées autant de fois que nécessaire. Dans PL / *, vous pouvez explicitement travailler avec des plans de requête, que nous allons essayer de commencer avec PL / Perl :

DO $$
     my $h = spi_prepare('SELECT count(*) FROM pg_class');
     for (0..999999) {
          spi_exec_prepared($h);
     }
     spi_freeplan($h);
$$ LANGUAGE plperl;
60s

PL / Python 3 :

DO $$
     h = plpy.prepare('SELECT count(*) FROM pg_class')
     for i in range (0,1000000): plpy.execute(h)
$$ LANGUAGE plpython3u;
62s

PL / V8 :

DO $$
     var h=plv8.prepare('SELECT count(*) FROM pg_class');
     for(var i=0;i<1000;i++) h.execute();
$$ LANGUAGE plv8;
53ms

Avec preparenos deux langues, nous avons presque rattrapé PL / pgSQL, tandis que le troisième le voulait aussi, mais n'a pas atteint la ligne d'arrivée en raison de la demande croissante de mémoire.

Mais si vous ne tenez pas compte de la mémoire, il est clair que toutes les langues vont presque de pair - et pas par hasard. Leur goulot d'étranglement est maintenant commun - travailler avec la base de données via SPI.

Performances informatiques


Nous voyons que la performance du langage repose sur le travail avec la base de données. Pour comparer les langues entre elles, essayons de calculer quelque chose sans avoir recours à la base de données, par exemple, la somme des carrés.

PL / pgSQL :

DO $$
     DECLARE i bigint; a bigint;
     BEGIN a=0;
     FOR i IN 0..1000000 LOOP
          a=a+i*i::bigint;
     END LOOP;
END;
$$ LANGUAGE plpgsql;
280ms

PL / Perl :

DO $$
     my $a=0;
     for my $i (0..1000000) { $a+=$i*$i; };
     warn $a;
$$ LANGUAGE plperl;
63ms

PL / Python 3 :

DO $$
a=0
for i in range(1,1000001): a=a+i*i
$$ LANGUAGE plpython3u;
73ms

PL / V8 :

DO $$
     var a=0;
     for(var i=0;i<=1000000;i++) a+=i*i;
     plv8.elog(NOTICE, a);
$$ language plv8;
7.5ms

On voit que PL / Perl et PL / Python ont dépassé et dépassé PL / pgSQL, ils sont 4 fois plus rapides. Et le huit déchire tout le monde! Mais est-ce vraiment pour rien? Ou allons-nous l'obtenir pour la tête? Oui nous le ferons.

Le nombre en JavaScript est un flottant, et le résultat est rapide, mais pas précis: 333333833333127550 au lieu de 333333833333500000.

Voici la formule par laquelle le résultat exact est calculé :

∑ = n*(n+1)*(2n+1)/6

Comme exercice, vous pouvez le prouver en utilisant l'induction mathématique.

Dans l'ordre du rire

DO LANGUAGE plv8 $$
plv8.elog(NOTICE, parseInt(33333383333312755033)) $$;

NOTICE:
33333383333312754000

En Javascript, parseIntil fait toujours un float, pas un Int.

Néanmoins, BigInt est apparu dans V8 en 2018 , et il peut maintenant être compté avec certitude, mais au détriment de la vitesse, car il ne s'agit pas d'un entier 64 bits, mais d'un entier de profondeur de bits arbitraire. Cependant, en PL / V8, cette innovation n'a pas encore frappé. Dans d'autres langages procéduraux, les nombres de bits arbitraires (analogues de SQL numeric) sont pris en charge via des bibliothèques spéciales.

En Perl, il existe un module Math :: BigFloat pour l'arithmétique avec une précision arbitraire, et en Python le package Bigfloat est un wrapper Cython autour de la bibliothèque GNU MPFR .

Fonctions de performance pour le tri


Voici un exemple pratique, qui montre la différence dans les performances de tri par fonction, si cette fonction est écrite dans différentes langues. Tâche: trier les champs de texte contenant les numéros des numéros de la revue, qui peuvent être les suivants:

1
2
3
4-5
6
6A
6
11
12

Ceux. c'est en fait une chaîne, mais elle commence par un nombre, et vous devez trier par ces nombres. Par conséquent, afin de trier correctement en tant que chaînes, nous complétons la partie numérique avec des zéros à gauche pour obtenir:

0000000001
0000000002
0000000003
0000000004-5
0000000006
0000000006A
0000000006
0000000011
0000000012

Oui, je sais que ce n'est pas la seule solution au problème (et même pas tout à fait raison). Mais par exemple, cela suffira.

Pour demander un type, SELECT ... ORDER BY nsort(n)nous écrivons des fonctions en PL / Perl, SQL, PL / Python et PL / V8 qui convertissent les numéros de journal sous cette forme:

CREATE OR REPLACE FUNCTION nsort(text) RETURNS text 
   LANGUAGE PLPERL IMMUTABLE AS $$
    my $x = shift;
    return ($x =~ /^\s*(\d+)(.*)$/)
        ? sprintf("%010d", $1).$2
        : $x;
$$;

CREATE OR REPLACE FUNCTION _nsort(x text) RETURNS text
     LANGUAGE SQL  IMMUTABLE  AS $$
 WITH y AS (
    SELECT regexp_match(x,'^\s*(\d*)(.*)$') as z
 )
 SELECT CASE WHEN z[1] = '' THEN x ELSE lpad(z[1],10,'0') || z[2] END FROM y;
$$;

CREATE OR REPLACE FUNCTION py_nsort(x text) RETURNS text 
   LANGUAGE plpython2u IMMUTABLE AS $$
import re
r = re.match('^\s*(\d+)(.*)$', x)
return x if r == None else ('%010d' % int(r.group(1))) + r.group(2)
$$;

CREATE OR REPLACE FUNCTION js_nsort(x text) RETURNS text 
   LANGUAGE plv8 IMMUTABLE AS $$
var m = x.match(/^\s*(\d+)(.*)$/);
if(m) { return m[1].padStart(10-m[1].length,'0') + m[2]; }
else { return x; } 
$$;

Dans ma bibliothèque de 15,5 mille articles de journaux, une requête utilisant une fonction en PL / Perl prend environ 64 ms contre 120 ms en PL / Python et 200 ms en PL / PgSQL. Mais le plus rapide - PL / v8: 54 ms.

Remarque: lors de l'expérimentation du tri, fournissez la quantité de mémoire de travail nécessaire pour que le tri soit enregistré en mémoire (EXPLAIN s'affiche alors Sort Method: quicksort). La quantité de mémoire est définie par le paramètre work_mem:

set work_mem = '20MB';

Mémoire


Perl n'aime pas les structures en boucle, il ne sait pas comment les nettoyer. Si vous aavez un pointeur vers bet un bpointeur vers a, le compteur de référence ne sera jamais réinitialisé et la mémoire ne sera pas libérée.

Les langues de récupération de place ont d'autres problèmes. On ne sait pas, par exemple, quand la mémoire sera libérée ou si elle sera libérée du tout. Ou - si vous ne vous en occupez pas exprès - les collecteurs iront ramasser les ordures au moment le plus inopportun.

Mais il existe également des fonctionnalités de gestion de la mémoire directement liées à Postgres. Il existe des structures que SPI alloue, et Perl ne se rend pas toujours compte qu'elles doivent être libérées.

PL / Perl

Ce n'est PAS comme ça que ça se passe:

CREATE OR REPLACE function cr()
RETURNS int LANGUAGE plperl AS
$$
     return spi_exec_query(
           'SELECT count(*) FROM pg_class'
     )->{rows}->[0]->{count};
$$;

Et il en va ainsi:

CREATE OR REPLACE function cr()
RETURNS int LANGUAGE plperl AS
$$
     my $h = spi_prepare(
          'SELECT count(*) FROM pg_class'
     );
     return spi_exec_prepared($h)->{rows}->[0]->{count};
$$;

Après l'exécution, le gestionnaire $hrestera en vie, malgré le fait qu'il ne reste aucun lien vivant avec lui.

C'est bon, il vous suffit de vous rappeler la nécessité de libérer explicitement les ressources avec spi_freeplan($h):

CREATE OR REPLACE function cr()
RETURNS int LANGUAGE plperl AS
$$
     my $h = spi_prepare(
          'select count(*) from pg_class'
     );
     my $res = spi_exec_prepared($h)->{rows}->[0]->{count};
     spi_freeplan($h);
     return $res;
$$;

PL / Python:

Python ne coule jamais , le plan est automatiquement publié:

CREATE OR REPLACE function cr3() RETURNS int
LANGUAGE plpythonu as
$$
     return plpy.execute(
           'select count(*) from pg_class'
     )[0]['count']
$$;

PL / V8

Même histoire que Perl. Ça ne coule pas comme ça:

CREATE OR REPLACE FUNCTION crq() RETURNS int
LANGUAGE plv8 AS
$$
     return plv8.execute(
          'select count(*) from pg_class‘
     )[0].count;
$$;

Et il en va ainsi:

CREATE OR REPLACE FUNCTION crq() RETURNS int
LANGUAGE plv8 AS
$$
     var h = plv8.prepare(
          'select count(*) from pg_class'
     );
     return h.execute()[0].count;
$$;

Encore une fois: n'oubliez pas de libérer des ressources. Ça y est, h.free();

ça ne coule pas:

CREATE OR REPLACE FUNCTION crq() RETURNS int
LANGUAGE plv8 AS
$$
     var h = plv8.prepare(
          'select count(*) from pg_class'
     );
     var r = h.execute()[0].count;
     h.free();
     return r;
$$;

Paramètres


Il est temps de comprendre comment les arguments sont passés aux fonctions. Dans les exemples, nous passerons 4 paramètres avec des types à la fonction:

  • entier;
  • un tableau;
  • bytea et
  • jsonb

Comment entrent-ils dans PL / Perl ?

CREATE OR REPLACE FUNCTION crq(a int, b
bytea, c int[], d jsonb ) RETURNS void
LANGUAGE plperl AS
$$
    warn Dumper(@_);
$$;

SELECT crq(1,'abcd', ARRAY[1,2,3],'{"a":2,"b":3}');


WARNING:  $VAR1 = '1';
$VAR2 = '\\x61626364';
$VAR3 = bless( {
                 'array' => [
                              '1',
                              '2',
                              '3'
                            ],
                 'typeoid' => 1007
               }, 'PostgreSQL::InServer::ARRAY' );
$VAR4 = '{"a": 2, "b": 3}';
 crq 
-----
(1 row)

Sera-ce JSON ou JSONB - dans ce cas, cela ne fait aucune différence: ils se présentent toujours sous la forme d'une chaîne. Il s'agit d'une redevance pour la polyvalence: Postgres a beaucoup de types, de différents degrés de «intégré». Demander au développeur qu'avec le nouveau type il fournit immédiatement des fonctions de conversion pour tous les PL / * serait trop. Par défaut, de nombreux types sont transmis sous forme de chaînes. Mais ce n'est pas toujours pratique, vous devez analyser ces termes. Bien sûr, j'aimerais que les données Postgres se transforment immédiatement en structures Perl appropriées. Par défaut, cela ne se produit pas, mais à partir de 9.6, le mécanisme TRANSFORM est apparu - la possibilité de définir des fonctions de conversion de type: CREATE TRANSFORM .

Pour créer TRANSFORM, vous devez écrire deux fonctions en C: l'une convertira les données d'un certain type d'un côté, de l'autre en arrière. Veuillez noter que TRANSFORM fonctionne à quatre endroits:

  • Lors du passage de paramètres à une fonction;
  • Lors du retour d'une valeur de fonction;
  • Lors du passage de paramètres à un appel SPI à l'intérieur d'une fonction;
  • Dès réception du résultat de l'appel SPI à l'intérieur de la fonction.

TRANSFORM JSONB pour Perl et Python, développé par Anton Bykov, est apparu dans la 11e version de Postgres. Maintenant, vous n'avez pas besoin d'analyser JSONB, il pénètre immédiatement dans Perl en tant que structure correspondante. Vous devez créer l'extension jsonb_plperl, puis vous pouvez utiliser TRANSFORM:

CREATE EXTENSION jsonb_plperl;
CREATE OR REPLACE FUNCTION crq2(d jsonb)
RETURNS void LANGUAGE plperl
TRANSFORM FOR TYPE jsonb AS $$
     warn Dumper(@_);
$$;

Vous pouvez appeler cette fonction pour vérifier que JSONB est devenu un hachage perlé:

SELECT crq2( '{"a":2,"b":3}');


WARNING:  $VAR1 = {
          'a' => '2',
          'b' => '3'
        };
 crq2 
------
(1 row)

Une toute autre affaire!

L'auteur de cet article a également contribué au développement de TRANSFORM. Il s'est avéré qu'un type de données aussi simple, tel que booleantransmis à PL / Perl sous une forme incommode, comme des chaînes de texte 't'ou 'f'. Mais d'après Perl, la chaîne «f» est vraie. Pour éliminer l'inconvénient, un patch a été inventé qui définit la conversion pour le type booléen . Ce correctif a frappé PostgreSQL 13 et sera bientôt disponible. En raison de sa simplicité, bool_plperl peut servir de modèle de départ minimal pour écrire toute autre conversion.

J'espère que quelqu'un développera TRANSFORM pour d'autres types de données (bytea, tableaux, dates, numériques).

Voyons maintenant comment les paramètres sont passés en Python .

CREATE EXTENSION jsonb_plpython3u;
CREATE OR REPLACE FUNCTION pdump(a int, b bytea, c int[], d jsonb ) RETURNS void
LANGUAGE plpython3u
TRANSFORM FOR TYPE jsonb AS $$
      plpy.warning(a,b,c,d)
$$;

SELECT pdump(1,'abcd', ARRAY[1,2,3],'{"a":2,"b":3}');


WARNING:  (1, b'abcd', [1, 2, 3], {'a': Decimal('2'), 'b': Decimal('3')})
 pdump 
-------
(1 row)

Un tableau est converti en tableau - c'est bien (puisque les tableaux multidimensionnels de la version PG10 sont également correctement transférés vers python). En Perl, un tableau a été converti en un objet d'une classe spéciale. Eh bien, jsonbtransformé. Sans TRANSFORM, jsonb sera transmis sous forme de chaîne.

Voyons maintenant sous quelle forme les paramètres entrent dans JS .

CREATE OR REPLACE FUNCTION jsdump(a int, b bytea, c int[], d jsonb) RETURNS void
LANGUAGE plv8 AS $$
     plv8.elog(WARNING,a,b,c,d)
$$;

SELECT jsdump(1,'abcd', ARRAY[1,2,3],'{"a":2,"b":3}');


WARNING:  1 97,98,99,100 1,2,3 [object Object]
jsdump 
-------
(1 row)

JSONB converti en un objet JavaScript sans aucune TRANSFORMATION! Les types Postgres temporaires sont également convertis en type Date JS. Même chose avec booléen. Toutes les transformations sont déjà intégrées dans PL / V8.

Travaillez à l'infini


La constante INFINITY n'est pas utilisée très souvent, mais un travail bâclé avec elle est dangereux. Dans PostgreSQL, Infinity et -Infinity existent en tant que valeurs spéciales pour certains types temporaires et à virgule flottante. Mais le transfert d'Infinity vers les langages procéduraux et vice versa doit être discuté en détail, car travailler avec eux peut dépendre non seulement de la langue, mais aussi des bibliothèques, du système d'exploitation et même du matériel.

Python possède un module Numpy qui définit l'infini numérique:

import numpy as nm
a = nm.inf
b = -nm.inf
print(a, b)

inf -inf

Perl a également l'infini, il utilise une chaîne "infinity"qui peut être raccourcie "inf". Par exemple, vous pourriez dire:

perl -e 'print 1 * "inf"'

Inf

ou

perl -e 'print 1/"inf"'

0

Dans PL / Perl, PL / Python, PL / v8, l'infini numérique de Postgres est passé correctement, mais la date infinie n'est pas tout à fait correcte. Au contraire, dans PL / Perl et PL / Python, il n'y a pas de type de données intégré pour l'heure, une chaîne vient là. Dans PL / V8, il existe un type intégré Date, et la date habituelle d'un postgres se transforme en elle. Mais le V8 ne connaît pas la date sans fin, et une fois transféré, il se transforme en Invalid Date.

Passer des paramètres aux requêtes préparées


Revenons à prepare, considérez comment les paramètres y sont passés. Différentes langues ont beaucoup en commun, car elles sont toutes basées sur SPI.

Lorsque vous préparez une requête dans PL / Perl , vous devez déterminer le type de paramètres transmis et lorsque vous exécutez la requête, vous spécifiez uniquement les valeurs de ces paramètres (les paramètres sont transmis à PL / pgSQL de la même manière).

DO LANGUAGE plperl $$
     my $h= spi_prepare('SELECT * FROM pg_class WHERE
          relname ~ $1', 'text' );                     #   
     warn Dumper(spi_exec_prepared($h, 'pg_language')); #   
     spi_freeplan($h);
$$;

En PL / Python, l' essence est la même, mais la syntaxe est légèrement différente:

DO LANGUAGE plpython3u $$
     h= plpy.prepare('SELECT relname FROM pg_class WHERE relname ~ $1', ['text'] )
     plpy.notice(.execute (['pg_language']))
$$;

En PL / V8, les différences sont minimes:

DO LANGUAGE plv8 $$
    var h= plv8.prepare('SELECT relname FROM pg_class WHERE relname ~ $1', ['text'] );
    plv8.elog(NOTICE, h.execute (['pg_language']));
    h.free();
$$;

En PL / Java, tout est différent. Là, SPI n'est clairement pas utilisé, mais une connexion pseudo-JDBC à la base de données est établie. Pour un programmeur PL / Java, tout se passe comme s'il créait une application client. C'est pratique, et on pourrait également aborder la conception de PL / Perl et PL / Python, mais pour une raison quelconque, cela n'a pas été fait (cependant, personne n'interdit de créer quelques implémentations supplémentaires de PL / Perl et PL / Python).

Travailler avec le curseur


Toutes les fonctions SPI que nous avons utilisées lorsque nous sommes allés dans la base de données - spi_exec_query()et d'autres - ont un paramètre qui limite le nombre de lignes renvoyées. Si vous avez besoin d'un grand nombre de lignes renvoyées, vous ne pouvez pas vous passer d'un curseur pour les remonter un peu.

Les curseurs travaillent dans toutes ces langues. Dans PL / Perl,
spi_exec_query renvoie un curseur à partir duquel vous pouvez extraire les chaînes une à la fois. Il n'est pas nécessaire de fermer le curseur, il se fermera automatiquement. Mais si vous souhaitez le redécouvrir à nouveau, vous pouvez le fermer explicitement avec une commande close().

DO LANGUAGE plperl $$
    my $cursor = spi_query('SELECT * FROM pg_class');
    my $row;
    while(defined($row = spi_fetchrow($cursor))) {
         warn $row->{relname};
    }
$$;

WARNING:  pg_statistic at line 5.
WARNING:  pg_toast_2604 at line 5.
WARNING:  pg_toast_2604_index at line 5.
WARNING:  pg_toast_2606 at line 5.
WARNING:  pg_toast_2606_index at line 5.
WARNING:  pg_toast_2609 at line 5.
WARNING:  pg_toast_2609_index at line 5.
...

En PL / Python, tout est très similaire, mais le curseur est présenté comme un objet que vous pouvez parcourir:

h = plpy.prepare('SELECT ...');
cursor = plpy.cursor(h);
for row in cursor:
...
cursor.close() //  

En PL / v8, tout est également très similaire, mais n'oubliez pas de libérer le plan de requête préparé:

var h = plv.prepare('SELECT ...');
var cursor = h.cursor();
var row;
while(row = cursor.fetch()) {
...
}
cursor.close();
h.free();

PL / V8: Accès rapide aux fonctionnalités


Dans PL / V8, vous pouvez appeler une fonction non pas à partir d'un SELECT ordinaire, mais la trouver par son nom et la lancer immédiatement avec plv8.find_function(name);. Mais gardez à l'esprit que dans JS, une fonction ne peut pas être polymorphe, comme dans PostgreSQL, dans laquelle des fonctions portant le même nom mais avec des paramètres différents peuvent coexister. En PL / v8, bien sûr, nous pouvons créer des fonctions polymorphes, mais il y find_functionaura une erreur lors de la tentative d'utilisation .

ERROR:  Error: more than one function named "jsdump"

Si une fonction par son nom est sans ambiguïté, elle peut être appelée sans SPI et conversions de type, c'est-à-dire Plus vite. Par exemple, comme ceci:

DO LANGUAGE plv8 $$
plv8.find_function('jsdump')(1, 'abc');
$$;

Transactions


Postgres 11 a beaucoup de plaisir: de vraies procédures sont apparues . Postgres n'avait auparavant que des fonctionnalités. La joie n'est pas seulement due à la compatibilité et au respect de la norme SQL, mais pourquoi: dans les procédures, vous pouvez valider et annuler des transactions.

PL / Perl et PL / Python ont déjà des fonctions SPI pour gérer les transactions, alors que PL / V8 n'en a pas encore. Dans PL / Perl, ces fonctions sont appelées spi_commit()et spi_rollback(), et un exemple d'utilisation se trouve dans la documentation . En PL / Python, c'est plpy.commit()et plpy.rollback().

Sous-transaction


Les sous-transactions sont pratiques pour la gestion correcte des erreurs dans une logique complexe à plusieurs niveaux.

En PL / pgSQL à l' intérieur d'une transaction, chaque bloc avec le mot-clé EXCEPTION est une sous-transaction. Vous pouvez lire sur certains problèmes de performances et de fiabilité qui peuvent survenir dans ce cas, par exemple, ici .

Il n'y a pas de sous-transactions explicites dans PL / Perl , mais elles peuvent être simulées via des points de sauvegarde. Apparemment, si vous le souhaitez, il est facile d'écrire un module Pearl qui implémente les sous-transactions sous une forme explicite.

En PL / Python, les sous-transactions sont apparues il y a longtemps: de 9.5 explicites , avant il y en avait implicitement . Vous pouvez définir une transaction, enveloppertry-et exécuter. Si la sous-transaction tombe, alors nous tombons dans le bloc except; si elle ne tombe pas, alors dans le bloc elseet continuons.

try:
     with plpy.subtransaction():
          plpy.execute("...")
          plpy.execute("...")
except plpy.SPIError, e:
. . .
else:
. . .

Une conception similaire existe en PL / V8 , uniquement dans la syntaxe JS.

try {
plv8.subtransaction(function() {
plv8.execute('UPDATE...');
plv8.execute('UPDATE...');
});
}
catch(e) {
...
}

Conclusion


Essayez, mais n'abusez pas :) La connaissance de PL / * peut apporter certains avantages. Comme tout outil, ils adorent être utilisés conformément à leur destination.

PL / v8 est très prometteur, mais il se comporte parfois de manière inattendue et présente un certain nombre de problèmes. Par conséquent, il est préférable de sortir les langues de la boîte si elles conviennent à votre tâche.

Je remercie Igor Levshin (Igor_Le), qui m'a beaucoup aidé à préparer le matériel pour l'article et a proposé quelques idées utiles, ainsi que Yevgeny Sergeyev et Alexey Fadeev pour les corrections qu'ils ont proposées.

All Articles