Como decompo o ctid em números de páginas e linhas?

16

Cada linha em uma tabela possui uma coluna ctid do sistema do tipo tidque representa o local físico da linha:

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

dbfiddle aqui

Qual é a melhor maneira de obter apenas o número da página ctidno tipo mais apropriado (por exemplo integer, bigintou numeric(1000,0))?

A única maneira de pensar é muito feia.

Jack Douglas
fonte
11
O IIRC é um tipo de vetor e não temos métodos de acesso neles. Não tenho certeza se você pode fazê-lo a partir de uma função C. Craig dirá com certeza :) #
1012 dezso
2
Você pode transmitir como PONTO? Por exemplo. select ct[0], ct[1] from (select ctid::text::point as ct from pg_class where ...) y;
bma 27/05
11
O título sugere que você está atrás do número da página e do índice da tupla , depois diminui para o número da página. Eu fui com a versão no corpo, índice de tupla é uma extensão trivial.
Erwin Brandstetter

Respostas:

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

Seu violino com a minha solução.

A @bma já sugeriu algo semelhante em um comentário. Aqui está um ...

Justificativa para o tipo

ctidé do tipo tid(identificador de tupla), chamado ItemPointerno código C. Por documentação:

Este é o tipo de dados da coluna do sistema ctid. Um ID de tupla é um par ( número de bloco , índice de tupla dentro de bloco ) que identifica a localização física da linha em sua tabela.

Negrito ênfase minha. E:

( ItemPointer, também conhecido como CTID)

Um bloco tem 8 KB em instalações padrão. O tamanho máximo da tabela é 32 TB . Segue-se logicamente que os números de bloco devem acomodar pelo menos um máximo de (cálculo corrigido de acordo com o comentário de @Daniel):

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

O que caberia em um não assinado integer. Em uma investigação mais aprofundada, encontrei no código fonte que ...

blocos são numerados seqüencialmente, 0 a 0xFFFFFFFE .

Negrito ênfase minha. O que confirma o primeiro cálculo:

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

O Postgres usa inteiro assinado e, portanto, é um pouco curto. Ainda não pude determinar se a representação do texto foi alterada para acomodar um número inteiro assinado. Até que alguém possa esclarecer isso, eu voltaria a bigint, que funciona em qualquer caso.

Fundida

Não há elenco registrado para o tidtipo no Postgres 9.3:

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

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

Você ainda pode transmitir para text. Existe uma representação de texto para tudo no Postgres :

Outra exceção importante é que "conversões de conversão de E / S automática", aquelas executadas usando as próprias funções de E / S de um tipo de dados para converter para ou de texto ou outros tipos de sequência, não são explicitamente representadas pg_cast.

A representação de texto corresponde à de um ponto, que consiste em dois float8números, que é convertido sem perdas.

Você pode acessar o primeiro número de um ponto com o índice 0. Transmitir para bigint. Voilá.

atuação

Fiz um teste rápido em uma tabela com 30k linhas (melhor de 5) em algumas expressões alternativas que vieram à mente, incluindo o 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;

intem vez de bigintaqui, principalmente irrelevantes para a finalidade do teste. Eu não repeti para bigint.
A t_tidconversão a partir de um tipo composto definido pelo usuário, como o @Jake comentou.
A essência disso: o elenco tende a ser mais rápido que a manipulação de cordas. Expressões regulares são caras. A solução acima é mais curta e mais rápida.

Erwin Brandstetter
fonte
11
Obrigado Erwin, coisas úteis. A partir daqui , parece que ctidsão 6 bytes com 4 para a página e 2 para a linha. Eu estava preocupado com o elenco, floatmas acho que não preciso do que você diz aqui. Parece que um tipo composto definido pelo usuário é muito mais lento do que o utilizado point. Você acha isso também?
Jack Douglas
@JackDouglas: Após uma investigação mais aprofundada, voltei a bigint. Considere a atualização.
Erwin Brandstetter
11
@JackDouglas: Eu gosto da sua ideia de um elenco para um tipo composto. É limpo e tem um desempenho muito bom - mesmo que a conversão para pointe para o retorno int8ainda seja mais rápida). A conversão para tipos predefinidos sempre será um pouco mais rápida. Eu o adicionei ao meu teste para comparar. Eu faria isso (page_number bigint, row_number integer)para ter certeza.
Erwin Brandstetter
11
2^40é de apenas 1 TB, e não 32 TB, 2^45dividido por 2^132^32, portanto os 32 bits completos são necessários para o número da página.
Daniel Vérité 28/05
11
Talvez também digno de nota é que pg_freespacemap usos bigintpara blkno
Jack Douglas