Comment décomposer ctid en numéros de page et de ligne?


16

Chaque ligne d'une table possède une colonne système ctid de type tidqui représente l'emplacement physique de la ligne:

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 ici

Quelle est la meilleure façon d'obtenir uniquement le numéro de page à partir du ctidtype le plus approprié (par exemple integer, bigintou numeric(1000,0))?

La seule façon dont je peux penser est très moche.


1
IIRC c'est un type de vecteur et nous n'avons pas de méthodes d'accesseur sur ceux-ci. Je ne sais pas si vous pouvez le faire à partir d'une fonction C. Craig le dira à coup sûr :)
dezso

2
Pouvez-vous lancer en tant que POINT? Par exemple. select ct[0], ct[1] from (select ctid::text::point as ct from pg_class where ...) y;
bma

1
Le titre suggère que vous recherchez à la fois le numéro de page et l' index de tuple . Je suis allé avec la version dans le corps, l'index de tuple est une extension triviale.
Erwin Brandstetter

Réponses:


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

Votre violon avec ma solution.

@bma a déjà fait allusion à quelque chose de similaire dans un commentaire. Voici une ...

Justification du type

ctidest de type tid(tuple identifier), appelé ItemPointerdans le code C. Par documentation:

Il s'agit du type de données de la colonne système ctid. Un ID de tuple est une paire ( numéro de bloc , index de tuple dans le bloc ) qui identifie l'emplacement physique de la ligne dans sa table.

Accentuation sur moi. Et:

( ItemPointer, également connu sous le nom de CTID)

Un bloc fait 8 Ko dans les installations standard. La taille maximale de la table est de 32 To . Il s'ensuit logiquement que les numéros de bloc doivent contenir au moins un maximum de (calcul fixé selon le commentaire de @Daniel):

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

Ce qui rentrerait dans un non signé integer. Après une enquête plus approfondie, j'ai trouvé dans le code source que ...

les blocs sont numérotés séquentiellement, de 0 à 0xFFFFFFFE .

Accentuation sur moi. Ce qui confirme le premier calcul:

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

Postgres utilise un entier signé et est donc un peu court. Je ne pouvais pas encore déterminer si la représentation du texte était décalée pour s'adapter à un entier signé. Jusqu'à ce que quelqu'un puisse éclaircir ce point, je reviendrais sur bigintce qui fonctionne dans tous les cas.

Jeter

Il n'y a pas de distribution enregistrée pour le tidtype dans Postgres 9.3:

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

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

Vous pouvez toujours lancer sur text. Il y a une représentation textuelle pour tout dans Postgres :

Une autre exception importante est que les "conversions d'E / S automatiques", celles effectuées à l'aide des propres fonctions d'E / S d'un type de données pour convertir vers ou à partir de texte ou d'autres types de chaînes, ne sont pas explicitement représentées dans pg_cast.

La représentation textuelle correspond à celle d'un point, composé de deux float8nombres, que la distribution est sans perte.

Vous pouvez accéder au premier numéro d'un point avec l'index 0. Cast to bigint. Voilá.

Performance

J'ai effectué un test rapide sur une table avec 30k lignes (le meilleur de 5) sur quelques expressions alternatives qui me sont venues à l'esprit, y compris l'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;

intau lieu d’ bigintici, ce qui n’est généralement pas pertinent aux fins du test. Je n'ai pas répété pour bigint.
Le cast à t_tids'appuie sur un type composite défini par l'utilisateur, comme @Jake a commenté.
L'essentiel: le casting a tendance à être plus rapide que la manipulation de cordes. Les expressions régulières coûtent cher. La solution ci-dessus est la plus courte et la plus rapide.


1
Merci Erwin, des trucs utiles. À partir de là, il ressemble à ctid6 octets avec 4 pour la page et 2 pour la ligne. J'étais inquiet à propos du casting, floatmais je suppose que je n'ai pas besoin de ce que vous dites ici. Il semble qu'un type composite défini par l'utilisateur soit beaucoup plus lent que son utilisation point, le trouvez-vous également?
Jack Douglas

@JackDouglas: Après une enquête plus approfondie, je suis retombé bigint. Considérez la mise à jour.
Erwin Brandstetter

1
@JackDouglas: J'aime votre idée d'une conversion vers un type composite. Il est propre et fonctionne très bien - même si le castingpoint et vers int8est toujours plus rapide). La conversion vers des types prédéfinis sera toujours un peu plus rapide. Je l'ai ajouté à mon test pour comparer. Je ferais ça (page_number bigint, row_number integer)pour être sûr.
Erwin Brandstetter

1
2^40est seulement 1 To, pas 32 To qui est 2^45, qui est divisé par 2^13donne 2^32, d'où les 32 bits complets étant nécessaires pour le numéro de page.
Daniel Vérité

1
Il est peut-être également intéressant de noter que pg_freespacemap utilise bigintpour blkno
Jack Douglas
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.