Como exatamente a visibilidade da linha é determinada?


10

No caso mais simples, quando inserimos uma nova linha em uma tabela (e a transação é confirmada), ela fica visível para todas as transações subseqüentes. Veja xmaxsendo 0 neste exemplo:

CREATE TABLE vis (
  id serial,
  is_active boolean
);

INSERT INTO vis (is_active) VALUES (FALSE);

SELECT ctid, xmin, xmax, * FROM vis;

  ctid xmin  xmax  id  is_active 
───────┼─────┼──────┼────┼───────────
 (0,1) 2699     0   1  f

Quando o atualizamos (porque o sinalizador foi definido FALSEpor acidente), ele muda um pouco:

UPDATE vis SET is_active = TRUE;

SELECT ctid, xmin, xmax, * FROM vis;

 ctid  xmin  xmax  id  is_active 
──────┼──────┼──────┼────┼───────────
(0,2)  2700     0   1  t

De acordo com o modelo MVCC que o PostgreSQL usa, uma nova linha física foi gravada e a antiga invalidada (isso pode ser visto no ctid). O novo ainda está visível para todas as transações subseqüentes.

Agora, há uma coisa interessante acontecendo quando revertemos o UPDATE:

BEGIN;

    UPDATE vis SET is_active = TRUE;

ROLLBACK;

SELECT ctid, xmin, xmax, * FROM vis;

 ctid   xmin  xmax  id  is_active 
───────┼──────┼──────┼────┼───────────
 (0,2)  2700  2702   1  t

A versão da linha permanece a mesma, mas agora xmaxestá definida como algo. Apesar disso, as transações subsequentes podem ver esta linha (caso contrário, inalterada).

Depois de ler um pouco sobre isso, você pode descobrir algumas coisas sobre a visibilidade da linha. Existe o mapa de visibilidade , mas apenas informa se uma página inteira está visível - definitivamente não funciona no nível da linha (tupla). Depois, há o log de confirmação (aka clog) - mas como o Postgres descobre se precisa visitá-lo?

Decidi dar uma olhada nos bits da máscara de informação para descobrir como a visibilidade realmente funciona. Para vê-los, a maneira mais fácil é usar a extensão pageinspect . Para descobrir quais bits estão definidos, criei uma tabela para armazená-los:

CREATE TABLE infomask (
  i_flag text,
  i_bits bit(16)
);

INSERT INTO infomask
VALUES 
('HEAP_HASNULL', x'0001'::bit(16)),
('HEAP_HASVARWIDTH', x'0002'::bit(16)),
('HEAP_HASEXTERNAL', x'0004'::bit(16)),
('HEAP_HASOID', x'0008'::bit(16)),
('HEAP_XMAX_KEYSHR_LOCK', x'0010'::bit(16)),
('HEAP_COMBOCID', x'0020'::bit(16)),
('HEAP_XMAX_EXCL_LOCK', x'0040'::bit(16)),
('HEAP_XMAX_LOCK_ONLY', x'0080'::bit(16)),
('HEAP_XMIN_COMMITTED', x'0100'::bit(16)),
('HEAP_XMIN_INVALID', x'0200'::bit(16)),
('HEAP_XMAX_COMMITTED', x'0400'::bit(16)),
('HEAP_XMAX_INVALID', x'0800'::bit(16)),
('HEAP_XMAX_IS_MULTI', x'1000'::bit(16)),
('HEAP_UPDATED', x'2000'::bit(16)),
('HEAP_MOVED_OFF', x'4000'::bit(16)),
('HEAP_MOVED_IN', x'8000'::bit(16)),
('HEAP_XACT_MASK', x'FFF0'::bit(16));

Em seguida, verifiquei o que está dentro da minha vistabela - observe que pageinspectmostra o conteúdo físico da pilha, para que não sejam retornadas apenas as linhas visíveis:

SELECT t_xmin, t_xmax, string_agg(i_flag, ', ') FILTER (WHERE (t_infomask::bit(16) & i_bits)::integer::boolean)
  FROM heap_page_items(get_raw_page('vis', 0)),
       infomask
 GROUP BY t_xmin, t_xmax;

 t_xmin  t_xmax                       string_agg                      
────────┼────────┼──────────────────────────────────────────────────────
   2699    2700  HEAP_XMIN_COMMITTED, HEAP_XMAX_COMMITTED
   2700    2702  HEAP_XMIN_COMMITTED, HEAP_XMAX_INVALID, HEAP_UPDATED
   2702       0  HEAP_XMIN_INVALID, HEAP_XMAX_INVALID, HEAP_UPDATED

O que entendi acima é que a primeira versão ganhou vida com a transação 2699, substituída com sucesso pela nova versão em 2700.
Em seguida, a próxima, que estava viva desde 2700, teve uma tentativa de retroceder UPDATEem 2702, vista de HEAP_XMAX_INVALID.
O último nunca realmente nasceu, como mostra HEAP_XMIN_INVALID.

Portanto, considerando o exposto acima, o primeiro e o último caso são óbvios - eles não são mais visíveis para a transação 2703 ou superior.
O segundo deve ser procurado em algum lugar - suponho que seja o log de confirmação, também conhecido como clog.

Para complicar ainda mais os problemas, um UPDATEresultado subsequente é o seguinte:

 t_xmin  t_xmax                      string_agg                     
────────┼────────┼────────────────────────────────────────────────────
   2699    2700  HEAP_XMIN_COMMITTED, HEAP_XMAX_COMMITTED
   2702       0  HEAP_XMIN_INVALID, HEAP_XMAX_INVALID, HEAP_UPDATED
   2703       0  HEAP_XMAX_INVALID, HEAP_UPDATED
   2700    2703  HEAP_XMIN_COMMITTED, HEAP_UPDATED

Aqui já vejo dois candidatos que podem ser visíveis. Então, finalmente, aqui estão minhas perguntas:

  • É minha suposição de que clogé o lugar para procurar determinar a visibilidade nesses casos?
  • Quais sinalizadores (ou combinação de sinalizadores) indicam ao sistema para visitar o clog?
  • Existe uma maneira de examinar o que há dentro clog? Há menções sobre clogcorrupção nas versões anteriores do Postgres e uma dica de que se pode criar um arquivo falso manualmente. Esta informação ajudaria muito com isso.

Respostas:


6

Portanto, considerando o exposto acima, o primeiro e o último caso são óbvios - eles não são mais visíveis para a transação 2703 ou superior. O segundo deve ser procurado em algum lugar - suponho que seja o log de confirmação, também conhecido como entupir.

O segundo tem HEAP_XMAX_INVALID. Isso significa que ele não precisa consultar o entupimento, porque alguém já o fez, visto que o xmaxprocedimento foi abortado e defina um "pedaço de dica" para que processos futuros não precisem visitá-lo novamente nessa linha.

Quais sinalizadores (ou combinação de sinalizadores) indicam ao sistema para visitar o entupimento?

Se não houver heap_xmin_committedou heap_xmin_invalid, será necessário visitar o entupimento para ver qual era a disposição do xmin. Se a transação ainda estiver em andamento, a linha não estará visível para você e você não poderá definir nenhum sinalizador. Se a transação for confirmada ou revertida, você definirá heap_xmin_committedou de heap_xmin_invalidacordo (se for conveniente fazer isso - não é obrigatório) para que as pessoas futuras não precisem procurar.

Se xminfor válido e confirmado, e se xmaxnão for zero, e não houver heap_max_committedou heap_max_invalid, será necessário visitar o entupimento para ver qual era a disposição dessa transação.

Existe uma maneira de examinar o que está dentro do entupimento? Existem menções sobre corrupção de entupimento nas versões anteriores do Postgres e uma dica de que se pode criar um arquivo falso manualmente. Esta informação ajudaria muito com isso.

Não conheço uma maneira fácil de fazer isso. Você pode usar "od" para despejar os arquivos de obstrução de maneira adequada para inspecioná-los e descobrir onde inspecionar usando as macros definidas emsrc/backend/access/transam/clog.c

Estou surpreso que não haja extensões no PGXN que funcionem para você, mas não consegui encontrar uma. Mas acho que não seria tão útil, porque você realmente precisa fazer isso enquanto o servidor não está em execução.


4

Dê uma olhada na implementação de HeapTupleSatisfiesMVCC () : a clogverificação real acontece em TransactionIdDidCommit () , mas é chamada apenas se o status da transação não puder ser inferido a partir dos bits da máscara de informação (macro e amigos HeapTupleHeaderXminCommitted () ).

Rastreei o acesso a pg_clogfunções TransactionDidCommit()e TransactionDidAbort(), em seguida, procurei onde elas são chamadas e o único lugar no código relacionado à sua pergunta parece estar HeapTupleSatisfiesMVCC(). A partir do código dessa função, você pode ver que a pesquisa de obstrução real só pode acontecer se a tupla não tiver os bits de máscara de informação relacionados definidos: o código começa com a verificação dos bits com HeapTupleHeaderXminCommitted()et al. E a pesquisa de entupimento ocorre apenas se os bits não estiverem definidos.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.