Услуги Портфолио Контакты БлогНовости +7 (800) 511‑32‑27 Обсудить проект
Блог

Индексы в PostgreSQL: 6 типов и классы

25 мая 2023·5 мин чтения
Логотип YuSMP Group
Автор материалаРедакция компании YuSMP GroupБлог YuSMP Group
Профиль редакции
Индексы в PostgreSQL. Типы и классы

TL;DR. PostgreSQL поддерживает шесть типов индексов — B-Tree, Hash, GiST, SP-GiST, GIN и BRIN, каждый под свою задачу: от точного совпадения до полнотекстового поиска. В отличие от SQL Server и MySQL, постоянного «кластерного индекса» здесь нет: таблица хранится как неупорядоченная куча (heap), а физическую переупорядочку по индексу выполняет разовая команда CLUSTER. Разбираем типы, классы, синтаксис CREATE INDEX и случаи, когда индекс не ускоряет запрос, а замедляет запись.

PostgreSQL — популярная реляционная система управления базами данных. Чтобы запросы к большим таблицам не превращались в полное сканирование, используют индексы в базах данных (далее в тексте — БД) — отдельные структуры, которые хранят значение столбца и TID (указатель на строку в таблице), а не сами данные. Ниже рассмотрим все типы индексов SQL в PostgreSQL и покажем, как их применять на практике.


В блоге студии web-разработки YuSMP Group пишем на различные темы, которые относятся к разработке. Статьи будут интересны как специалистам, так и тем, кто только знакомится с IT.

Подбор индексов и оптимизация запросов к БД — часть разработки backend и API на заказ в YuSMP Group: мы проектируем структуру базы данных под реальную нагрузку проекта, а не по шаблону.

Индекс: это что такое и зачем он нужен

Индекс — объект базы данных, который играет ключевую роль в обеспечении эффективной работы с информацией. Его предназначение — ускорение операций поиска, сортировки и фильтрации, что критически важно при работе с большими таблицами.

Индекс — это дополнительная структура, которая хранит значения столбца (или набора столбцов) и TID — указатель на физическое место строки в таблице. Найдя нужное значение в индексе, PostgreSQL сразу переходит по TID к строке в таблице, вместо того чтобы читать её целиком с диска. Это позволяет избежать полного сканирования таблицы (Seq Scan) и сокращает количество операций ввода-вывода.

Как работают индексы: от создания до использования в запросах

Создание

Индекс создаётся на одном или нескольких столбцах таблицы командой CREATE INDEX. При создании PostgreSQL анализирует значения столбца и строит структуру, обеспечивающую быстрый доступ к записям с этими значениями. Процесс может быть явным (разработчик выполняет CREATE INDEX) или неявным — индекс автоматически создаётся для PRIMARY KEY и UNIQUE-ограничений.

Структура

Индексы реализуются разными структурами данных — B-дерево, хеш-таблица, Generalized Search Tree и другие. Каждая имеет свои особенности и подходит для конкретного типа запросов; подробно разбираем все шесть типов индексов PostgreSQL ниже.

Обновление

При вставке, обновлении или удалении строк таблицы все индексы, построенные на изменённых столбцах, обновляются автоматически — PostgreSQL берёт эту работу на себя, чтобы гарантировать согласованность индекса с данными в таблице. Именно поэтому индексы не бесплатны: чем больше индексов на таблице, тем дороже становится запись.

Использование в запросах

Планировщик запросов PostgreSQL использует информацию о существующих индексах при построении плана выполнения. Он анализирует структуру запроса, оценивает селективность условий и статистику таблицы (собранную ANALYZE), а затем выбирает индекс, последовательное сканирование или их комбинацию — то, что даст меньшую расчётную стоимость выполнения.

Какие операторы поддерживают индексы

Индексы ускоряют разные типы операций: точное совпадение значений (оператор «=»), поиск по диапазону (операторы «<», «>», «<=», «>=»), частичное совпадение (например, оператор LIKE PostgreSQL с якорным префиксом), а также сортировку (ORDER BY). Какой именно оператор поддерживает конкретный индекс, зависит от его типа — не любой postgres indexes одинаково хорош для любой операции.

Разработка и оптимизация backend на PostgreSQL
Узнать стоимость

Шесть типов индексов PostgreSQL

В PostgreSQL есть шесть встроенных типов индексов, и у каждого — своя специализация. Ниже сводная таблица, а после неё — разбор по каждому типу.

ТипДля чегоОсновные операторыПример применения
B-TreeУниверсальный, тип по умолчанию=, <, <=, >=, >, BETWEEN, LIKE 'abc%'Поиск по id, email, дате
HashТолько точное совпадение=Редко используется отдельно от B-Tree
GiSTГеометрия, диапазоны, ближайший сосед&&, <->, @>PostGIS, диапазонные типы данных
SP-GiSTНеоднородные структуры данных<<, >>, ~IP-адреса, префиксные деревья
GINСоставные значения: массивы, JSONB@>, ?, ?&, ?|, @@Полнотекстовый поиск, фильтры по JSONB
BRINОгромные таблицы с естественным порядком=, <, <=, >=, >Таблицы логов, партиции по дате
Схема работы B-Tree индекса: от узла дерева к строкам таблицы

B-дерево (B-Tree)

Наиболее распространённый тип в PostgreSQL и индекс по умолчанию для команды CREATE INDEX без явного указания типа. Поддерживает быстрый поиск и сортировку данных, подходит для равенства, диапазонного и частичного совпадения (оператор LIKE PostgreSQL с якорным префиксом). B-Tree можно создать на одном или нескольких столбцах.

Хеш (Hash)

Использует хеш-функцию для определения местоположения записей. Обеспечивает быстрый доступ к данным при точном совпадении (оператор «=»), но не поддерживает поиск по диапазону или частичное совпадение. На практике применяется реже B-Tree: с современных версий PostgreSQL хеш-индексы журналируются (WAL-logged) и безопасны для репликации, но по функциональности всё ещё уступают B-Tree, который справляется с равенством не хуже.

GiST (Generalized Search Tree)

Обобщённая инфраструктура поиска, которая позволяет определять местоположение на основе произвольных условий, а не только точного совпадения. Используется для географических данных (PostGIS), диапазонных типов и поиска «ближайшего соседа» — например, найти пять ближайших точек к заданным координатам.

SP-GiST (Space-Partitioned GiST)

Родственник GiST, оптимизированный под неоднородные (не сбалансированные) структуры данных — квадродеревья, k-мерные деревья, префиксные деревья. Хорошо подходит для данных с неравномерным распределением: IP-адресов, телефонных префиксов, иерархических идентификаторов.

GIN (Generalized Inverted Index)

Обратный индекс: индексирует не значение столбца целиком, а каждый его элемент — поэтому эффективен для полнотекстового поиска, а также для массивов и JSONB, где нужно искать конкретные вхождения внутри составного значения. Строится медленнее и занимает больше места, чем B-Tree, но при частых обновлениях можно использовать GIN с fastupdate для ускорения записи.

BRIN (Block Range Index)

Хранит не значения каждой строки, а сводную информацию (min/max) по блокам физически последовательных страниц таблицы. Занимает на порядки меньше места, чем B-Tree, и хорошо работает на очень больших таблицах с естественным порядком данных — например, таблица логов или события, отсортированные по времени вставки. На таблице без естественного порядка BRIN почти бесполезен.

Классы индексов и миф о «кластерном индексе» в PostgreSQL

Важная оговорка, которую часто упускают в статьях про индексы: в PostgreSQL нет постоянного «кластерного индекса» в том смысле, в каком он есть в SQL Server или MySQL/InnoDB. В этих СУБД кластерный индекс — постоянное свойство таблицы: строки физически хранятся на диске в порядке ключа. PostgreSQL устроен иначе: таблица — это heap («куча»), строки в ней размещаются в порядке вставки, без гарантии физического упорядочивания по какому-либо индексу.

Разовая физическая переупорядочка строк таблицы командой CLUSTER

Команда CLUSTER: разовая физическая переупорядочка

Физически переупорядочить строки таблицы на диске по индексу можно командой CLUSTER — но, в отличие от SQL Server, это не постоянное свойство таблицы, а разовая операция. После CLUSTER строки временно лежат в порядке индекса, что улучшает производительность запросов с диапазонными условиями за счёт локальности чтения. Но каждый новый INSERT и большинство UPDATE добавляют строки в произвольные блоки heap, и порядок постепенно нарушается — потребуется повторный CLUSTER. На одну таблицу можно «привязать» только один индекс для CLUSTER, и сама операция блокирует таблицу на запись, пока выполняется.

Составной индекс (Composite)

Создаётся на нескольких столбцах таблицы. Позволяет оптимизировать поиск по комбинации значений в этих столбцах и полезен, когда в запросах регулярно используются условия фильтрации или сортировки сразу по нескольким полям — например, «пользователь + дата создания заказа». Порядок столбцов в составном индексе важен: индекс на (user_id, created_at) эффективно ускорит и запрос только по user_id, и запрос по обоим полям, но не ускорит запрос только по created_at.

Пример: создаём индекс для таблицы users

Допустим, у нас есть таблица users с колонками id, name и email. Чтобы создать B-дерево (B-Tree) по столбцу email, используем такой SQL-запрос:

CREATE INDEX idx_users_email ON users USING btree (email);

Этот индекс ускорит операции поиска и сортировки по полю email в таблице users. Если приложение обычно ищет пользователей по паре «email + дата регистрации», эффективнее составной индекс:

CREATE INDEX idx_users_email_created ON users (email, created_at);

Когда индексы снижают производительность

Индекс — не бесплатный ускоритель, а компромисс между скоростью чтения и скоростью записи. Несколько типичных ситуаций, когда индексов становится слишком много или они работают против вас:

Избыточная запись. Каждый INSERT, UPDATE или DELETE обновляет все индексы таблицы, а не только изменённые данные. Пять индексов на активно пишущей таблице — это пять дополнительных операций записи на каждое изменение строки.

Раздувание (bloat). После массовых DELETE или частых UPDATE индекс может «раздуться»: занимать больше места и работать медленнее, даже если актуальных данных стало меньше — старые версии записей физически остаются в структуре индекса до очистки.

Неиспользуемые индексы. Индекс, который планировщик ни разу не выбрал для запроса, всё равно потребляет дисковое пространство и замедляет запись — от него стоит избавляться.

Дублирующиеся индексы. Индекс на (a, b) делает избыточным отдельный индекс на (a) — B-Tree по нескольким столбцам уже ускоряет запросы по первому столбцу композита.

Как проверить, какие индексы используются

Посмотреть все индексы таблицы можно запросом к системному представлению:

SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'users';

Чтобы узнать, использует ли конкретный запрос индекс, выполните EXPLAIN ANALYZE перед ним:

EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@example.com';

В плане выполнения Index Scan или Index Only Scan означают, что PostgreSQL использует индекс; Seq Scan — что читается вся таблица целиком. Найти индексы, которые планировщик ни разу не выбрал, поможет системное представление pg_stat_user_indexes (столбец idx_scan = 0). Если индекс раздулся после массовых изменений — пересоберите его командой REINDEX (в проде — REINDEX CONCURRENTLY, чтобы не блокировать таблицу на время пересборки).

Заключение

Индексы в PostgreSQL — мощный инструмент для оптимизации производительности запросов к базе данных. Знание всех шести типов и понимание того, как PostgreSQL физически хранит данные (heap, а не постоянный кластерный индекс), позволяет найти индекс, который лучше всего подходит для конкретного сценария. Важно помнить: индексы ускоряют чтение и замедляют запись, поэтому их количество и типы нужно балансировать с реальной нагрузкой на таблицу, а не создавать «про запас».

Специалисты, которые занимаются веб-услугами и разработкой в YuSMP Group, хорошо знакомы с проектированием и оптимизацией баз данных PostgreSQL и MySQL. В стек команды входят навыки из разных областей разработки — от backend и API до PHP, JS, Java, Kotlin, Swift, Flutter и других языков.

Частые вопросы об индексах в PostgreSQL

Сколько индексов можно создать на одну таблицу в PostgreSQL?

Формального ограничения нет — можно создать десятки индексов на одну таблицу. Но у каждого есть цена: любой INSERT, UPDATE или DELETE обновляет все индексы таблицы, а не только изменённые данные. На практике оптимально 3–6 индексов на таблицу с интенсивной записью; для read-heavy таблиц (справочники, витрины) их может быть больше.

Что такое кластерный индекс в PostgreSQL — он вообще есть?

Постоянного кластерного индекса, как в SQL Server или MySQL/InnoDB, в PostgreSQL нет. Таблица хранится как heap — строки лежат в порядке вставки, без физической привязки к какому-либо индексу. Разово упорядочить строки на диске по индексу можно командой CLUSTER, но это не постоянное свойство: новые INSERT и UPDATE снова нарушают порядок.

Индекс всегда ускоряет запрос?

Нет. Индекс ускоряет выборку по условию, которое он покрывает, но замедляет запись — каждое изменение строки обновляет и индекс. Если таблица маленькая или запрос возвращает большую часть строк, планировщик PostgreSQL может выбрать последовательное сканирование (Seq Scan) как более быстрый вариант, даже при наличии подходящего индекса.

Чем B-Tree отличается от GIN?

B-Tree — индекс по умолчанию для скалярных значений: чисел, дат, строк, он ускоряет равенство, диапазоны и сортировку. GIN — обратный индекс для составных значений: массивов, JSONB и полнотекстового поиска, он индексирует не всё значение целиком, а каждый его элемент, что позволяет искать вхождения внутри массива или JSON-документа.

Как узнать, использует ли запрос индекс?

Выполните EXPLAIN ANALYZE перед запросом. В плане выполнения будет виден тип сканирования: Index Scan или Index Only Scan означают, что индекс используется, Seq Scan — что PostgreSQL читает всю таблицу целиком. Список неиспользуемых индексов можно получить запросом к системному представлению pg_stat_user_indexes.

Нужно ли пересоздавать индекс после массового удаления строк?

После массового DELETE или частых UPDATE индекс может «раздуться» (bloat) — занимать больше места и работать медленнее, даже если актуальных данных стало меньше. В таких случаях помогает REINDEX (или REINDEX CONCURRENTLY на проде, чтобы не блокировать таблицу) — он пересобирает индекс с нуля и убирает накопленный мусор.

Проекты, которые мы создали, успешно функционируют и приносят пользу владельцам: автоматизируют бизнес-процессы, развивают бренд и увеличивают прибыль. Свяжитесь с нами, чтобы получить бесплатную консультацию по проекту. Наши эксперты подробно расскажут, как из вашей идеи сделать качественный продукт на лучших условиях.

Нужны разработчики в команду?

Подберём инженеров нужного стека под ваш проект — быстрый старт.

Обсудить команду