lesson ── postgres-labs ── ~24 мин ── 4 шагов
B-tree можно вскрыть тем же pageinspect, что и heap. Пройдём от корня к
листу за конкретным значением и подтвердим путь, а потом построим
покрывающий индекс и увидим Index-Only Scan. Запусти psql во вкладке
client.
интерактивный sandbox
Поднимется контейнер postgreslab/postgres-base с PostgreSQL 17 и psql. В браузере откроется терминал, база lab уже настроена. Каждый шаг проверяется автоматически. Сеть air-gapped, наружу контейнер не ходит.
stack ── PostgreSQL 17 · psql · 1 GB RAM · air-gapped · самоуничтожается через 45 мин простоя
CREATE EXTENSION IF NOT EXISTS pageinspect;
CREATE TABLE bt AS SELECT g AS id, (g % 1000) AS k FROM generate_series(1, 200000) g;
CREATE INDEX bt_idx ON bt (id);
ANALYZE bt;
200 тысяч строк дадут дерево с несколькими уровнями.
✓ Линза и дерево готовы.
SELECT root, level FROM bt_metap('bt_idx');root - номер корневой страницы, level - высота. level > 0
значит, что корень - внутренняя страница, под ним есть листья.
Запиши номер корня.
level = 0 было бы у дерева из одной страницы; у нас оно выше.
✓ Дерево многоуровневое: корень внутренний, листья ниже.
SELECT itemoffset, ctid, data
FROM bt_page_items('bt_idx', (SELECT root FROM bt_metap('bt_idx')));На внутренней странице ctid в записи - это downlink на дочернюю страницу. Спустись по нужному downlink на уровень ниже и читай bt_page_items этой страницы, пока не дойдёшь до листа.
Спуск идёт по downlink к диапазону, куда попадает искомый id.
✓ В корне видны разделители и downlink на детей.
CREATE INDEX bt_cov ON bt (id) INCLUDE (k);
VACUUM bt;
EXPLAIN SELECT id, k FROM bt WHERE id = 42;
Обе колонки (id, k) есть в индексе, в таблицу идти не надо - план идёт по Index Only Scan.
INCLUDE кладёт k в лист как payload, не делая его частью ключа.
✓ Index Only Scan - запрос покрыт индексом, таблица не нужна.
B-tree - сбалансированное дерево из 8-КБ страниц: meta page указывает на корень, внутренние хранят разделители и downlink, листья - ключ и ctid. Спуск meta → корень → лист виден через bt_metap/bt_page_items. Большой fanout держит дерево на 3-4 уровнях; INCLUDE даёт покрытие под IOS.
команды
SELECT root, level FROM bt_metap('idx');корень и высота дереваSELECT * FROM bt_page_items('idx', N);содержимое страницы индексаCREATE INDEX ON t (k) INCLUDE (v);покрывающий индекс под IOSконцепции