PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

PostgreSQL Upsert diferencia linhas inseridas e atualizadas usando colunas do sistema XMIN, XMAX e outras


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:

  1. 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 entre xmin e xmax 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 que xmax .

  2. 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 .

  1. No ponteiro de linha (lp ) 1 encontramos a tupla antiga e atualizada. Originalmente tinha ctid = (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 o INSERT ... ON CONFLICT ). Esta tupla não está mais visível e será removida durante VACUUM .

    t_infomask mostra que tanto xmin e xmax 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 (consulte src/backend/access/heap/README.HOT ).

  2. No ponteiro de linha 2 vemos a nova tupla atualizada que foi criada pela transação INSERT ... ON CONFLICT (transação 102508).

    t_infomask mostra que esta tupla é o resultado de uma atualização, xmin é válido e xmax contém um KEY SHARE bloqueio de linha (que não é mais relevante desde que a transação foi concluída). Este bloqueio de linha foi obtido durante INSERT ... ON CONFLICT em processamento. t_infomask2 mostra que esta é uma tupla HOT.

  3. No ponteiro de linha 3, vemos a linha recém-inserida.

    t_infomask mostra que xmin é válido e xmax é 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.