Как мне разложить 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
— Джек Дуглас
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.