¿Cómo se determina exactamente la visibilidad de la fila?

10

En el caso más simple, cuando insertamos una nueva fila en una tabla (y la transacción se confirma), será visible para todas las transacciones posteriores. Ver xmaxsiendo 0 en este ejemplo:

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

Cuando lo actualizamos (porque el indicador se configuró FALSEpor accidente), cambia un poco:

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 acuerdo con el modelo MVCC que utiliza PostgreSQL, se escribió una nueva fila física y se anuló la anterior (esto se puede ver desde ctid). El nuevo todavía es visible para todas las transacciones posteriores.

Ahora sucede algo interesante cuando retrocedemos 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

La versión de fila permanece igual, pero ahora xmaxestá configurada en algo. A pesar de esto, las transacciones posteriores pueden ver esta fila (sin cambios).

Después de leer un poco sobre esto, puede descubrir algunas cosas sobre la visibilidad de la fila. Existe el mapa de visibilidad , pero solo indica si una página completa es visible; definitivamente no funciona en el nivel de fila (tupla). Luego está el registro de confirmación (aka clog), pero ¿cómo se da cuenta Postgres si tiene que visitarlo?

Decidí echar un vistazo a los bits de infomask para descubrir cómo funciona realmente la visibilidad. Para verlos, la forma más fácil es usar la extensión pageinspect . Para saber qué bits están configurados, creé una tabla para almacenarlos:

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));

Luego verifiqué lo que hay dentro de mi vistabla: tenga en cuenta que pageinspectmuestra el contenido físico del montón, por lo que no solo se devuelven las filas visibles:

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

Lo que entiendo de lo anterior es que la primera versión cobró vida con la transacción 2699, luego se reemplazó con éxito por la nueva versión a las 2700.
Luego, la siguiente, que estaba viva desde 2700, tuvo un intento de UPDATEreversión en 2702, visto desde HEAP_XMAX_INVALID.
El último nunca nació realmente, como lo demuestra HEAP_XMIN_INVALID.

Entonces, adivinando lo anterior, el primer y el último caso son obvios: ya no son visibles para la transacción 2703 o superior.
El segundo tiene que buscarse en alguna parte, supongo que es el registro de confirmación, también conocido como clog.

Para complicar aún más los problemas, un UPDATEresultado posterior en lo siguiente:

 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

Aquí ya veo dos candidatos que podrían ser visibles. Entonces, finalmente, aquí están mis preguntas:

  • ¿Es mi suposición de que cloges el lugar para mirar para determinar la visibilidad en estos casos?
  • ¿Qué banderas (o combinación de banderas) le dicen al sistema que visite clog?
  • ¿Hay alguna manera de examinar lo que hay dentro del clog? Hay menciones sobre la clogcorrupción en versiones anteriores de Postgres y una pista de que uno puede construir un archivo falso manualmente. Esta información ayudaría mucho con ella.
dezso
fuente

Respuestas:

6

Entonces, adivinando lo anterior, el primer y el último caso son obvios: ya no son visibles para la transacción 2703 o superior. El segundo tiene que buscarse en alguna parte, supongo que es el registro de confirmación, también conocido como obstrucción.

El segundo tiene HEAP_XMAX_INVALID. Eso significa que no tiene que consultar la obstrucción, porque alguien ya lo ha hecho, visto que xmaxse cancela, y establece un "bit de pista" para que los procesos futuros no necesiten visitar la obstrucción nuevamente para esa fila.

¿Qué banderas (o combinación de banderas) le dicen al sistema que visite la obstrucción?

Si no hay heap_xmin_committedo heap_xmin_invalid, entonces debe visitar la obstrucción para ver cuál era la disposición de xmin. Si la transacción aún está en progreso, entonces la fila no es visible para usted y no puede establecer ninguna marca. Si la transacción se confirma o se revierte, usted establece heap_xmin_committedo en heap_xmin_invalidconsecuencia (si es conveniente hacerlo, no es obligatorio) para que las personas futuras no necesiten buscarlo.

Si xmines válido y confirmado, y si xmaxno es cero, y no hay heap_max_committedo heap_max_invalid, entonces debe visitar la obstrucción para ver cuál fue la disposición de esa transacción.

¿Hay alguna manera de examinar lo que hay dentro de la obstrucción? Hay menciones sobre la corrupción de obstrucciones en versiones anteriores de Postgres y una pista de que uno puede construir un archivo falso manualmente. Esta información ayudaría mucho con ella.

No conozco una forma fácil de hacerlo. Puede usar "od" para volcar los archivos de obstrucción de una manera adecuada para inspeccionarlos y averiguar dónde inspeccionar utilizando las macros definidas ensrc/backend/access/transam/clog.c

Me sorprende que no haya extensiones en PGXN que hagan el trabajo por usted, pero no pude encontrar una. Pero creo que no sería tan útil, porque realmente necesita poder hacer esto mientras su servidor no se está ejecutando.

jjanes
fuente
4

Eche un vistazo a la implementación de HeapTupleSatisfiesMVCC () : la clogverificación real ocurre en TransactionIdDidCommit () , pero solo se llama si el estado de la transacción no se puede inferir de los bits infomask (macro y amigos HeapTupleHeaderXminCommitted () ).

He rastreado el acceso a las pg_clogfunciones TransactionDidCommit()y TransactionDidAbort(), luego, he buscado dónde se llaman y el único lugar en el código relacionado con su pregunta parece estar HeapTupleSatisfiesMVCC(). Del código de esta función puede ver que la búsqueda de obstrucción real solo puede ocurrir si la tupla no tiene los bits de infomask relacionados: el código comienza con la comprobación de los bits con HeapTupleHeaderXminCommitted()et al. Y la búsqueda de obstrucciones solo ocurre si los bits no están establecidos.

alex
fuente