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:
-
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
yxmax
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 axmax
. -
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
.
-
En puntero de línea (
lp
) 1 encontramos la tupla antigua actualizada. Originalmente teníactid = (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ó elINSERT ... ON CONFLICT
). Esta tupla ya no es visible y se eliminará duranteVACUUM
.t_infomask
muestra que tantoxmin
yxmax
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 (versrc/backend/access/heap/README.HOT
). -
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, yxmax
contiene unaKEY SHARE
bloqueo de fila (que ya no es relevante ya que la transacción se ha completado). Este bloqueo de fila se realizó duranteINSERT ... ON CONFLICT
Procesando.t_infomask2
muestra que esta es una tupla HOT. -
En el puntero de línea 3 vemos la fila recién insertada.
t_infomask
muestra quexmin
es válido yxmax
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.