Як розкласти 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 - це векторний тип, і у нас немає методів доступу. Я не впевнений, чи можете ви це зробити з функції С. Крейг точно скаже :)
dezso

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у коді С. За документацію:

Це тип даних стовпця системи 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, що працює в будь-якому випадку.

У ролях

Там немає не з'являлися лита для tidтипу в Postgres 9.3:

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, чи вважаєте ви це також?
Джек каже, спробуйте topanswers.xyz

@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 використовує bigintblkno
Джек каже спробувати topanswers.xyz
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.