¿Cómo descompongo ctid en números de página y fila?


16

Cada fila de una tabla tiene una columna ctid de tipo de sistematid que representa la ubicación física de la fila:

create table t(id serial);
insert into t default values;
insert into t default values;
select ctid
     , id
from t;
ctid | carné de identidad
: ---- | -:
(0,1) | 1
(0,2) | 2

dbfiddle aquí

¿Cuál es la mejor manera de obtener solo el número de página del ctidtipo más apropiado (por ejemplo integer, biginto numeric(1000,0))?

La única forma en que puedo pensar es muy fea.


1
IIRC es un tipo de vector y no tenemos métodos de acceso en estos. No estoy seguro si puedes hacerlo desde una función C. Craig lo dirá seguro :)
dezso

2
¿Puedes lanzar como POINT? P.ej. select ct[0], ct[1] from (select ctid::text::point as ct from pg_class where ...) y;
bma

1
El título sugiere que está detrás del número de página y del índice de tuplas , luego se reduce al número de página. Fui con la versión en el cuerpo, el índice de tupla es una extensión trivial.
Erwin Brandstetter

Respuestas:


21
SELECT (ctid::text::point)[0]::bigint AS page_number FROM t;

Tu violín con mi solución.

@bma ya insinuó algo similar en un comentario. Aquí hay un ...

Justificación del tipo

ctides de tipo tid(identificador de tupla), llamado ItemPointeren el código C. Por documentación:

Este es el tipo de datos de la columna del sistema ctid. Una ID de tupla es un par ( número de bloque , índice de tupla dentro del bloque ) que identifica la ubicación física de la fila dentro de su tabla.

El énfasis en negrita es mío. Y:

( ItemPointer, también conocido como CTID)

Un bloque es de 8 KB en instalaciones estándar. El tamaño máximo de la mesa es de 32 TB . Se deduce lógicamente que los números de bloque deben acomodar al menos un máximo de (cálculo fijado según el comentario de @Daniel):

SELECT (2^45 / 2^13)::int      -- = 2^32 = 4294967294

Lo que encajaría en un sin firmar integer. En una investigación adicional encontré en el código fuente que ...

Los bloques están numerados secuencialmente, de 0 a 0xFFFFFFFE .

El énfasis en negrita es mío. Lo que confirma el primer cálculo:

SELECT 'xFFFFFFFE'::bit(32)::int8 -- max page number: 4294967294

Postgres usa un entero con signo y, por lo tanto, es un poco corto. No pude precisar, aún, si la representación de texto se desplaza para acomodar un entero con signo. Hasta que alguien pueda aclarar esto, recurriría a bigint, lo que funciona en cualquier caso.

Emitir

No hay elenco registrado para el tidtipo en Postgres 9.3:

SELECT *
FROM   pg_cast
WHERE  castsource = 'tid'::regtype
OR     casttarget = 'tid'::regtype;

 castsource | casttarget | castfunc | castcontext | castmethod
------------+------------+----------+-------------+------------
(0 rows)

Todavía puedes lanzar a text. Hay una representación de texto para todo en Postgres :

Otra excepción importante es que las "conversiones de conversión de E / S automáticas", aquellas realizadas utilizando las propias funciones de E / S de un tipo de datos para convertir hacia o desde texto u otros tipos de cadenas, no están explícitamente representadas en pg_cast.

La representación de texto coincide con la de un punto, que consta de dos float8números, ese reparto no tiene pérdidas.

Puede acceder al primer número de un punto con índice 0. Transmitir a bigint. Voilá

Actuación

Realicé una prueba rápida en una tabla con 30k filas (la mejor de 5) en un par de expresiones alternativas que me vinieron a la mente, incluido el original:

SELECT (ctid::text::point)[0]::int                              --  25 ms
      ,right(split_part(ctid::text, ',', 1), -1)::int           --  28 ms
      ,ltrim(split_part(ctid::text, ',', 1), '(')::int          --  29 ms
      ,(ctid::text::t_tid).page_number                          --  31 ms
      ,(translate(ctid::text,'()', '{}')::int[])[1]             --  45 ms
      ,(replace(replace(ctid::text,'(','{'),')','}')::int[])[1] --  51 ms
      ,substring(right(ctid::text, -1), '^\d+')::int            --  52 ms
      ,substring(ctid::text, '^\((\d+),')::int                  -- 143 ms
FROM tbl;

inten lugar de bigintaquí, mayormente irrelevante para el propósito de la prueba. No repetí para bigint.
El elenco para t_tidconstruir sobre un tipo compuesto definido por el usuario, como @Jake comentó.
La esencia de esto: el casting tiende a ser más rápido que la manipulación de cuerdas. Las expresiones regulares son caras. La solución anterior es más corta y más rápida.


1
Gracias Erwin, cosas útiles. Desde aquí parece que ctidson 6 bytes con 4 para la página y 2 para la fila. Estaba preocupado por el casting, floatpero supongo que no necesito tener lo que dices aquí. Parece que un tipo compuesto definido por el usuario es mucho más lento que el uso point, ¿también lo encuentra?
Jack Douglas

@JackDouglas: Tras una investigación más exhaustiva me he vuelto a caer bigint. Considera la actualización.
Erwin Brandstetter

1
@JackDouglas: Me gusta tu idea de un elenco para un tipo compuesto. Está limpio y funciona muy bien, incluso si el lanzamiento pointy el regreso int8son aún más rápidos). La conversión a tipos predefinidos siempre será un poco más rápida. Lo agregué a mi prueba para comparar. Lo haría (page_number bigint, row_number integer)para estar seguro.
Erwin Brandstetter

1
2^40es solo 1TB, no 32TB, que es 2^45dividido por 2^13da 2^32, por lo tanto, los 32 bits completos son necesarios para el número de página.
Daniel Vérité

1
También quizás digno de mención es que pg_freespacemap utiliza bigintpara blkno
Jack Douglas
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.