Acho que esta é uma pergunta interessante que merece uma resposta aprofundada; por favor, tenha paciência comigo se for um pouco longo.
Resumindo:seu palpite está certo e você pode usar o seguinte
RETURNING
cláusula para determinar se a linha foi inserida e não atualizada:RETURNING (xmax = 0) AS inserted
Agora a explicação detalhada:
Quando uma linha é atualizada, o PostgreSQL não modifica os dados, mas cria uma nova versão da fila; a versão antiga será excluída por autovacuum quando não for mais necessário. Uma versão de uma linha é chamada de tupla , então no PostgreSQL pode haver mais de uma tupla por linha.
xmax
serve a dois propósitos diferentes:-
Conforme indicado na documentação, pode ser o ID da transação que excluiu (ou atualizou) a tupla (“tupla” é outra palavra para “linha”). Somente transações com um ID de transação entrexmin
exmax
pode ver a tupla. Uma tupla antiga pode ser excluída com segurança se não houver transação com um ID de transação menor quexmax
.
-
xmax
também é usado para armazenar bloqueios de linha . No PostgreSQL, os bloqueios de linha não são armazenados na tabela de bloqueio, mas na tupla para evitar estouro da tabela de bloqueio.
Se apenas uma transação tiver um bloqueio na linha,xmax
conterá o ID da transação de bloqueio. Se mais de uma transação tiver um bloqueio na linha,xmax
contém o número de um chamado multixact , que é uma estrutura de dados que, por sua vez, contém os IDs das transações de bloqueio.
A documentação de
xmax
não está completo, pois o significado exato deste campo é considerado um detalhe de implementação e não pode ser entendido sem o conhecimento de t_infomask
da tupla, que não é imediatamente visível via SQL. Você pode instalar o módulo contrib
pageinspect
para visualizar este e outros campos de uma tupla. Executei seu exemplo e é isso que vejo quando uso o
heap_page_items
função para examinar os detalhes (os números de ID da transação são obviamente diferentes no meu caso):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)
Os significados de
t_infomask
e t_infomask2
pode ser encontrado em src/include/access/htup_details.h
. lp_off
é o deslocamento dos dados da tupla na página e t_ctid
é o ID da tupla atual que consiste no número da página e um número de tupla dentro da página. Como a tabela foi criada recentemente, todos os dados estão na página 0. Deixe-me discutir as três linhas retornadas por
heap_page_items
. -
No ponteiro de linha (lp
) 1 encontramos a tupla antiga e atualizada. Originalmente tinhactid = (0,1)
, mas que foi modificado para conter o ID da tupla da versão atual durante a atualização. A Tupla foi criada pela transação 102507 e invalidada pela transação 102508 (a transação que emitiu oINSERT ... ON CONFLICT
). Esta tupla não está mais visível e será removida duranteVACUUM
.
t_infomask
mostra que tantoxmin
exmax
pertencem a transações confirmadas e, consequentemente, mostram quando as tuplas foram criadas e excluídas.t_infomask2
mostra que a tupla foi actualizada com um HOT (tupla apenas de pilha ) update, o que significa que a tupla atualizada está na mesma página que a tupla original e nenhuma coluna indexada foi modificada (consultesrc/backend/access/heap/README.HOT
).
-
No ponteiro de linha 2 vemos a nova tupla atualizada que foi criada pela transaçãoINSERT ... ON CONFLICT
(transação 102508).
t_infomask
mostra que esta tupla é o resultado de uma atualização,xmin
é válido exmax
contém umKEY SHARE
bloqueio de linha (que não é mais relevante desde que a transação foi concluída). Este bloqueio de linha foi obtido duranteINSERT ... ON CONFLICT
em processamento.t_infomask2
mostra que esta é uma tupla HOT.
-
No ponteiro de linha 3, vemos a linha recém-inserida.
t_infomask
mostra quexmin
é válido exmax
é inválido.xmax
é definido como 0 porque esse valor é sempre usado para tuplas recém-inseridas.
Portanto, o
xmax
diferente de zero da linha atualizada é um artefato de implementação causado por um bloqueio de linha. É concebível que INSERT ... ON CONFLICT
é reimplementado um dia para que esse comportamento mude, mas acho que isso é improvável.