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 entrexminexmaxpode 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.
-
xmaxtambé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,xmaxconterá o ID da transação de bloqueio. Se mais de uma transação tiver um bloqueio na linha,xmaxconté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_infomaskmostra que tantoxminexmaxpertencem a transações confirmadas e, consequentemente, mostram quando as tuplas foram criadas e excluídas.t_infomask2mostra 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_infomaskmostra que esta tupla é o resultado de uma atualização,xminé válido exmaxcontém umKEY SHAREbloqueio de linha (que não é mais relevante desde que a transação foi concluída). Este bloqueio de linha foi obtido duranteINSERT ... ON CONFLICTem processamento.t_infomask2mostra que esta é uma tupla HOT.
-
No ponteiro de linha 3, vemos a linha recém-inserida.
t_infomaskmostra 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.