Як розкласти 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.