Индекс (базы данных)
| Назначение | ускорение поиска, сортировки и соединений |
|---|---|
| Основная структура | B-дерево (B+-дерево) |
| Другие виды | хеш, GIN, GiST, BRIN, векторные (HNSW, IVF) |
| Цена | место на диске, замедление INSERT/UPDATE/DELETE |
| Команда | CREATE INDEX |
Индекс в базе данных — вспомогательная структура, которая позволяет находить нужные строки таблицы, не просматривая её целиком. Аналогия — предметный указатель в конце книги: чтобы найти упоминания термина, не нужно перелистывать все страницы. Индекс хранит значения выбранных столбцов в упорядоченном (или иначе организованном) виде вместе со ссылками на строки таблицы.
Индексы — главный инструмент ускорения запросов в реляционных базах. Хорошо подобранный индекс превращает запрос, читающий миллионы строк, в запрос, читающий несколько страниц. Но за это приходится платить местом и скоростью записи.
Зачем нужен
Без индекса единственный способ выполнить WHERE email = 'a@b.c' — последовательное сканирование (sequential scan, full table scan): прочитать все строки и проверить условие. Время растёт линейно с размером таблицы. Индекс на основе B-дерева находит значение за логарифмическое число шагов: для таблицы в сто миллионов строк это три–четыре уровня дерева.
Индекс помогает не только фильтрации:
- сортировке —
ORDER BYпо индексированному столбцу не требует отдельной сортировки; - соединениям — поиск пары по внешнему ключу в другой таблице;
- уникальности — ограничения
PRIMARY KEYиUNIQUEреализуются уникальными индексами; - агрегатам —
min/maxпо индексу читаются с краёв дерева.
Виды индексов
| Вид | Как устроен | Для чего подходит |
|---|---|---|
| B-tree | сбалансированное дерево с упорядоченными ключами | равенство, диапазоны, сортировка, префикс LIKE 'abc%' — вариант по умолчанию |
| Хеш | хеш-таблица | только проверка на равенство |
| GIN | инвертированный индекс | массивы, jsonb, полнотекстовый поиск |
| GiST / SP-GiST | обобщённые деревья поиска | геометрия, диапазоны, ближайшие соседи |
| BRIN | сводки по блокам страниц | огромные таблицы, где значения коррелируют с физическим порядком (время вставки) |
| Векторные (HNSW, IVF) | графы и кластеры векторов | приближённый поиск ближайших векторов |
Список выше соответствует методам доступа PostgreSQL;[2] в других СУБД набор отличается, но B-дерево (точнее, его вариант B+-дерево) есть везде. Саму структуру описали Рудольф Байер и Эдвард Маккрейт в 1970–1972 годах.[5] Векторные индексы добавляет расширение pgvector.
Составные, частичные и покрывающие индексы
Составной индекс строится по нескольким столбцам, и порядок столбцов важен. Индекс по (customer_id, created_at) ускоряет запросы по customer_id и по паре customer_id + диапазон дат, но почти не помогает запросу только по created_at — так же, как телефонный справочник, отсортированный по фамилии и имени, бесполезен для поиска по одному имени.[3]
Частичный индекс включает только строки, удовлетворяющие условию, — например, только неоплаченные заказы. Индекс по выражению строится по результату функции, например lower(email). Покрывающий индекс содержит все столбцы, нужные запросу, и тогда СУБД отвечает, не заходя в таблицу (index-only scan).[4]
-- составной индекс под частый запрос «последние заказы покупателя»
CREATE INDEX orders_customer_created_idx ON orders (customer_id, created_at DESC);
-- частичный индекс
CREATE INDEX orders_unpaid_idx ON orders (created_at) WHERE paid = false;
-- индекс по выражению для поиска без учёта регистра
CREATE UNIQUE INDEX users_email_lower_idx ON users (lower(email));
-- в PostgreSQL: построить без блокировки записи в таблицу
CREATE INDEX CONCURRENTLY orders_total_idx ON orders (total);
Цена индексов
- Запись медленнее. Каждый
INSERT,UPDATEилиDELETEобязан обновить все индексы таблицы. - Место. Индексы занимают диск и память; на нагруженных таблицах их суммарный объём нередко превышает объём данных.
- Не всегда используются. Если условие отбирает значительную часть таблицы, последовательное чтение быстрее тысяч случайных переходов по индексу, и оптимизатор это учитывает.
Почему индекс не используется
Типичные причины: функция над столбцом в условии (WHERE lower(email) = ... при индексе по email), неявное приведение типов, LIKE '%abc' с подстановочным знаком в начале, неверный порядок столбцов в составном индексе, устаревшая статистика. Проверять нужно не догадками, а планом выполнения через EXPLAIN: в нём видно, выбран ли Index Scan или Seq Scan. Подробный разбор типичных ошибок — в книге Маркуса Винанда «Use The Index, Luke».[6]
Практические правила
- Индексируйте столбцы из
WHERE,JOINиORDER BYсамых частых и самых медленных запросов, а не «все подряд». - Внешние ключи почти всегда стоит индексировать: иначе удаление родительской строки сканирует дочернюю таблицу.
- Периодически ищите неиспользуемые индексы (в PostgreSQL — представление
pg_stat_user_indexes) и удаляйте их. - На больших таблицах создавайте индексы без блокировки записи и оформляйте их миграциями.[1]
См. также
Источники
- PostgreSQL Documentation — Indexes
- PostgreSQL Documentation — Index Types
- PostgreSQL Documentation — Multicolumn Indexes
- PostgreSQL Documentation — Index-Only Scans and Covering Indexes
- R. Bayer, E. McCreight — Organization and Maintenance of Large Ordered Indexes (1972)
- Markus Winand — Use The Index, Luke