Comment exactement la visibilité des rangées est-elle déterminée?

10

Dans le cas le plus simple, lorsque nous insérons une nouvelle ligne dans une table (et que la transaction est validée), elle sera visible pour toutes les transactions suivantes. Voir xmaxêtre 0 dans cet exemple:

CREATE TABLE vis (
  id serial,
  is_active boolean
);

INSERT INTO vis (is_active) VALUES (FALSE);

SELECT ctid, xmin, xmax, * FROM vis;

  ctid xmin  xmax  id  is_active 
───────┼─────┼──────┼────┼───────────
 (0,1) 2699     0   1  f

Lorsque nous le mettons à jour (car l'indicateur a été défini FALSEpar accident), il change un peu:

UPDATE vis SET is_active = TRUE;

SELECT ctid, xmin, xmax, * FROM vis;

 ctid  xmin  xmax  id  is_active 
──────┼──────┼──────┼────┼───────────
(0,2)  2700     0   1  t

Selon le modèle MVCC utilisé par PostgreSQL, une nouvelle ligne physique a été écrite et l'ancienne invalidée (cela peut être vu sur ctid). Le nouveau est toujours visible pour toutes les transactions ultérieures.

Maintenant, il se passe quelque chose d'intéressant lorsque nous annulons UPDATE:

BEGIN;

    UPDATE vis SET is_active = TRUE;

ROLLBACK;

SELECT ctid, xmin, xmax, * FROM vis;

 ctid   xmin  xmax  id  is_active 
───────┼──────┼──────┼────┼───────────
 (0,2)  2700  2702   1  t

La version en ligne reste la même, mais xmaxest maintenant définie sur quelque chose. Malgré cela, les transactions suivantes peuvent voir cette ligne (sinon inchangée).

Après avoir lu un peu à ce sujet, vous pouvez comprendre quelques éléments sur la visibilité des lignes. Il y a la carte de visibilité , mais elle indique seulement si une page entière est visible - elle ne fonctionne certainement pas au niveau de la ligne (tuple). Ensuite, il y a le journal de validation (aka clog) - mais comment Postgres détermine-t-il s'il doit le visiter?

J'ai décidé de jeter un coup d'œil aux bits du masque d'information pour comprendre comment la visibilité fonctionne réellement. Pour les voir, le plus simple est d'utiliser l' extension pageinspect . Afin de savoir quels bits sont définis, j'ai créé une table pour les stocker:

CREATE TABLE infomask (
  i_flag text,
  i_bits bit(16)
);

INSERT INTO infomask
VALUES 
('HEAP_HASNULL', x'0001'::bit(16)),
('HEAP_HASVARWIDTH', x'0002'::bit(16)),
('HEAP_HASEXTERNAL', x'0004'::bit(16)),
('HEAP_HASOID', x'0008'::bit(16)),
('HEAP_XMAX_KEYSHR_LOCK', x'0010'::bit(16)),
('HEAP_COMBOCID', x'0020'::bit(16)),
('HEAP_XMAX_EXCL_LOCK', x'0040'::bit(16)),
('HEAP_XMAX_LOCK_ONLY', x'0080'::bit(16)),
('HEAP_XMIN_COMMITTED', x'0100'::bit(16)),
('HEAP_XMIN_INVALID', x'0200'::bit(16)),
('HEAP_XMAX_COMMITTED', x'0400'::bit(16)),
('HEAP_XMAX_INVALID', x'0800'::bit(16)),
('HEAP_XMAX_IS_MULTI', x'1000'::bit(16)),
('HEAP_UPDATED', x'2000'::bit(16)),
('HEAP_MOVED_OFF', x'4000'::bit(16)),
('HEAP_MOVED_IN', x'8000'::bit(16)),
('HEAP_XACT_MASK', x'FFF0'::bit(16));

Ensuite, j'ai vérifié ce qu'il y avait dans ma vistable - notez que cela pageinspectmontre le contenu physique du tas, donc non seulement les lignes visibles sont retournées:

SELECT t_xmin, t_xmax, string_agg(i_flag, ', ') FILTER (WHERE (t_infomask::bit(16) & i_bits)::integer::boolean)
  FROM heap_page_items(get_raw_page('vis', 0)),
       infomask
 GROUP BY t_xmin, t_xmax;

 t_xmin  t_xmax                       string_agg                      
────────┼────────┼──────────────────────────────────────────────────────
   2699    2700  HEAP_XMIN_COMMITTED, HEAP_XMAX_COMMITTED
   2700    2702  HEAP_XMIN_COMMITTED, HEAP_XMAX_INVALID, HEAP_UPDATED
   2702       0  HEAP_XMIN_INVALID, HEAP_XMAX_INVALID, HEAP_UPDATED

Ce que je comprends de ce qui précède, c'est que la première version a pris vie avec la transaction 2699, puis a été remplacée avec succès par la nouvelle version à 2700.
Ensuite, la suivante, qui était en vie depuis 2700, a eu une tentative d' UPDATEannulation de 2702, vue de HEAP_XMAX_INVALID.
Le dernier n'est jamais vraiment né, comme le montre HEAP_XMIN_INVALID.

Donc, à en juger par ce qui précède, le premier et le dernier cas sont évidents - ils ne sont plus visibles pour la transaction 2703 ou supérieure.
Le second doit être recherché quelque part - je suppose que c'est le journal de validation, aka clog.

Pour compliquer davantage les problèmes, un UPDATErésultat ultérieur dans les éléments suivants:

 t_xmin  t_xmax                      string_agg                     
────────┼────────┼────────────────────────────────────────────────────
   2699    2700  HEAP_XMIN_COMMITTED, HEAP_XMAX_COMMITTED
   2702       0  HEAP_XMIN_INVALID, HEAP_XMAX_INVALID, HEAP_UPDATED
   2703       0  HEAP_XMAX_INVALID, HEAP_UPDATED
   2700    2703  HEAP_XMIN_COMMITTED, HEAP_UPDATED

Ici, je vois déjà deux candidats qui pourraient être visibles. Alors, enfin, voici mes questions:

  • Est-ce que je suppose que clogc'est le lieu à considérer pour déterminer la visibilité dans ces cas?
  • Quels drapeaux (ou combinaison de drapeaux) indiquent au système de visiter le clog?
  • Existe-t-il un moyen d'examiner ce qu'il y a à l'intérieur clog? Il y a des mentions sur la clogcorruption dans les versions antérieures de Postgres et un indice que l'on peut créer un faux fichier manuellement. Cette information nous aiderait beaucoup.
dezso
la source

Réponses:

6

Donc, à en juger par ce qui précède, le premier et le dernier cas sont évidents - ils ne sont plus visibles pour la transaction 2703 ou supérieure. Le second doit être recherché quelque part - je suppose que c'est le journal de validation, alias clog.

Le 2ème a HEAP_XMAX_INVALID. Cela signifie qu'il n'a pas besoin de consulter le sabot, car quelqu'un l'a déjà fait, vu que le xmaxest abandonné, et défini un "indice" afin que les processus futurs n'aient pas besoin de visiter à nouveau le sabot pour cette ligne.

Quels drapeaux (ou combinaison de drapeaux) indiquent au système de visiter le sabot?

S'il n'y a pas de heap_xmin_committedou heap_xmin_invalid, alors vous devez visiter le sabot pour voir quelle était la disposition de xmin. Si la transaction est toujours en cours, la ligne n'est pas visible pour vous et vous ne pouvez définir aucun indicateur. Si la transaction est validée ou annulée, vous définissez le heap_xmin_committedou en heap_xmin_invalidconséquence (si cela est pratique - ce n'est pas obligatoire) afin que les futurs utilisateurs n'aient pas besoin de la rechercher.

Si xminest valide et engagé, et s'il xmaxn'est pas nul, et qu'il n'y a pas de heap_max_committedou heap_max_invalid, alors vous devez visiter le sabot pour voir quelle a été la disposition de cette transaction.

Existe-t-il un moyen d'examiner ce qui se trouve à l'intérieur du sabot? Il y a des mentions sur la corruption du colmatage dans les versions antérieures de Postgres et un indice que l'on peut créer un faux fichier manuellement. Cette information nous aiderait beaucoup.

Je ne connais pas de méthode conviviale pour le faire. Vous pouvez utiliser "od" pour vider les fichiers de colmatage d'une manière appropriée pour les inspecter, et déterminer où inspecter en utilisant les macros définies danssrc/backend/access/transam/clog.c

Je suis surpris qu'il n'y ait aucune extension sur PGXN qui fasse le travail pour vous, mais je n'ai pas pu en trouver une. Mais je pense que ce ne serait pas très utile, car vous devez vraiment pouvoir le faire pendant que votre serveur ne fonctionne pas.

jjanes
la source
4

Jetez un œil à l' implémentation de HeapTupleSatisfiesMVCC () : la clogvérification réelle se produit dans TransactionIdDidCommit () , mais elle n'est appelée que si l'état de la transaction ne peut pas être déduit des bits d' infomask (macro HeapTupleHeaderXminCommitted () et amis).

J'ai retracé l' accès arrière à pg_clogdes fonctions TransactionDidCommit()et TransactionDidAbort(), je l' ai regardé où ils sont appelés et le seul endroit dans le code lié à votre question semble être HeapTupleSatisfiesMVCC(). D'après le code de cette fonction, vous pouvez voir que la recherche de sabotage réelle ne peut se produire que si le tuple n'a pas les bits de masque d'infomation associés définis: le code commence par vérifier les bits avec HeapTupleHeaderXminCommitted()et al. Et la recherche de blocage ne se produit que si le ou les bits ne sont pas définis.

alex
la source