sql >> Base de Datos >  >> RDS >> PostgreSQL

PostgreSQL Upsert diferencia las filas insertadas y actualizadas usando las columnas del sistema XMIN, XMAX y otras

Creo que esta es una pregunta interesante que merece una respuesta en profundidad; por favor tengan paciencia conmigo si es un poco largo.

En resumen:su suposición es correcta y puede usar el siguiente RETURNING cláusula para determinar si la fila se insertó y no se actualizó:

RETURNING (xmax = 0) AS inserted

Ahora la explicación detallada:

Cuando se actualiza una fila, PostgreSQL no modifica los datos, sino que crea una nueva versión de la fila; la versión anterior será eliminada por autovacuum cuando ya no se necesita. Una versión de una fila se denomina tupla , por lo que en PostgreSQL puede haber más de una tupla por fila.

xmax tiene dos propósitos diferentes:

  1. Como se indica en la documentación, puede ser el ID de transacción de la transacción que eliminó (o actualizó) la tupla ("tupla" es otra palabra para "fila"). Solo transacciones con un ID de transacción entre xmin y xmax puede ver la tupla. Una tupla antigua se puede eliminar de forma segura si no hay ninguna transacción con un ID de transacción inferior a xmax .

  2. xmax también se utiliza para almacenar bloqueos de fila . En PostgreSQL, los bloqueos de fila no se almacenan en la tabla de bloqueo, sino en la tupla para evitar el desbordamiento de la tabla de bloqueo.
    Si solo una transacción tiene un bloqueo en la fila, xmax contendrá el ID de transacción de la transacción de bloqueo. Si más de una transacción tiene un bloqueo en la fila, xmax contiene el número de un llamado multixact , que es una estructura de datos que a su vez contiene los ID de transacción de las transacciones de bloqueo.

La documentación de xmax no está completo, porque el significado exacto de este campo se considera un detalle de implementación y no se puede entender sin conocer t_infomask de la tupla, que no es inmediatamente visible a través de SQL.

Puede instalar el módulo de contribución pageinspect para ver este y otros campos de una tupla.

Ejecuté tu ejemplo, y esto es lo que veo cuando uso heap_page_items función para examinar los detalles (los números de identificación de la transacción son, por supuesto, diferentes en mi 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)

Los significados de t_infomask y t_infomask2 se puede encontrar en src/include/access/htup_details.h . lp_off es el desplazamiento de los datos de la tupla en la página, y t_ctid es el ID de tupla actual que consiste en el número de página y un número de tupla dentro de la página. Dado que la tabla se creó recientemente, todos los datos están en la página 0.

Permítanme discutir las tres filas devueltas por heap_page_items .

  1. En puntero de línea (lp ) 1 encontramos la tupla antigua actualizada. Originalmente tenía ctid = (0,1) , pero se modificó para contener el ID de tupla de la versión actual durante la actualización. La Tupla fue creada por la transacción 102507 e invalidada por la transacción 102508 (la transacción que emitió el INSERT ... ON CONFLICT ). Esta tupla ya no es visible y se eliminará durante VACUUM .

    t_infomask muestra que tanto xmin y xmax pertenecen a transacciones comprometidas y, en consecuencia, muestran cuándo se crearon y eliminaron las tuplas. t_infomask2 muestra que la tupla se actualizó con HOT (tupla de almacenamiento dinámico ), lo que significa que la tupla actualizada está en la misma página que la tupla original y no se modificó ninguna columna indexada (ver src/backend/access/heap/README.HOT ).

  2. En el puntero de línea 2, vemos la tupla nueva y actualizada que se creó mediante la transacción INSERT ... ON CONFLICT (transacción 102508).

    t_infomask muestra que esta tupla es el resultado de una actualización, xmin es válido, y xmax contiene una KEY SHARE bloqueo de fila (que ya no es relevante ya que la transacción se ha completado). Este bloqueo de fila se realizó durante INSERT ... ON CONFLICT Procesando. t_infomask2 muestra que esta es una tupla HOT.

  3. En el puntero de línea 3 vemos la fila recién insertada.

    t_infomask muestra que xmin es válido y xmax es inválido. xmax se establece en 0 porque este valor siempre se usa para tuplas recién insertadas.

Entonces el xmax distinto de cero de la fila actualizada es un artefacto de implementación causado por un bloqueo de fila. Es concebible que INSERT ... ON CONFLICT se vuelve a implementar algún día para que este comportamiento cambie, pero creo que es poco probable.