GiST нужен PostgreSQL для запросов, где обычной сортировки по одному значению недостаточно: пересечения интервалов, близость объектов, геометрические отношения, поиск похожего текста и запрет конфликтующих диапазонов. Гайд показывает устройство GiST, выбор операторного класса, рабочие SQL-примеры и способы проверить, что индекс действительно ускоряет запрос.

GiST превращает правила предметной области в индексируемый поиск
Обычный B-tree хорошо работает там, где значения можно выстроить в одну линию: 10 меньше 20, дата 1 июля раньше 10 июля, строка apple идёт перед banana. Многие прикладные задачи устроены сложнее. Периоды бронирования пересекаются, геометрические объекты накладываются друг на друга, адреса находятся на разном расстоянии от точки, а две строки могут быть похожи без полного совпадения.
GiST расшифровывается как Generalized Search Tree, или «обобщённое поисковое дерево». PostgreSQL описывает GiST как сбалансированный древовидный метод доступа, который служит каркасом для разных схем индексирования. На этом каркасе можно реализовать поведение R-tree, B-tree и других поисковых структур. Конкретные возможности задаёт операторный класс — набор правил для определённого типа данных и набора операторов.
Удобная аналогия выглядит так. GiST — это здание склада с этажами, коридорами и правилами перемещения. Операторный класс определяет, какие товары хранятся внутри, как они группируются и по какому признаку сотрудник выбирает нужный коридор. Команда USING GIST создаёт само здание. Тип столбца, операторный класс и оператор в SQL-запросе определяют, сможет ли PostgreSQL пройти по короткому маршруту.
На июль 2026 года актуальная стабильная документация PostgreSQL относится к версии 18. Базовые принципы и примеры из этого гайда применимы к поддерживаемым веткам PostgreSQL 14–18, хотя набор операторных классов и отдельные возможности расширений зависят от установленной версии. Актуальный перечень встроенных классов опубликован в документации GiST.
Три части определяют поведение GiST
Для работающего индекса должны совпасть три элемента:
- Тип данных. Например,
tsrange,geometry,text,pointилиinet. - Операторный класс. Например,
range_ops,gist_geometry_ops_2d,gist_trgm_opsили класс из расширенияbtree_gist. - Оператор запроса. Например,
&&для пересечения,@>для включения,<->для расстояния или%для триграммного сходства.
Команда ниже создаёт GiST-индекс для временных диапазонов:
CREATE INDEX bookings_period_gist_idx
ON bookings
USING GIST (period);Такой индекс может ускорять запросы с операторами диапазонов:
SELECT id, period
FROM bookings
WHERE period && tsrange(
TIMESTAMP '2026-08-01 10:00',
TIMESTAMP '2026-08-01 12:00',
'[)'
);Оператор && означает пересечение двух диапазонов. Планировщик знает, что класс range_ops поддерживает этот оператор, и получает возможность использовать индекс.
Запрос с функцией или оператором, которого нет в операторном классе, не получает индексного маршрута через этот GiST. Сам факт наличия индекса на столбце ничего не гарантирует.
Дерево хранит компактные описания групп значений
GiST строит сбалансированное дерево из страниц. Листовые страницы связаны со строками таблицы, а узлы верхних уровней содержат сводные ключи, описывающие целые группы значений. Сводный ключ помогает решить, в какие ветви дерева стоит заходить во время поиска.
Для геометрии таким описанием часто служит ограничивающий прямоугольник, или bounding box. Один прямоугольник охватывает объект, другой — группу объектов, третий — более крупную группу. Запрос на пересечение сначала отбрасывает ветви, чьи прямоугольники точно лежат далеко от области поиска.
Для диапазонов сводный ключ может описывать общий интервал, охватывающий несколько дочерних значений. Для полнотекстового поиска GiST использует сигнатуру фиксированной длины. Для pg_trgm операторный класс gist_trgm_ops представляет набор триграмм как битовую сигнатуру.
Приближённый фильтр сокращает число точных проверок
Часть GiST-классов хранит точное представление ключа. Другие классы используют приближённое описание. Второй вариант называют lossy, или индексом с потерями точности представления.
Lossy-индекс может вернуть лишние строки-кандидаты. PostgreSQL автоматически перепроверяет условие по исходному значению из таблицы. Такой механизм не приводит к неправильному результату: он влияет на объём дополнительной работы.
Пространственный поиск хорошо показывает двухэтапную схему:
- GiST быстро находит объекты, чьи ограничивающие прямоугольники могут пересекаться с областью запроса.
- PostGIS выполняет точное геометрическое вычисление для оставшихся кандидатов.
В плане выполнения эта работа может отображаться через Recheck Cond и счётчик Rows Removed by Index Recheck.
EXPLAIN (ANALYZE, BUFFERS)
SELECT id
FROM parcels
WHERE geom && ST_MakeEnvelope(37.60, 55.74, 37.66, 55.78, 4326);Большое число перепроверок само по себе не доказывает проблему. Сравнивайте время выполнения, число прочитанных блоков, количество кандидатов и итоговых строк. Если индекс возвращает почти всю таблицу, селективность условия слишком низкая или сводные ключи плохо разделяют данные.
Операторный класс связывает SQL-операторы с логикой дерева
PostgreSQL выбирает операторный класс автоматически, когда для типа данных существует класс GiST по умолчанию. Диапазоны и встроенные геометрические типы обычно работают без явного имени класса. Текстовый поиск через pg_trgm требует явного gist_trgm_ops.
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX products_name_trgm_gist_idx
ON products
USING GIST (name gist_trgm_ops);Для сетевых адресов встроенный inet_ops исторически не назначен классом GiST по умолчанию, поэтому его указывают в определении индекса:
CREATE INDEX firewall_network_gist_idx
ON firewall_rules
USING GIST (network inet_ops);Системные каталоги показывают доступные классы
Список GiST-классов в конкретной базе можно получить из системных каталогов:
SELECT
opc.opcname AS operator_class,
typ.typname AS data_type,
nsp.nspname AS schema_name,
opc.opcdefault AS is_default
FROM pg_opclass AS opc
JOIN pg_am AS am
ON am.oid = opc.opcmethod
JOIN pg_type AS typ
ON typ.oid = opc.opcintype
JOIN pg_namespace AS nsp
ON nsp.oid = opc.opcnamespace
WHERE am.amname = 'gist'
ORDER BY schema_name, data_type, operator_class;Расширения добавляют свои строки в этот список. После установки PostGIS появятся классы для geometry и geography, после установки pg_trgm — gist_trgm_ops, после установки btree_gist — классы со сравнительным поведением для скалярных типов.
Состав установленного индекса удобно проверить через pg_indexes:
SELECT indexname, indexdef
FROM pg_indexes
WHERE schemaname = 'public'
AND tablename = 'products';В psql ту же информацию показывает команда:
\d productsДиапазоны дают GiST естественную модель времени и интервалов
Диапазонные типы PostgreSQL daterange, tsrange, tstzrange, int4range, int8range и numrange хранят нижнюю и верхнюю границы как единое значение. GiST ускоряет поиск пересечений, включения, соседства и взаимного расположения диапазонов.
Создадим таблицу бронирований:
CREATE TABLE room_bookings (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
room_id bigint NOT NULL,
guest_name text NOT NULL,
period tstzrange NOT NULL,
status text NOT NULL DEFAULT 'confirmed'
);
CREATE INDEX room_bookings_period_gist_idx
ON room_bookings
USING GIST (period);Запрос найдёт бронирования, которые пересекаются с новым интервалом:
SELECT id, room_id, guest_name, period
FROM room_bookings
WHERE period && tstzrange(
TIMESTAMPTZ '2026-08-15 12:00:00+03',
TIMESTAMPTZ '2026-08-15 16:00:00+03',
'[)'
);Запись [) включает нижнюю границу и исключает верхнюю. Бронирование до 12:00 и следующее бронирование с 12:00 не пересекаются. Такой формат удобен для расписаний, аренды, действия тарифов и версий записей.
Основные операторы диапазонов
| Оператор | Смысл | Пример условия |
&& | диапазоны пересекаются | period && :requested_period |
@> | левый диапазон содержит значение или диапазон | period @> now() |
<@ | левый диапазон содержится в правом | period <@ :season |
<< | левый диапазон полностью левее правого | period << :requested_period |
>> | левый диапазон полностью правее правого | period >> :requested_period |
-|- | диапазоны соприкасаются границами | period -|- :requested_period |
&< | левый диапазон не простирается правее правого | period &< :requested_period |
&> | левый диапазон не простирается левее правого | period &> :requested_period |
Параметризированный запрос лучше передавать как значение диапазона одного типа. Это упрощает сопоставление оператора с индексным классом и снижает риск неявных преобразований типов.
Ограничение EXCLUDE блокирует пересекающиеся записи на уровне базы
Проверка пересечений в приложении уязвима к гонке. Два параллельных запроса могут одновременно увидеть свободный интервал и записать конфликтующие бронирования. Ограничение исключения EXCLUDE переносит правило в PostgreSQL и проверяет его вместе с записью данных.
Для запрета любых пересекающихся периодов достаточно одного столбца:
CREATE TABLE maintenance_windows (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
period tstzrange NOT NULL,
EXCLUDE USING GIST (period WITH &&)
);Любая новая строка, чей period пересекается с существующим, завершится ошибкой ограничения.
Чаще конфликт зависит от дополнительного признака: номер комнаты, идентификатор оборудования, сотрудник или автомобиль. Расширение btree_gist добавляет GiST-классы со сравнительным поведением для чисел, строк, дат, UUID и других скалярных типов.
CREATE EXTENSION IF NOT EXISTS btree_gist;
CREATE TABLE room_bookings_safe (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
room_id bigint NOT NULL,
guest_name text NOT NULL,
period tstzrange NOT NULL,
cancelled boolean NOT NULL DEFAULT false,
EXCLUDE USING GIST (
room_id WITH =,
period WITH &&
)
WHERE (cancelled = false)
);Правило читается так: две активные строки конфликтуют, когда у них равны room_id и пересекаются period. Бронирования разных комнат разрешены. Отменённые строки исключены предикатом частичного ограничения.
Отложенная проверка поддерживает сложные транзакции
Ограничение можно объявить отложенным:
CREATE TABLE employee_shifts (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
employee_id bigint NOT NULL,
period tstzrange NOT NULL,
EXCLUDE USING GIST (
employee_id WITH =,
period WITH &&
) DEFERRABLE INITIALLY DEFERRED
);PostgreSQL проверит конфликт к моменту фиксации транзакции. Такой режим подходит для пакетного переноса смен, когда промежуточное состояние внутри транзакции временно содержит пересечения.
Обработчик приложения должен распознавать SQLSTATE 23P01 — exclusion_violation. Это позволяет вернуть понятное сообщение пользователю и повторно запросить актуальное расписание.
PostGIS использует GiST для пространственных отношений
PostGIS добавляет типы geometry и geography, пространственные операторы, функции и операторные классы GiST. Типовой индекс выглядит так:
CREATE INDEX places_geom_gist_idx
ON places
USING GIST (geom);Для геометрии в двумерных координатах обычно применяется класс по умолчанию gist_geometry_ops_2d. PostGIS использует индексные фильтры по ограничивающим прямоугольникам, затем выполняет точную проверку геометрии для функций, которым она нужна.
Поиск объектов в области
SELECT id, name
FROM places
WHERE geom && ST_MakeEnvelope(
37.55, 55.70,
37.75, 55.82,
4326
);Оператор && проверяет пересечение ограничивающих прямоугольников. Он подходит для предварительной выборки объектов в окне карты. Для точного отношения добавьте пространственную функцию:
WITH area AS (
SELECT ST_MakeEnvelope(
37.55, 55.70,
37.75, 55.82,
4326
) AS geom
)
SELECT p.id, p.name
FROM places AS p
CROSS JOIN area AS a
WHERE ST_Intersects(p.geom, a.geom);ST_Intersects входит в число функций PostGIS, которые автоматически включают индексный фильтр, когда подходящий пространственный индекс доступен.
Поиск в радиусе через ST_DWithin
Для выборки объектов в пределах расстояния используйте ST_DWithin:
SELECT id, name
FROM places
WHERE ST_DWithin(
geom::geography,
ST_SetSRID(ST_MakePoint(37.6173, 55.7558), 4326)::geography,
3000
);В этом примере расстояние задано в метрах благодаря типу geography. Индекс должен соответствовать выражению из запроса. Если столбец хранится как geometry, а запрос постоянно приводит его к geography, создайте индекс выражения:
CREATE INDEX places_geography_gist_idx
ON places
USING GIST ((geom::geography));После этого условие с geom::geography получает совместимый индексный ключ.
Проверка ST_Distance(...) < 3000 часто вынуждает вычислять расстояние для большого числа строк. ST_DWithin содержит индексируемый предварительный фильтр и подходит для запроса «в пределах радиуса».
Ближайшие объекты через KNN GiST
GiST поддерживает упорядоченный индексный поиск, который часто называют KNN GiST. Запрос проходит по дереву в порядке увеличения расстояния и может остановиться после первых LIMIT строк.
SELECT
id,
name,
geom <-> ST_SetSRID(ST_MakePoint(37.6173, 55.7558), 4326) AS distance_order
FROM places
ORDER BY geom <-> ST_SetSRID(ST_MakePoint(37.6173, 55.7558), 4326)
LIMIT 20;Критическая форма запроса — ORDER BY индексированный_столбец <-> константа LIMIT N. Оператор <-> должен входить в операторный класс как оператор упорядочивания. Обычная сортировка по произвольной функции расстояния может потребовать вычислить значение для всех подходящих строк и выполнить отдельный Sort.
Для geography и сложных систем координат семантика дистанционного оператора зависит от класса PostGIS и версии расширения. Перед внедрением проверяйте единицы измерения и точность результата на контрольных точках.
pg_trgm ускоряет похожие строки и поиск с шаблоном
Расширение pg_trgm разбивает текст на триграммы — последовательности по три символа — и вычисляет сходство строк. Оно предоставляет классы GiST и GIN для text, а GiST дополнительно поддерживает эффективную выдачу ближайших строк через операторы расстояния.
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE TABLE catalog_items (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name text NOT NULL
);
CREATE INDEX catalog_items_name_trgm_gist_idx
ON catalog_items
USING GIST (name gist_trgm_ops);Поиск строк выше порога сходства
SELECT
id,
name,
similarity(name, 'postgress') AS score
FROM catalog_items
WHERE name % 'postgress'
ORDER BY score DESC, name;Оператор % использует параметр pg_trgm.similarity_threshold. В PostgreSQL 18 значение по умолчанию равно 0.3. Порог можно изменить для сеанса:
SET pg_trgm.similarity_threshold = 0.4;Высокий порог сокращает число кандидатов и требует большего сходства. Низкий порог расширяет выдачу и увеличивает объём проверок.
Поиск десяти ближайших строк
SELECT
id,
name,
name <-> 'postgress' AS distance
FROM catalog_items
ORDER BY name <-> 'postgress'
LIMIT 10;Документация pg_trgm указывает, что такую форму GiST выполняет эффективно, а GIN не поддерживает аналогичный упорядоченный KNN-поиск. Для сценария автодополнения, исправления опечаток и выдачи небольшого числа ближайших совпадений GiST часто оказывается подходящим выбором.
LIKE, ILIKE и регулярные выражения
gist_trgm_ops поддерживает индексный поиск для LIKE, ILIKE, ~, ~* и =:
SELECT id, name
FROM catalog_items
WHERE name ILIKE '%postgres%';Чем меньше извлекаемых триграмм содержит шаблон, тем слабее фильтрация. Очень короткая подстрока может привести к чтению большой части индекса. План и реальные показатели нужно проверять на данных, близких к рабочим.
siglen управляет точностью сигнатуры
gist_trgm_ops хранит битовую сигнатуру. Параметр siglen задаёт её длину в байтах. Значение по умолчанию — 12 байт, допустимый диапазон в PostgreSQL 18 — от 1 до 2024 байт.
CREATE INDEX catalog_items_name_trgm_gist_32_idx
ON catalog_items
USING GIST (name gist_trgm_ops(siglen = 32));Более длинная сигнатура уменьшает число ложных кандидатов и увеличивает размер индекса. Оптимальное значение зависит от длины строк, распределения текста, размера таблицы и запросов. Сравнивайте варианты через EXPLAIN (ANALYZE, BUFFERS) и размер индекса:
SELECT pg_size_pretty(pg_relation_size('catalog_items_name_trgm_gist_32_idx'));Полнотекстовый GiST хранит сигнатуры документов
PostgreSQL поддерживает GiST и GIN для полнотекстового поиска по tsvector. GiST представляет документ сигнатурой фиксированной длины и выполняет перепроверку найденных строк. GIN хранит отдельные ключи лексем и обычно обеспечивает более точный доступ для поиска слов.
CREATE INDEX articles_search_gist_idx
ON articles
USING GIST (search_vector);SELECT id, title
FROM articles
WHERE search_vector @@ websearch_to_tsquery('russian', 'gist индекс postgresql');Для большинства каталогов статей и документов GIN часто даёт более быстрый поиск по лексемам. GiST может быть полезен при конкретных требованиях к размеру, скорости обновлений, составному GiST-индексу или экспериментально подтверждённой нагрузке. Решение стоит принимать по измерениям на рабочем наборе запросов.
KNN-поиск использует оператор расстояния внутри дерева
Обычный индексный фильтр отвечает на вопрос «какие строки подходят условию». KNN-поиск отвечает на вопрос «какие строки ближе к заданному значению». GiST получает эту возможность через функцию поддержки distance и оператор упорядочивания из операторного класса.
Рабочий шаблон:
SELECT ...
FROM table_name
ORDER BY indexed_column <-> constant_value
LIMIT 20;План обычно содержит Index Scan с секцией Order By:
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, name
FROM catalog_items
ORDER BY name <-> 'postgress'
LIMIT 10;Ожидаемый фрагмент плана:
Limit
-> Index Scan using catalog_items_name_trgm_gist_idx on catalog_items
Order By: (name <-> 'postgress'::text)Константа справа сохраняет понятный индексный маршрут
Под «константой» здесь понимается значение, одинаковое для всего сканирования: литерал, параметр подготовленного запроса или результат InitPlan. Коррелированное значение из другой строки может привести к другому плану и многократным индексным сканированиям.
Для поиска ближайшего объекта к каждой точке используют LATERAL:
SELECT
q.id AS query_point_id,
p.id AS nearest_place_id,
p.name
FROM query_points AS q
CROSS JOIN LATERAL (
SELECT id, name
FROM places
ORDER BY geom <-> q.geom
LIMIT 1
) AS p;PostgreSQL выполняет отдельный KNN-поиск для каждой строки query_points. Такой план хорошо работает для небольшого набора исходных точек и селективного индекса. Для миллионов исходных точек потребуется пакетная обработка, пространственное разбиение или иной алгоритм сопоставления.
GiST, B-tree, GIN, SP-GiST и BRIN решают разные задачи
Выбор индекса начинается с операторов реальных запросов. Название типа данных даёт только часть ответа.
| Метод | Сильные сценарии | Типовые операторы и запросы | Ограничения выбора |
| B-tree | равенство, диапазон по линейно упорядоченным значениям, сортировка | =, <, <=, >, >=, BETWEEN, ORDER BY | плохо описывает пересечения, близость и многомерные отношения |
| GiST | диапазоны, геометрия, пространственные отношения, KNN, сигнатуры, EXCLUDE | &&, @>, <@, <->, доменные операторы | возможности зависят от операторного класса; часть классов требует recheck |
| GIN | поиск элементов внутри составного значения | массивы, JSONB, tsvector, триграммы | не поддерживает KNN-выдачу ORDER BY <-> для pg_trgm; обновления могут быть дороже |
| SP-GiST | данные, хорошо разделяемые на неперекрывающиеся области | точки, префиксы, quad-tree, k-d tree, trie | структура несбалансированная; доступные классы отличаются от GiST |
| BRIN | огромные таблицы с корреляцией значения и физического порядка | временные журналы, последовательные идентификаторы | хранит сводки диапазонов страниц и возвращает широкие кандидаты при слабой корреляции |
GiST для обычного равенства требует причины
Расширение btree_gist позволяет создать GiST для integer, text, uuid, дат и других скалярных типов:
CREATE EXTENSION IF NOT EXISTS btree_gist;
CREATE INDEX users_id_gist_idx
ON users
USING GIST (id);Документация прямо указывает, что такие классы обычно не превосходят стандартный B-tree и не умеют обеспечивать уникальность. Их применяют в составных GiST-индексах, ограничениях EXCLUDE, поиске по <> и KNN для типов с метрикой расстояния.
Для WHERE id = 42, ORDER BY created_at и уникального ключа B-tree остаётся базовым выбором.
GiST и GIN для pg_trgm выбирают по форме выдачи
GiST подходит, когда запрос часто просит несколько ближайших строк:
ORDER BY name <-> :query
LIMIT 10GIN подходит для фильтрации большого количества строк по триграммным условиям, когда KNN-порядок не нужен. Разница зависит от данных и настроек, поэтому окончательный выбор требует теста на рабочем объёме.
Составной GiST учитывает порядок столбцов
PostgreSQL поддерживает многоколоночные GiST-индексы. Условия могут относиться к любому подмножеству столбцов, но первый столбец сильнее влияет на объём сканируемого дерева. Документация предупреждает, что первый столбец с малым числом различных значений делает составной GiST сравнительно слабым, даже когда следующие столбцы имеют высокую кардинальность.
Рассмотрим индекс:
CREATE EXTENSION IF NOT EXISTS btree_gist;
CREATE INDEX events_tenant_period_gist_idx
ON events
USING GIST (
tenant_id,
period
);Если tenant_id принимает тысячи значений и запросы почти всегда фильтруют конкретного арендатора, такой порядок может хорошо разделять дерево:
SELECT id
FROM events
WHERE tenant_id = 824
AND period && tstzrange(:from_ts, :to_ts, '[)');Если tenant_id принимает всего два значения, первый ключ слабо сокращает область поиска. Индекс (period, tenant_id) может оказаться эффективнее для запросов с узким временным диапазоном. Проверка нужна на реальном распределении данных.
Отдельные индексы иногда дают планировщику больше свободы
Два индекса:
CREATE INDEX events_tenant_btree_idx
ON events (tenant_id);
CREATE INDEX events_period_gist_idx
ON events
USING GIST (period);могут объединяться через BitmapAnd, если планировщик считает это выгодным. Составной GiST экономит один маршрут поиска и нужен для некоторых ограничений EXCLUDE. Отдельные индексы проще использовать в запросах, где фильтры встречаются независимо.
Сравнивайте три варианта на наборе типовых запросов:
- один составной GiST;
- отдельный B-tree и GiST;
- только селективный индекс, который покрывает основной фильтр.
Частичный GiST уменьшает индекс до востребованной части таблицы
Частичный индекс содержит строки, удовлетворяющие предикату WHERE в определении индекса. Он полезен, когда запросы регулярно работают с небольшой активной частью данных.
CREATE INDEX deliveries_active_area_gist_idx
ON deliveries
USING GIST (service_area)
WHERE status = 'active';Индекс подходит запросу, условие которого логически включает предикат:
SELECT id
FROM deliveries
WHERE status = 'active'
AND service_area && :target_area;Запрос без ограничения по status не может безопасно использовать этот индекс для полной выборки, потому что завершённые доставки в нём отсутствуют.
Планировщик сопоставляет предикаты на этапе планирования. Параметризованная форма с неопределённым значением статуса иногда мешает доказать включение предиката для общего подготовленного плана. Эту особенность стоит проверить в приложении через EXPLAIN с реальными параметрами.
Индекс выражения требует точного совпадения выражения в запросе
GiST можно строить по выражению:
CREATE INDEX places_geography_gist_idx
ON places
USING GIST ((geom::geography));Запрос должен использовать совместимое выражение:
SELECT id
FROM places
WHERE ST_DWithin(
geom::geography,
:center::geography,
:radius_meters
);Функции и операторы в определении индексного выражения должны быть IMMUTABLE. PostgreSQL требует, чтобы индексный ключ зависел только от значений строки и оставался стабильным между запросами.
Неявное преобразование типов, другая функция, иной SRID или дополнительная оболочка вокруг столбца могут изменить выражение настолько, что планировщик не сопоставит его с индексом.
INCLUDE помогает отдельным GiST-классам выполнять index-only scan
PostgreSQL 18 разрешает INCLUDE для GiST:
CREATE INDEX places_geom_covering_gist_idx
ON places
USING GIST (geom)
INCLUDE (id, name);Столбцы id и name хранятся в листовых записях индекса и не участвуют в навигации. Они могут сократить обращения к таблице, когда операторный класс поддерживает index-only scan и карта видимости показывает, что страницы таблицы доступны без проверки версий строк.
Поддержка index-only scan зависит от операторного класса. Lossy-сжатие листового ключа может лишить класс возможности восстановить исходное значение. Широкие INCLUDE-столбцы увеличивают индекс и могут привести к превышению допустимого размера индексной записи.
Проверяйте фактический план:
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, name
FROM places
WHERE geom && :bbox;Ищите Index Only Scan и значение Heap Fetches. Большое число Heap Fetches показывает, что исполнителю всё ещё приходится обращаться к таблице.
Внутренние функции GiST определяют качество дерева
Разработчик расширения создаёт операторный класс и реализует функции поддержки. Пользователь базы обычно работает с готовыми классами. Понимание ролей объясняет поведение разных GiST-индексов.
consistent решает, стоит ли заходить в ветвь
consistent получает ключ узла и условие поиска. Функция отвечает, может ли среди дочерних записей находиться подходящая строка. Она же сообщает, нужна ли точная перепроверка через флаг recheck.
Слишком широкий ответ true сохраняет корректность и заставляет читать лишние ветви. Ошибочный ответ false способен пропустить подходящие строки, поэтому корректность consistent критична.
union строит сводный ключ группы
union объединяет ключи нескольких дочерних записей в один сводный ключ. Для прямоугольников это может быть минимальный прямоугольник, охватывающий все дочерние объекты. Компактный сводный ключ позволяет раньше отсекать удалённые ветви.
penalty выбирает ветвь для новой записи
penalty оценивает стоимость добавления нового ключа в существующую ветвь. GiST идёт по пути с наименьшим штрафом. Хорошая функция штрафа сохраняет близкие значения рядом и уменьшает перекрытие сводных областей.
picksplit делит переполненную страницу
Когда страница заполнена, picksplit распределяет записи между двумя страницами. Качество разделения влияет на будущие чтения. Сильное перекрытие групп заставляет поиск заходить сразу в несколько ветвей.
compress и decompress меняют внутреннее представление
compress может преобразовать исходное значение в компактный индексный ключ. decompress готовит сохранённый ключ для внутренних функций. Сжатие бывает точным или lossy.
distance включает KNN-порядок
Функция distance оценивает расстояние от ключа до значения запроса. Она позволяет GiST обходить ветви в порядке перспективности и поддерживает ORDER BY <-> LIMIT.
fetch открывает путь к index-only scan
fetch восстанавливает исходное значение из листового ключа. Класс с необратимым lossy-сжатием не может предоставить полноценный fetch и index-only scan для ключевого столбца.
sortsupport ускоряет построение индекса
sortsupport задаёт порядок сортировки, сохраняющий локальность данных. PostgreSQL использует его в CREATE INDEX и REINDEX. При отсутствии sortsupport GiST строится последовательной вставкой записей через penalty и picksplit, что обычно занимает больше времени.
EXPLAIN показывает реальную роль индекса
Наличие Index Scan в плане ещё не доказывает выигрыш. Смотрите на фактическое время, число строк, чтения буферов и объём перепроверки.
Официальная документация подробно описывает EXPLAIN. Базовая команда:
EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
SELECT id, period
FROM room_bookings
WHERE period && tstzrange(
TIMESTAMPTZ '2026-08-15 12:00:00+03',
TIMESTAMPTZ '2026-08-15 16:00:00+03',
'[)'
);ANALYZE выполняет запрос. Для UPDATE, DELETE, INSERT и других изменяющих команд тест проводите внутри транзакции с откатом:
BEGIN;
EXPLAIN (ANALYZE, BUFFERS)
UPDATE room_bookings
SET status = 'checked'
WHERE period && tstzrange(:from_ts, :to_ts, '[)');
ROLLBACK;Поля плана, которые стоит читать
| Поле | Что показывает |
Index Cond | условие, использованное для навигации или отбора в индексе |
Order By внутри Index Scan | упорядоченный KNN-поиск через индекс |
Recheck Cond | условие, перепроверяемое по строке таблицы |
Rows Removed by Index Recheck | число ложных кандидатов |
actual rows | фактическое число строк на узле |
rows в оценке | прогноз планировщика |
Buffers: shared hit/read | блоки из кэша и с диска |
Heap Fetches | обращения к таблице при index-only scan |
Sort | отдельная сортировка, которая может указывать на отсутствие KNN-маршрута |
Последовательное сканирование бывает рациональным выбором
Планировщик может выбрать Seq Scan, когда таблица мала, условие возвращает большую долю строк или чтение индекса вместе с таблицей оценивается дороже последовательного прохода. Такой план не доказывает неисправность индекса.
После крупной загрузки данных обновите статистику:
ANALYZE room_bookings;Для диагностики можно временно отключить последовательное сканирование в тестовом сеансе:
SET LOCAL enable_seqscan = off;Этот приём показывает доступность индексного плана. Он не подходит для оценки оптимальной производительности и не должен становиться постоянной настройкой сервера.
Статистика сервера показывает использование индекса во времени
Представление pg_stat_user_indexes собирает счётчики сканирований и чтения строк:
SELECT
schemaname,
relname AS table_name,
indexrelname AS index_name,
idx_scan,
idx_tup_read,
idx_tup_fetch,
pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
FROM pg_stat_user_indexes
WHERE indexrelname = 'room_bookings_period_gist_idx';idx_scan = 0 после нескольких недель рабочей нагрузки требует проверки. Возможные причины включают несовместимые операторы, другой индекс, низкую селективность, малую таблицу, устаревшую статистику и запросы, которые не доходят до этой базы.
Счётчики накапливаются с момента сброса статистики и не показывают качество каждого отдельного запроса. Свяжите их с pg_stat_statements, журналом медленных запросов и выборочными планами EXPLAIN.
Создание GiST на рабочей таблице требует учёта блокировок
Обычная команда CREATE INDEX разрешает чтение таблицы и блокирует записи до завершения построения. Для нагруженной таблицы применяют CONCURRENTLY:
CREATE INDEX CONCURRENTLY room_bookings_period_gist_idx
ON room_bookings
USING GIST (period);Конкурентное построение выполняет больше работы и занимает больше времени. Команда не запускается внутри явного блока транзакции. На одной таблице одновременно может строиться только один конкурентный индекс.
После ошибки конкурентного построения в каталоге может остаться невалидный индекс. Проверьте состояние:
SELECT
c.relname AS index_name,
i.indisvalid,
i.indisready
FROM pg_index AS i
JOIN pg_class AS c
ON c.oid = i.indexrelid
WHERE c.relname = 'room_bookings_period_gist_idx';Невалидный объект удаляют и создают заново:
DROP INDEX CONCURRENTLY IF EXISTS room_bookings_period_gist_idx;Прогресс построения виден в pg_stat_progress_create_index
SELECT
pid,
relid::regclass AS table_name,
index_relid::regclass AS index_name,
command,
phase,
lockers_total,
lockers_done,
blocks_total,
blocks_done,
tuples_total,
tuples_done
FROM pg_stat_progress_create_index;Набор заполненных счётчиков зависит от метода индекса и текущей фазы.
Fillfactor и стратегия построения влияют на размер и вставки
GiST принимает параметр fillfactor. Он задаёт целевую плотность заполнения страниц во время построения. Свободное место уменьшает вероятность ранних разделений страниц при последующих вставках и увеличивает начальный размер индекса.
CREATE INDEX room_bookings_period_gist_idx
ON room_bookings
USING GIST (period)
WITH (fillfactor = 80);Универсального значения для всех нагрузок нет. Таблица с редкими изменениями обычно не нуждается в агрессивном запасе. Поток случайных вставок и обновлений может выиграть от меньшего заполнения, если измерения показывают частые разделения и рост индекса.
Для GiST доступен параметр buffering, который управляет буферизованным построением в классах без подходящего сортировочного пути:
CREATE INDEX large_ranges_gist_idx
ON large_ranges
USING GIST (period)
WITH (buffering = on);btree_gist в PostgreSQL 18 по умолчанию использует sortsupport и сортированное построение, которое обычно заметно быстрее старой вставочной схемы. Параметр buffering позволяет явно запросить буферизованный режим.
Изменение параметра существующего индекса не переписывает все страницы автоматически:
ALTER INDEX room_bookings_period_gist_idx
SET (fillfactor = 75);Для полного применения нового расположения выполните REINDEX в подходящее окно обслуживания.
VACUUM и REINDEX решают разные задачи обслуживания
VACUUM удаляет мёртвые версии строк из доступной области хранения таблицы и обслуживает индексы. Autovacuum выполняет эту работу автоматически при стандартной конфигурации. Частые обновления индексируемого столбца создают новые индексные записи и увеличивают объём обслуживания.
REINDEX полностью перестраивает индекс:
REINDEX INDEX room_bookings_period_gist_idx;Для уменьшения блокировок на рабочей системе доступна конкурентная форма:
REINDEX INDEX CONCURRENTLY room_bookings_period_gist_idx;Поводы для перестроения включают повреждение, подтверждённое разрастание, смену параметров хранения и требования после отдельных обновлений расширений или правил сравнения. Регулярный REINDEX по календарю без измерений создаёт лишнюю нагрузку.
Размер индекса можно отслеживать так:
SELECT
pg_size_pretty(pg_relation_size('room_bookings_period_gist_idx')) AS index_size,
pg_size_pretty(pg_relation_size('room_bookings')) AS table_size;Для оценки bloat нужны специализированные расширения и аккуратная интерпретация. Рост размера сам по себе может отражать рост данных.
Частые ошибки связаны с оператором, выражением и селективностью
USING GIST без подходящего класса завершается ошибкой
Команда вида:
CREATE INDEX documents_body_gist_idx
ON documents
USING GIST (body);для обычного text завершится ошибкой, если PostgreSQL не находит GiST-класс по умолчанию. Для похожего текста нужен pg_trgm и явный класс:
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX documents_body_gist_idx
ON documents
USING GIST (body gist_trgm_ops);Неподдерживаемый оператор оставляет индекс без работы
Индекс на period ускоряет &&, @>, <@ и другие операторы класса диапазонов. Произвольная функция над границами диапазона может не получить тот же маршрут:
WHERE lower(period) <= :to_ts
AND upper(period) >= :from_tsЗапись условия через оператор пересечения выражает задачу прямо:
WHERE period && tstzrange(:from_ts, :to_ts, '[)')Функция вокруг столбца меняет индексируемое выражение
Пространственный индекс на geom не совпадает с постоянным запросом по ST_Transform(geom, 3857). Для такого сценария создают индекс выражения при условии, что функция допускается в индексном выражении:
CREATE INDEX places_geom_3857_gist_idx
ON places
USING GIST (ST_Transform(geom, 3857));Запрос должен использовать ту же форму:
WHERE ST_Intersects(
ST_Transform(geom, 3857),
:area_3857
)Широкое условие читает большую часть дерева
Запрос, охватывающий 80 процентов геометрий или временных диапазонов, редко получает большой выигрыш от индекса. GiST сокращает поиск, когда условие отбрасывает значительную долю дерева.
Короткий шаблон ослабляет pg_trgm
Условие ILIKE '%a%' содержит мало полезных триграмм. Индекс может вернуть большую долю строк. Для автодополнения по одному или двум символам применяют отдельную стратегию: префиксный B-tree с подходящим классом, нормализованный поисковый столбец, специализированный движок или ожидание минимальной длины ввода.
Составной индекс начинается с слабого ключа
Первый столбец GiST с двумя-тремя различными значениями плохо разделяет дерево. Порядок полей выбирают по селективности и частоте условий, затем подтверждают планами.
Дублирующие индексы увеличивают стоимость записи
GiST, GIN и B-tree на одном столбце могут быть оправданы разными запросами. Каждый дополнительный индекс увеличивает объём хранения, WAL, время INSERT, UPDATE, DELETE, VACUUM и резервного копирования. Сохраняйте индекс, когда рабочая нагрузка показывает понятную пользу.
Проверка только на маленькой таблице искажает вывод
На тысяче строк последовательный проход часто дешевле индексного доступа. Тестовый набор должен воспроизводить объём, распределение, долю обновлений и параметры запроса рабочей системы.
Практический алгоритм выбора GiST
Начните с трёх реальных медленных запросов. Для каждого выпишите тип столбца, оператор условия, долю возвращаемых строк и требуемый порядок результата.
Шаг 1. Зафиксируйте оператор задачи
Примеры:
- пересечение времени —
&&дляtstzrange; - точка внутри области —
ST_ContainsилиST_Intersects; - объекты в радиусе —
ST_DWithin; - ближайшие строки —
<->изpg_trgm; - запрет пересечений —
EXCLUDE USING GIST; - поиск сети, содержащей адрес — сетевой оператор с
inet_ops.
Шаг 2. Найдите операторный класс
Проверьте документацию типа или расширения и каталог pg_opclass. Убедитесь, что класс поддерживает именно нужный оператор и KNN-порядок, если запрос содержит ORDER BY ... LIMIT.
Шаг 3. Создайте минимальный индекс
Начните с одного ключевого столбца:
CREATE INDEX ... USING GIST (...);INCLUDE, составные поля, siglen, частичный предикат и уменьшенный fillfactor добавляйте после измерений или при ясном ограничении модели данных.
Шаг 4. Обновите статистику
ANALYZE target_table;Шаг 5. Снимите план с реальными параметрами
EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
SELECT ...;Сравните время, блоки, число кандидатов, recheck и итоговые строки.
Шаг 6. Измерьте влияние на запись
Проверьте скорость массовой загрузки, одиночных вставок и обновлений индексируемого поля. Для таблицы с интенсивной записью выигрыш чтения должен покрывать постоянную стоимость обслуживания индекса.
Шаг 7. Наблюдайте индекс после запуска
Используйте pg_stat_user_indexes, pg_stat_statements, планы медленных запросов и размер индекса. Через несколько недель удалите дублирующий объект только после проверки полного цикла нагрузки, включая редкие отчёты и фоновые задания.
GiST особенно полезен для работы с данными, имеющими форму, протяжённость или расстояние
GiST даёт PostgreSQL общий механизм для задач, которые описываются пересечением, включением, близостью и доменными отношениями. Диапазоны, PostGIS, pg_trgm, полнотекстовые сигнатуры и ограничения EXCLUDE используют один каркас с разными операторными классами.
Надёжный выбор строится от SQL-оператора к операторному классу и индексу. Создайте индекс под конкретный запрос, проверьте его через EXPLAIN (ANALYZE, BUFFERS) и сохраните только тот вариант, который уменьшает время и чтение блоков на рабочем распределении данных.