Verrous dans PostgreSQL: 3. Verrouille d'autres objets

Nous avons déjà parlé de certains verrous au niveau de l'objet (en particulier, des verrous sur les relations), ainsi que des verrous au niveau de la ligne , de leur relation avec les verrous d'objet et de la file d'attente, ce qui n'est pas toujours honnête.

Aujourd'hui, nous avons un méli-mélo. Commençons par les blocages (en fait, j'allais en parler la dernière fois, mais cet article s'est avéré indécemment long), puis nous passerons en revue les verrous d'objet restants et parlerons des verrous de prédicat en conclusion.

Deadlocks


Lors de l'utilisation de verrous, une situation de blocage (ou de blocage ) est possible. Il se produit lorsqu'une transaction tente de capturer une ressource déjà capturée par une autre transaction, tandis qu'une autre transaction tente de capturer une ressource capturée par la première. Ceci est illustré dans la figure de gauche ci-dessous: les flèches pleines montrent les ressources capturées, les flèches en pointillés montrent les tentatives de capture d'une ressource déjà occupée.

Il est pratique de visualiser une impasse en construisant un graphique des attentes. Pour ce faire, nous supprimons des ressources spécifiques et ne laissons que des transactions, en notant celle qui attend. Si le graphique a un contour (du haut, vous pouvez y accéder par les flèches) - c'est une impasse.



Bien sûr, un blocage est possible non seulement pour deux transactions, mais aussi pour un nombre plus important.

Si un blocage se produit, les transactions qui y sont impliquées ne peuvent rien y faire - elles attendront indéfiniment. Par conséquent, tous les SGBD, ainsi que PostgreSQL, suivent automatiquement les blocages.

Cependant, la vérification nécessite certains efforts, que je ne veux pas faire chaque fois qu'un nouveau verrou est demandé (après tout, les blocages sont assez rares). Par conséquent, lorsque le processus tente de capturer le verrou et ne peut pas, il entre dans la file d'attente et s'endort, mais démarre le minuteur à la valeur spécifiée dans le paramètre deadlock_timeout (par défaut - 1 seconde). Si la ressource est libérée plus tôt, alors bon, nous avons économisé lors de la vérification. Mais si, après deadlock_timeout, l' attente se poursuit, le processus d'attente sera réveillé et lancera une vérification.

Si la vérification (qui consiste à construire un graphique des attentes et à y rechercher des contours) n'a pas révélé de blocages, alors le processus continue de s'endormir - maintenant déjà à la fin.

Plus tôt dans les commentaires, on m'a reproché à juste titre de ne rien dire sur le paramètre lock_timeout , qui agit sur n'importe quel opérateur et évite une attente indéfiniment longue: si le verrou n'a pas pu être obtenu dans le temps spécifié, l'instruction se termine par l'erreur lock_not_available. Il ne doit pas être confondu avec le paramètre statement_timeout , qui limite le temps d'exécution total de l'instruction, peu importe s'il attend un verrou ou fait simplement le travail.

Si un blocage est détecté, l'une des transactions (dans la plupart des cas, celle qui a initié le contrôle) est interrompue de force. Dans ce cas, les verrous capturés par celui-ci sont libérés et les transactions restantes peuvent continuer à fonctionner.

Les interblocages signifient généralement que l'application n'est pas conçue correctement. Il existe deux façons de détecter de telles situations: premièrement, les messages apparaîtront dans le journal du serveur et deuxièmement, la valeur de pg_stat_database.deadlocks augmentera.

Exemple de blocage


Une cause fréquente de blocages est l'ordre différent dans lequel les lignes des tableaux sont verrouillées.
Un exemple simple. La première transaction vise à transférer 100 roubles du premier compte au second. Pour ce faire, elle réduit d'abord le premier décompte:

=> BEGIN; => UPDATE accounts SET amount = amount - 100.00 WHERE acc_no = 1; 
 UPDATE 1 

Dans le même temps, la deuxième transaction vise à transférer 10 roubles du deuxième compte au premier. Elle commence par réduire le deuxième décompte:

 | => BEGIN; | => UPDATE accounts SET amount = amount - 10.00 WHERE acc_no = 2; 
 | UPDATE 1 

Maintenant, la première transaction tente d'augmenter le deuxième compte, mais constate que la ligne est verrouillée.

 => UPDATE accounts SET amount = amount + 100.00 WHERE acc_no = 2; 

Ensuite, la deuxième transaction tente d'augmenter le premier compte, mais est également bloquée.

 | => UPDATE accounts SET amount = amount + 10.00 WHERE acc_no = 1; 

Il existe une attente cyclique qui ne se terminera jamais d'elle-même. Après une seconde, la première transaction, n'ayant pas accès à la ressource, initie une vérification de blocage et arrête le serveur.

 ERROR: deadlock detected DETAIL: Process 16477 waits for ShareLock on transaction 530695; blocked by process 16513. Process 16513 waits for ShareLock on transaction 530694; blocked by process 16477. HINT: See server log for query details. CONTEXT: while updating tuple (0,2) in relation "accounts" 

Maintenant, la deuxième transaction peut continuer.

 | UPDATE 1 
 | => ROLLBACK; 

 => ROLLBACK; 

La bonne façon d'effectuer de telles opérations consiste à bloquer les ressources dans le même ordre. Par exemple, dans ce cas, vous pouvez bloquer les comptes dans l'ordre croissant de leurs numéros.

Blocage pour deux commandes UPDATE


Parfois, vous pouvez obtenir une impasse où, semble-t-il, elle ne devrait pas l'être. Par exemple, il est pratique et familier de percevoir les commandes SQL comme atomiques, mais prenez UPDATE - cette commande bloque les lignes lors de leur mise à jour. Cela ne se produit pas immédiatement. Par conséquent, si une commande met à jour les lignes dans un ordre et l'autre dans un autre, elles peuvent être bloquées.

Il est peu probable d'obtenir une telle situation, mais néanmoins elle peut se rencontrer. Pour la lecture, nous allons créer un index sur la colonne de montant, construit par ordre décroissant de montant:

 => CREATE INDEX ON accounts(amount DESC); 

Afin d'avoir le temps de voir ce qui se passe, nous écrirons une fonction qui augmente la valeur transmise, mais lentement, lentement, pendant une seconde:

 => CREATE FUNCTION inc_slow(n numeric) RETURNS numeric AS $$ SELECT pg_sleep(1); SELECT n + 100.00; $$ LANGUAGE SQL; 

Nous avons également besoin de l'extension pgrowlocks.

 => CREATE EXTENSION pgrowlocks; 

La première commande UPDATE mettra à jour la table entière. Le plan d'exécution est évident - un scan séquentiel:

 | => EXPLAIN (costs off) | UPDATE accounts SET amount = inc_slow(amount); 
 | QUERY PLAN | ---------------------------- | Update on accounts | -> Seq Scan on accounts | (2 rows) 

Étant donné que les versions des lignes de la page de notre tableau sont dans l'ordre croissant de la somme (exactement comme nous les avons ajoutées), elles seront mises à jour dans le même ordre. Nous commençons la mise à jour pour travailler.

 | => UPDATE accounts SET amount = inc_slow(amount); 

Pendant ce temps, dans une autre session, nous interdirons l'utilisation du balayage séquentiel:

 || => SET enable_seqscan = off; 

Dans ce cas, le planificateur décide d'utiliser l'analyse d'index pour l'instruction UPDATE suivante:

 || => EXPLAIN (costs off) || UPDATE accounts SET amount = inc_slow(amount) WHERE amount > 100.00; 
 || QUERY PLAN || -------------------------------------------------------- || Update on accounts || -> Index Scan using accounts_amount_idx on accounts || Index Cond: (amount > 100.00) || (3 rows) 

Les deuxième et troisième lignes relèvent de la condition et, puisque l'index est construit dans l'ordre décroissant, les lignes seront mises à jour dans l'ordre inverse.

Nous lançons la prochaine mise à jour.

 || => UPDATE accounts SET amount = inc_slow(amount) WHERE amount > 100.00; 

Un rapide coup d'œil à la page tabulaire montre que le premier opérateur a déjà réussi à mettre à jour la première ligne (0,1), et la seconde - la dernière (0,3):

 => SELECT * FROM pgrowlocks('accounts') \gx 
 -[ RECORD 1 ]----------------- locked_row | (0,1) locker | 530699 <-  multi | f xids | {530699} modes | {"No Key Update"} pids | {16513} -[ RECORD 2 ]----------------- locked_row | (0,3) locker | 530700 <-  multi | f xids | {530700} modes | {"No Key Update"} pids | {16549} 

Une autre seconde passe. Le premier opérateur a mis à jour la deuxième ligne et le second souhaite le faire, mais ne peut pas.

 => SELECT * FROM pgrowlocks('accounts') \gx 
 -[ RECORD 1 ]----------------- locked_row | (0,1) locker | 530699 <-  multi | f xids | {530699} modes | {"No Key Update"} pids | {16513} -[ RECORD 2 ]----------------- locked_row | (0,2) locker | 530699 <-    multi | f xids | {530699} modes | {"No Key Update"} pids | {16513} -[ RECORD 3 ]----------------- locked_row | (0,3) locker | 530700 <-  multi | f xids | {530700} modes | {"No Key Update"} pids | {16549} 

Maintenant, la première instruction souhaite mettre à jour la dernière ligne du tableau, mais elle est déjà verrouillée par la seconde. Voici l'impasse.

L'une des transactions est abandonnée:

 || ERROR: deadlock detected || DETAIL: Process 16549 waits for ShareLock on transaction 530699; blocked by process 16513. || Process 16513 waits for ShareLock on transaction 530700; blocked by process 16549. || HINT: See server log for query details. || CONTEXT: while updating tuple (0,2) in relation "accounts" 

Et l'autre termine l'exécution:

 | UPDATE 3 

Des détails intéressants sur la détection et la prévention des interblocages peuvent être trouvés dans le gestionnaire de verrous README .

C'est tout à propos des verrous mortels, et nous procédons aux verrous d'objet restants.



Verrous sans relation


Lorsque vous souhaitez verrouiller une ressource qui n'est pas une relation dans la compréhension de PostgreSQL, des verrous d'objet sont utilisés. Une telle ressource peut être à peu près n'importe quoi: espaces table, abonnements, schémas, rôles, types de données énumérés ... En gros, tout ce qui peut être trouvé dans le catalogue système.

Regardons un exemple simple. Nous commençons la transaction et créons une table dedans:

 => BEGIN; => CREATE TABLE example(n integer); 

Voyons maintenant quel type d'objet les verrous sont apparus dans pg_locks:

 => SELECT database, (SELECT datname FROM pg_database WHERE oid = l.database) AS dbname, classid, (SELECT relname FROM pg_class WHERE oid = l.classid) AS classname, objid, mode, granted FROM pg_locks l WHERE l.locktype = 'object' AND l.pid = pg_backend_pid(); 
  database | dbname | classid | classname | objid | mode | granted ----------+--------+---------+--------------+-------+-----------------+--------- 0 | | 1260 | pg_authid | 16384 | AccessShareLock | t 16386 | test | 2615 | pg_namespace | 2200 | AccessShareLock | t (2 rows) 

Pour comprendre exactement ce qui est bloqué ici, vous devez regarder trois champs: base de données, classid et objid. Commençons par la première ligne.

La base de données est l'OID de la base de données à laquelle appartient la ressource verrouillée. Dans notre cas, il y a zéro dans cette colonne. Cela signifie que nous avons affaire à un objet global qui n'appartient à aucune base particulière.

Classid contient l'OID de pg_class, qui correspond au nom de la table de catalogue système, qui détermine le type de ressource. Dans notre cas, pg_authid, c'est-à-dire que le rôle est la ressource (utilisateur).

Objid contient l'OID de la table de catalogue système que classid nous a indiqué.

 => SELECT rolname FROM pg_authid WHERE oid = 16384; 
  rolname --------- student (1 row) 

Ainsi, le rôle étudiant est bloqué, à partir duquel nous travaillons.

Passons maintenant à la deuxième ligne. La base de données est indiquée, et c'est la base de données de test à laquelle nous sommes connectés.

Classid pointe vers la table pg_namespace qui contient les schémas.

 => SELECT nspname FROM pg_namespace WHERE oid = 2200; 
  nspname --------- public (1 row) 

Ainsi, le schéma public est bloqué.

Ainsi, nous avons vu que lors de la création d'un objet, le rôle de propriétaire et le schéma dans lequel l'objet est créé sont bloqués (en mode partagé). Ce qui est logique: sinon, quelqu'un pourrait supprimer le rôle ou le schéma alors que la transaction n'est pas encore terminée.

 => ROLLBACK; 

Verrou d'extension de relation


Lorsque le nombre de lignes dans une relation (c'est-à-dire dans une table, un index, une vue matérialisée) augmente, PostgreSQL peut utiliser l'espace libre dans les pages existantes pour insérer, mais, bien sûr, à un moment donné, vous devez ajouter de nouvelles pages. Physiquement, ils sont ajoutés à la fin du fichier correspondant. Ceci est compris comme élargissant la relation .

Pour éviter que deux processus ne se précipitent pour ajouter des pages en même temps, ce processus est protégé par un verrou spécial de type extend. Le même verrou est utilisé lors du nettoyage des index afin que d'autres processus ne puissent pas ajouter de pages pendant la numérisation.

Bien entendu, ce verrou est libéré sans attendre la fin de la transaction.

Auparavant, les tableaux n'étaient développés qu'une seule page à la fois. Cela a causé des problèmes lorsque plusieurs processus ont inséré simultanément des lignes, par conséquent, dans PostgreSQL 9.6, plusieurs pages ont été ajoutées aux tables à la fois (proportionnellement au nombre de processus en attente de verrouillage, mais pas plus de 512).

Verrouillage de page


Un verrou au niveau de la page est appliqué dans le seul cas (à l'exception des verrous de prédicat, qui sont abordés plus loin).

Les index GIN vous permettent d'accélérer la recherche dans des valeurs composées, par exemple, des mots dans des documents texte (ou des éléments dans des tableaux). En première approximation, de tels index peuvent être représentés comme un arbre B régulier, dans lequel non les documents eux-mêmes sont stockés, mais les mots individuels de ces documents. Par conséquent, lors de l'ajout d'un nouveau document, l'index doit être reconstruit assez fortement, en y introduisant chaque mot inclus dans le document.

Pour améliorer les performances, les index GIN ont une fonction d'insertion retardée qui est activée par l'option de stockage fastupdate. Les nouveaux mots sont d'abord rapidement ajoutés à la liste en attente non ordonnée, et après un certain temps, tout ce qui s'est accumulé est déplacé vers la structure d'index principale. Les économies sont dues au fait que différents documents sont susceptibles de contenir des mots en double.

Pour empêcher plusieurs processus de passer de la liste d'attente à l'index principal en même temps, la méta-page d'index est bloquée en mode exclusif pendant la durée du transfert. Cela n'interfère pas avec l'utilisation de l'index en mode normal.

Serrures consultatives


Contrairement à d'autres verrous (tels que les verrous de relation), les verrous consultatifs ne sont jamais définis automatiquement, ils sont gérés par le développeur de l'application. Ils sont pratiques à utiliser, par exemple, si une application a besoin d'une logique de blocage à des fins qui ne rentrent pas dans la logique standard des verrous ordinaires.

Supposons que nous ayons une ressource conditionnelle qui ne correspond à aucun objet de base de données (que nous pourrions bloquer avec des commandes comme SELECT FOR ou LOCK TABLE). Vous devez trouver un identifiant numérique pour cela. Si la ressource a un nom unique, une option simple consiste à en extraire un code de hachage:

 => SELECT hashtext('1'); 
  hashtext ----------- 243773337 (1 row) 

Voici comment nous capturons le verrou:

 => BEGIN; => SELECT pg_advisory_lock(hashtext('1')); 

Comme d'habitude, les informations de verrouillage sont disponibles dans pg_locks:

 => SELECT locktype, objid, mode, granted FROM pg_locks WHERE locktype = 'advisory' AND pid = pg_backend_pid(); 
  locktype | objid | mode | granted ----------+-----------+---------------+--------- advisory | 243773337 | ExclusiveLock | t (1 row) 

Pour qu'un verrou fonctionne réellement, les autres processus doivent également obtenir un verrou avant d'accéder à la ressource. Le respect de cette règle doit évidemment être assuré par la demande.

Dans l'exemple ci-dessus, le verrou est valide jusqu'à la fin de la session, et non la transaction, comme d'habitude.

 => COMMIT; => SELECT locktype, objid, mode, granted FROM pg_locks WHERE locktype = 'advisory' AND pid = pg_backend_pid(); 
  locktype | objid | mode | granted ----------+-----------+---------------+--------- advisory | 243773337 | ExclusiveLock | t (1 row) 

Il doit être explicitement publié:

 => SELECT pg_advisory_unlock(hashtext('1')); 

Il existe un large éventail de fonctions pour travailler avec des verrous consultatifs pour toutes les occasions:

  • pg_advisory_lock_shared traite un verrou partagé,
  • pg_advisory_xact_lock (et pg_advisory_xact_lock_shared) obtient un verrou jusqu'à la fin de la transaction,
  • pg_try_advisory_lock (ainsi que pg_try_advisory_xact_lock et pg_try_advisory_xact_lock_shared) ne s'attend pas à recevoir un verrou, mais renvoie une valeur fausse si le verrou n'a pas pu être obtenu immédiatement.

L'ensemble des fonctions d'essai fournit une autre façon de ne pas attendre un verrou, en plus de celles répertoriées dans un article précédent .

Verrous de prédicat


Le terme verrouillage de prédicat est apparu il y a longtemps, lors des premières tentatives d'implémentation d'une isolation complète basée sur des verrous dans les premiers SGBD (le niveau est sérialisable, bien que la norme SQL n'existait pas à l'époque). Le problème qui a ensuite été rencontré était que même le blocage de toutes les lignes lues et modifiées ne fournit pas une isolation complète: de nouvelles lignes peuvent apparaître dans le tableau qui tombent dans les mêmes conditions de sélection, ce qui conduit à des fantômes (voir l' article sur l'isolement ) .

L'idée des verrous de prédicat était de bloquer les prédicats, pas les lignes. Si, lors de l'exécution d'une requête avec la condition a > 10, le prédicat a > 10 est bloqué, cela n'ajoutera pas de nouvelles lignes à la table qui relèvent de la condition et permettra d'éviter les fantômes. Le problème est que dans le cas général, c'est une tâche difficile sur le plan des calculs; en pratique, il ne peut être résolu que pour les prédicats qui ont une forme très simple.

Dans PostgreSQL, la couche Serializable est implémentée différemment, en plus de l'isolement basé sur l'instantané existant. Le terme verrou de prédicat reste, mais sa signification a radicalement changé. En fait, ces «verrous» ne bloquent rien, mais sont utilisés pour suivre les dépendances de données entre les transactions.

Il est prouvé que l'isolement basé sur les images permet une anomalie d'enregistrement incohérent et une anomalie de seulement une transaction de lecture , mais aucune autre anomalie n'est possible. Pour comprendre que nous avons affaire à l'une des deux anomalies répertoriées, nous pouvons analyser les dépendances entre les transactions et y trouver certains modèles.

Nous nous intéressons à deux types de dépendances:

  • une transaction lit une ligne, qui est ensuite modifiée par une autre transaction (dépendance RW),
  • une transaction modifie la ligne qu'une autre transaction lit ensuite (dépendance WR).

Les dépendances WR peuvent être suivies à l'aide de verrous conventionnels existants, mais les dépendances RW n'ont qu'à suivre en plus.

Je répète encore une fois: malgré le nom, les verrous de prédicat ne bloquent rien. Au lieu de cela, lorsqu'une transaction est validée, une vérification est effectuée et, si une «mauvaise» séquence de dépendances est détectée, ce qui peut indiquer une anomalie, la transaction est interrompue.

Voyons comment l'installation des verrous de prédicat se produit. Pour ce faire, créez une table avec un nombre de lignes suffisamment important et un index dessus.

 => CREATE TABLE pred(n integer); => INSERT INTO pred(n) SELECT gn FROM generate_series(1,10000) g(n); => CREATE INDEX ON pred(n) WITH (fillfactor = 10); => ANALYZE pred; 

Si la requête est exécutée par analyse séquentielle de la table entière, le verrou de prédicat est défini sur toute la table (même si toutes les lignes ne sont pas soumises aux conditions de filtrage).

 | => SELECT pg_backend_pid(); 
 | pg_backend_pid | ---------------- | 12763 | (1 row) 

 | => BEGIN ISOLATION LEVEL SERIALIZABLE; | => EXPLAIN (analyze, costs off) | SELECT * FROM pred WHERE n > 100; 
 | QUERY PLAN | ---------------------------------------------------------------- | Seq Scan on pred (actual time=0.047..12.709 rows=9900 loops=1) | Filter: (n > 100) | Rows Removed by Filter: 100 | Planning Time: 0.190 ms | Execution Time: 15.244 ms | (5 rows) 

Tous les verrous de prédicat sont toujours capturés dans un mode SIReadLock (Serializable Isolation Read) spécial:

 => SELECT locktype, relation::regclass, page, tuple FROM pg_locks WHERE mode = 'SIReadLock' AND pid = 12763; 
  locktype | relation | page | tuple ----------+----------+------+------- relation | pred | | (1 row) 

 | => ROLLBACK; 

Mais si la requête est exécutée à l'aide de l'analyse d'index, la situation change pour le mieux. Si nous parlons de l'arbre B, il suffit de définir le verrou sur les lignes de la table de lecture et sur les pages feuillues de l'index - nous bloquons ainsi non seulement des valeurs spécifiques, mais également toute la plage lue.

 | => BEGIN ISOLATION LEVEL SERIALIZABLE; | => EXPLAIN (analyze, costs off) | SELECT * FROM pred WHERE n BETWEEN 1000 AND 1001; 
 | QUERY PLAN | ------------------------------------------------------------------------------------ | Index Only Scan using pred_n_idx on pred (actual time=0.122..0.131 rows=2 loops=1) | Index Cond: ((n >= 1000) AND (n <= 1001)) | Heap Fetches: 2 | Planning Time: 0.096 ms | Execution Time: 0.153 ms | (5 rows) 

 => SELECT locktype, relation::regclass, page, tuple FROM pg_locks WHERE mode = 'SIReadLock' AND pid = 12763; 
  locktype | relation | page | tuple ----------+------------+------+------- tuple | pred | 3 | 236 tuple | pred | 3 | 235 page | pred_n_idx | 22 | (3 rows) 

Vous pouvez remarquer plusieurs difficultés.

Tout d'abord, un verrou distinct est créé pour chaque version de la ligne lue, mais il peut y avoir de nombreuses versions de ce type. Le nombre total de verrous de prédicat dans le système est limité par le produit des valeurs des paramètres max_pred_locks_per_transaction × max_connections (les valeurs par défaut sont respectivement 64 et 100). La mémoire pour ces verrous est allouée au démarrage du serveur; tenter de dépasser ce nombre entraînera des erreurs.

Par conséquent, pour les verrous de prédicat (et uniquement pour eux!), Une augmentation de niveau est utilisée. Avant PostgreSQL 10, il y avait des restrictions qui étaient câblées dans le code, et pour commencer, vous pouvez contrôler les paramètres en augmentant le niveau. Si le nombre de verrous de version de ligne par ligne est supérieur à max_pred_locks_per_page , ces verrous sont remplacés par un verrou de niveau page. Voici un exemple:

 => SHOW max_pred_locks_per_page; 
  max_pred_locks_per_page ------------------------- 2 (1 row) 

 | => EXPLAIN (analyze, costs off) | SELECT * FROM pred WHERE n BETWEEN 1000 AND 1002; 
 | QUERY PLAN | ------------------------------------------------------------------------------------ | Index Only Scan using pred_n_idx on pred (actual time=0.019..0.039 rows=3 loops=1) | Index Cond: ((n >= 1000) AND (n <= 1002)) | Heap Fetches: 3 | Planning Time: 0.069 ms | Execution Time: 0.057 ms | (5 rows) 

Au lieu de trois verrous de tuple, nous voyons un type de page:

 => SELECT locktype, relation::regclass, page, tuple FROM pg_locks WHERE mode = 'SIReadLock' AND pid = 12763; 
  locktype | relation | page | tuple ----------+------------+------+------- page | pred | 3 | page | pred_n_idx | 22 | (2 rows) 

De même, si le nombre de verrous de page associés à une seule relation dépasse max_pred_locks_per_relation , ces verrous sont remplacés par un verrou de niveau de relation.

Il n'y a pas d'autres niveaux: les verrous de prédicat ne sont capturés que pour les relations, les pages ou les versions de lignes, et toujours avec le mode SIReadLock.

Bien entendu, une augmentation du niveau des verrous conduit inévitablement au fait qu'un plus grand nombre de transactions entraînera faussement une erreur de sérialisation et, par conséquent, le débit du système diminuera. Ici, vous devez rechercher un équilibre entre la consommation de mémoire et les performances.

La deuxième difficulté est que dans diverses opérations avec l'index (par exemple, en raison du fractionnement des pages d'index lors de l'insertion de nouvelles lignes), le nombre de pages de feuille couvrant la plage de lecture peut changer. Mais la mise en œuvre de cela prend en compte:

 => INSERT INTO pred SELECT 1001 FROM generate_series(1,1000); => SELECT locktype, relation::regclass, page, tuple FROM pg_locks WHERE mode = 'SIReadLock' AND pid = 12763; 
  locktype | relation | page | tuple ----------+------------+------+------- page | pred | 3 | page | pred_n_idx | 211 | page | pred_n_idx | 212 | page | pred_n_idx | 22 | (4 rows) 

 | => ROLLBACK; 

Soit dit en passant, les verrous de prédicat ne sont pas toujours supprimés immédiatement après la fin de la transaction, car ils sont nécessaires pour suivre les dépendances entre plusieurs transactions. Mais en tout cas, ils sont gérés automatiquement.

Tous les types d'index de PostgreSQL ne prennent pas en charge les verrous de prédicat. Auparavant, seuls les arbres B pouvaient s'en vanter, mais dans PostgreSQL 11, la situation s'est améliorée: les index de hachage, GiST et GIN ont été ajoutés à la liste. Si l'accès à l'index est utilisé et que l'index ne fonctionne pas avec les verrous de prédicat, alors l'index entier est verrouillé sur le verrou. Bien sûr, cela augmente également le nombre de fausses interruptions de transaction.

En conclusion, je note que c'est avec l'utilisation de verrous de prédicat qu'il existe une restriction qui, pour garantir une isolation complète, toutes les transactions doivent fonctionner au niveau sérialisable. Si une transaction utilise un niveau différent, elle ne définira tout simplement pas (et ne vérifiera pas) les verrous de prédicat.

Par tradition, je vais laisser un lien vers README sur les verrous de prédicat , à partir duquel vous pouvez commencer à étudier le code source.

À suivre .

Source: https://habr.com/ru/post/fr465263/


All Articles