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 :
-
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
etxmax
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
. -
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
.
-
Au pointeur de ligne (
lp
) 1 nous trouvons l'ancien tuple mis à jour. Il avait à l'originectid = (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 leINSERT ... ON CONFLICT
). Ce tuple n'est plus visible et sera supprimé pendantVACUUM
.t_infomask
montre quexmin
etxmax
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 (voirsrc/backend/access/heap/README.HOT
). -
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, etxmax
contient unKEY SHARE
verrou de ligne (qui n'est plus pertinent puisque la transaction est terminée). Ce verrou de ligne a été pris pendantINSERT ... ON CONFLICT
En traitement.t_infomask2
montre qu'il s'agit d'un tuple HOT. -
Au pointeur de ligne 3, nous voyons la ligne nouvellement insérée.
t_infomask
montre quexmin
est valide etxmax
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.