Как мне разложить ctid на номера страниц и строк?

16

Каждая строка в таблице имеет системный столбец ctid типа, tidкоторый представляет физическое местоположение строки:

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

dbfiddle здесь

Как лучше всего получить только номер страницы из ctidнаиболее подходящего типа (например integer, bigintили numeric(1000,0))?

Единственным способом я могу думать очень некрасиво.

Джек Дуглас
источник
1
IIRC это векторный тип, и у нас нет методов доступа к ним. Я не уверен, что вы можете сделать это с помощью функции C. Крейг точно скажет :)
Дезсо
2
Вы можете сыграть как POINT? Например. select ct[0], ct[1] from (select ctid::text::point as ct from pg_class where ...) y;
bma
1
Заголовок предполагает, что вы следите за номером страницы и индексом кортежа , позже вы сузитесь до номера страницы. Я пошел с версией в теле, индекс кортежа является тривиальным расширением.
Эрвин Брандштеттер

Ответы:

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

Твоя скрипка с моим решением.

@bma уже намекнул нечто подобное в комментарии. Вот ...

Обоснование типа

ctidимеет тип tid(идентификатор кортежа), вызываемый ItemPointerв коде C. По документации:

Это тип данных системного столбца ctid. Идентификатор кортежа - это пара ( номер блока , индекс кортежа в блоке ), который идентифицирует физическое местоположение строки в своей таблице.

Жирный акцент мой. И:

( ItemPointerтакже известный как CTID)

Блок составляет 8 КБ в стандартных установках. Максимальный размер таблицы составляет 32 ТБ . Логически следует, что номера блоков должны соответствовать как минимум максимуму (расчет фиксируется согласно комментарию @Daniel):

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

Который вписался бы в неподписанного integer . При дальнейшем расследовании я обнаружил в исходном коде, что ...

блоки нумеруются последовательно, от 0 до 0xFFFFFFFE .

Жирный акцент мой. Что подтверждает первый расчет:

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

Postgres использует целое число со знаком и, следовательно, на один бит меньше. Я пока не могу определить, смещено ли текстовое представление для размещения целого числа со знаком. Пока кто-нибудь не сможет это прояснить, я бы отступилbigint , что работает в любом случае.

В ролях

В Postgres 9.3 не зарегистрировано приведение tidтипа:

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

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

Вы все еще можете бросить на text. В Postgres есть текстовое представление для всего :

Другое важное исключение заключается в том, что «автоматическое преобразование ввода-вывода», те, которые выполняются с использованием собственных функций ввода-вывода для преобразования данных в текстовые или другие строковые типы или из них, явно не представлены в pg_cast.

Текстовое представление соответствует представлению точки, состоящей из двух float8чисел, которое приведено без потерь.

Вы можете получить доступ к первому номеру точки с индексом 0. Приведение к bigint. Вуаля.

Производительность

Я провел быстрый тест для таблицы с 30 тыс. Строк (лучше всего из 5) на нескольких альтернативных выражениях, которые пришли в голову, включая ваш исходный:

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;

intвместо этого bigint, в основном не имеет значения для целей теста. Я не повторил для bigint.
Приведение к t_tidосновывается на пользовательском составном типе, как, например, прокомментировал @Jake.
Суть этого: кастинг, как правило, быстрее, чем манипуляции со строками. Регулярные выражения стоят дорого. Вышеуказанное решение является самым коротким и быстрым.

Эрвин Брандштеттер
источник
1
Спасибо Эрвин, полезные вещи. От здесь она выглядит ctid6 байт с 4 на странице и 2 для строки. Я волновался по поводу кастинга, floatно, думаю, мне не нужно понимать то, что вы здесь говорите. Похоже, что пользовательский составной тип намного медленнее, чем при использовании point, вы тоже это нашли?
Джек Дуглас
@JackDouglas: После дальнейшего расследования я вернулся к bigint. Рассмотрим обновление.
Эрвин Брандштеттер
1
@JackDouglas: мне нравится ваша идея о приведении к составному типу. Он чистый и работает очень хорошо - даже если приведение к pointи обратно int8все еще быстрее). Приведение к предопределенным типам всегда будет немного быстрее. Я добавил его в свой тест для сравнения. Я бы сделал это, (page_number bigint, row_number integer)чтобы быть уверенным.
Эрвин Брандштеттер
1
2^40составляет всего 1 ТБ, а не 32 ТБ, то есть 2^45, что делится на, 2^13дает 2^32, следовательно, полные 32 бита, необходимые для номера страницы.
Даниэль Верите
1
Также, возможно, стоит отметить, что pg_freespacemap использует bigintдля blkno
Джек Дуглас