PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

PostgreSQL Upsert différencie les lignes insérées et mises à jour à l'aide des colonnes système XMIN, XMAX et autres

Je pense que c'est une question intéressante qui mérite une réponse approfondie; s'il vous plaît soyez patient si c'est un peu long.

En bref :votre supposition est bonne et vous pouvez utiliser le RETURNING suivant clause pour déterminer si la ligne a été insérée et non mise à jour :

RETURNING (xmax = 0) AS inserted

Maintenant l'explication détaillée :

Lorsqu'une ligne est mise à jour, PostgreSQL ne modifie pas les données, mais crée une nouvelle version de la rangée ; l'ancienne version sera supprimée par autovacuum lorsqu'il n'est plus nécessaire. Une version d'une ligne est appelée un tuple , donc dans PostgreSQL, il peut y avoir plus d'un tuple par ligne.

xmax a deux objectifs différents :

  1. Comme indiqué dans la documentation, il peut s'agir de l'ID de transaction de la transaction qui a supprimé (ou mis à jour) le tuple ("tuple" est un autre mot pour "ligne"). Uniquement les transactions avec un ID de transaction compris entre xmin et xmax peut voir le tuple. Un ancien tuple peut être supprimé en toute sécurité s'il n'y a pas de transaction avec un ID de transaction inférieur à xmax .

  2. xmax est également utilisé pour stocker les verrous de ligne . Dans PostgreSQL, les verrous de ligne ne sont pas stockés dans la table des verrous, mais dans le tuple pour éviter le débordement de la table des verrous.
    Si une seule transaction a un verrou sur la ligne, xmax contiendra l'ID de transaction de la transaction de verrouillage. Si plus d'une transaction a un verrou sur la ligne, xmax contient le numéro d'un soi-disant multixact , qui est une structure de données qui contient à son tour les ID de transaction des transactions de verrouillage.

La documentation de xmax n'est pas complet, car la signification exacte de ce champ est considérée comme un détail d'implémentation et ne peut être comprise sans connaître t_infomask du tuple, qui n'est pas immédiatement visible via SQL.

Vous pouvez installer le module contrib pageinspect pour voir ceci et d'autres champs d'un tuple.

J'ai couru votre exemple, et c'est ce que je vois quand j'utilise le heap_page_items fonction pour examiner les détails (les numéros d'identification de transaction sont bien sûr différents dans mon cas) :

SELECT *, ctid, xmin, xmax FROM t;

┌───┬────┬───────┬────────┬────────┐
│ i │ x  │ ctid  │  xmin  │  xmax  │
├───┼────┼───────┼────────┼────────┤
│ 1 │ 11 │ (0,2) │ 102508 │ 102508 │
│ 2 │ 22 │ (0,3) │ 102508 │      0 │
└───┴────┴───────┴────────┴────────┘
(2 rows)

SELECT lp, lp_off, t_xmin, t_xmax, t_ctid,
       to_hex(t_infomask) AS t_infomask, to_hex(t_infomask2) AS t_infomask2
FROM heap_page_items(get_raw_page('laurenz.t', 0));

┌────┬────────┬────────┬────────┬────────┬────────────┬─────────────┐
│ lp │ lp_off │ t_xmin │ t_xmax │ t_ctid │ t_infomask │ t_infomask2 │
├────┼────────┼────────┼────────┼────────┼────────────┼─────────────┤
│  1 │   8160 │ 102507 │ 102508 │ (0,2)  │ 500        │ 4002        │
│  2 │   8128 │ 102508 │ 102508 │ (0,2)  │ 2190       │ 8002        │
│  3 │   8096 │ 102508 │      0 │ (0,3)  │ 900        │ 2           │
└────┴────────┴────────┴────────┴────────┴────────────┴─────────────┘
(3 rows)

La signification de t_infomask et t_infomask2 peut être trouvé dans src/include/access/htup_details.h . lp_off est le décalage des données de tuple dans la page, et t_ctid est l'ID de tuple actuel qui se compose du numéro de page et d'un numéro de tuple dans la page. Puisque la table a été nouvellement créée, toutes les données sont à la page 0.

Permettez-moi de discuter des trois lignes renvoyées par heap_page_items .

  1. Au pointeur de ligne (lp ) 1 nous trouvons l'ancien tuple mis à jour. Il avait à l'origine ctid = (0,1) , mais qui a été modifié pour contenir l'ID de tuple de la version actuelle lors de la mise à jour. Le Tuple a été créé par la transaction 102507 et invalidé par la transaction 102508 (la transaction qui a émis le INSERT ... ON CONFLICT ). Ce tuple n'est plus visible et sera supprimé pendant VACUUM .

    t_infomask montre que xmin et xmax appartiennent à des transactions validées et indiquent par conséquent quand les tuples ont été créés et supprimés. t_infomask2 montre que le tuple a été mis à jour avec un HOT (tuple de tas uniquement ) update, ce qui signifie que le tuple mis à jour est dans la même page que le tuple d'origine et qu'aucune colonne indexée n'a été modifiée (voir src/backend/access/heap/README.HOT ).

  2. Au pointeur de ligne 2, nous voyons le nouveau tuple mis à jour qui a été créé par la transaction INSERT ... ON CONFLICT (transaction 102508).

    t_infomask montre que ce tuple est le résultat d'une mise à jour, xmin est valide, et xmax contient un KEY SHARE verrou de ligne (qui n'est plus pertinent puisque la transaction est terminée). Ce verrou de ligne a été pris pendant INSERT ... ON CONFLICT En traitement. t_infomask2 montre qu'il s'agit d'un tuple HOT.

  3. Au pointeur de ligne 3, nous voyons la ligne nouvellement insérée.

    t_infomask montre que xmin est valide et xmax est invalide. xmax est défini sur 0 car cette valeur est toujours utilisée pour les tuples nouvellement insérés.

Donc le xmax non nul de la ligne mise à jour est un artefact d'implémentation causé par un verrou de ligne. Il est concevable que INSERT ... ON CONFLICT est réimplémenté un jour pour que ce comportement change, mais je pense que c'est peu probable.