Come posso scomporre ctid in numeri di pagina e di riga?


16

Ogni riga in una tabella ha una colonna ctid di sistema di tipo tidche rappresenta la posizione fisica della riga:

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

dbfiddle qui

Qual è il modo migliore per ottenere solo il numero di pagina dal ctidtipo più appropriato (ad esempio integer, biginto numeric(1000,0))?

L' unico modo in cui riesco a pensare è molto brutto.


1
IIRC è un tipo vettoriale e non abbiamo metodi di accesso su questi. Non sono sicuro che tu possa farlo da una funzione C. Craig lo dirà per certo :)
dezso

2
Puoi scegliere come POINT? Per esempio. select ct[0], ct[1] from (select ctid::text::point as ct from pg_class where ...) y;
bma,

1
Il titolo suggerisce che stai cercando sia il numero di pagina che l' indice di tupla , poi ti restringi al numero di pagina. Sono andato con la versione nel corpo, l'indice di tupla è un'estensione banale.
Erwin Brandstetter,

Risposte:


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

Il tuo violino con la mia soluzione.

@bma ha già suggerito qualcosa di simile in un commento. Ecco un ...

Razionale per il tipo

ctidè di tipo tid(identificatore tupla), chiamato ItemPointernel codice C. Per documentazione:

Questo è il tipo di dati della colonna di sistema ctid. Un ID tupla è una coppia ( numero di blocco , indice di tupla all'interno del blocco ) che identifica la posizione fisica della riga all'interno della sua tabella.

Enorme enfasi sulla mia. E:

( ItemPointer, noto anche come CTID)

Un blocco è 8 KB in installazioni standard. La dimensione massima della tabella è di 32 TB . Ne consegue logicamente che i numeri di blocco devono contenere almeno un massimo di (calcolo fissato in base al commento di @Daniel):

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

Che si adatterebbe in un unsigned integer . Su ulteriori indagini ho trovato nel codice sorgente che ...

i blocchi sono numerati in sequenza, da 0 a 0xFFFFFFFE .

Enorme enfasi sulla mia. Il che conferma il primo calcolo:

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

Postgres utilizza numeri interi con segno ed è quindi un po 'corto. Non potrei ancora stabilire se la rappresentazione del testo viene spostata per adattarsi all'intero con segno. Fino a quando qualcuno non riuscirà a chiarire questo, vorrei tornare indietrobigint , il che funziona in ogni caso.

Fusioni

Non esiste alcun cast registrato per il tidtipo in Postgres 9.3:

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

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

Puoi ancora lanciare text. In Postgres esiste una rappresentazione testuale per tutto :

Un'altra importante eccezione è che i "cast di conversione I / O automatici", quelli eseguiti utilizzando le funzioni I / O proprie di un tipo di dati per convertire da o verso testo o altri tipi di stringa, non sono rappresentati esplicitamente pg_cast.

La rappresentazione del testo corrisponde a quella di un punto, che consiste di due float8numeri, che il cast è senza perdita.

È possibile accedere al primo numero di un punto con indice 0. Trasmetti a bigint. Ecco.

Prestazione

Ho eseguito un rapido test su un tavolo con 30k righe (la migliore delle 5) su un paio di espressioni alternative che mi sono venute in mente, incluso il tuo originale:

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;

intanziché bigintqui, per lo più irrilevante ai fini del test. Non ho ripetuto per bigint.
Il cast si t_tidbasa su un tipo composito definito dall'utente, come ha commentato @Jake.
L'essenza: il casting tende ad essere più veloce della manipolazione delle stringhe. Le espressioni regolari sono costose. La soluzione sopra è la più breve e veloce.


1
Grazie Erwin, cose utili. Da qui sembra che ctidsiano 6 byte con 4 per la pagina e 2 per la riga. Ero preoccupato per il casting floatma credo di non aver bisogno di quello che dici qui. Sembra che un tipo composito definito dall'utente sia molto più lento dell'uso point, lo trovi anche tu?
Jack Douglas,

@JackDouglas: dopo ulteriori indagini sono tornato a bigint. Considera l'aggiornamento.
Erwin Brandstetter,

1
@JackDouglas: mi piace la tua idea di cast per un tipo composito. È pulito e si comporta molto bene, anche se il cast da pointe verso int8è ancora più veloce). Trasmetti a tipi predefiniti sarà sempre un po 'più veloce. L'ho aggiunto al mio test per confrontare. Lo farei (page_number bigint, row_number integer)per essere sicuro.
Erwin Brandstetter,

1
2^40è solo 1 TB, non 32 TB, che è 2^45diviso per 2^132^32, quindi sono necessari i 32 bit completi per il numero di pagina.
Daniel Vérité,

1
Forse anche degno di nota è che pg_freespacemap usa bigintper blkno
Jack Douglas
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.