EnglishРусский Map

Что вы не знали об индексах

title
Что вы не знали об индексах
type
summary
summary
Типичные ошибки при работе с индексами (порядок в составных, функции) и полезные типы индексов (частичные, покрывающие, функциональные)
tags
databases, postgresql, performance
created
2026-04-15
updated
2026-07-22
lang
ru
source_updated
2026-07-22
translated
2026-09-01
translator
lllm/antigravity/gemini-3.7-flash-medium

Практическое руководство по индексам баз данных на примере таблицы с покемонами. Рассматриваются компромиссы, которые обычно опускают в руководствах, и три типа индексов под конкретные задачи.

Базовый компромисс

Чтение ускоряется, запись замедляется.

Каждый INSERT, UPDATE или DELETE вынужден обновлять все индексы таблицы. Таблица с восемью индексами требует поддерживать девять структур данных. Кроме того, чем больше индексов, тем больше вариантов перебирает планировщик запросов - на быстрых точечных выборках время планирования может превысить время выполнения.

Почему индекс не работает

В составных индексах важен порядок колонок

Индекс по (type_1, type_2) создаёт структуру, отсортированную сначала по type_1, а затем внутри каждой группы - по type_2:

Bug      → Flying   → [Butterfree, ...]
         → Poison   → [Venonat, ...]
Electric → Flying   → [Zapdos, ...]
Fire     → Flying   → [Charizard, ...]
Water    → Flying   → [Wingull, ...]

Это помогает в запросах только по type_1 или по type_1 AND type_2 вместе. Но для запроса только по type_2 индекс бесполезен: записи с Flying разбросаны по группам Bug, Electric, Fire, Water, и перейти в одну конкретную точку дерева нельзя.

Если запросы только по type_2 выполняются так же часто, как и по type_1, создайте второй индекс.

Функции ломают использование индекса

SELECT * FROM pokemon WHERE lower(name) = 'pikachu';

Индекс построен по name, а не по lower(name). Для базы данных это разные вещи. Она переключается на полное сканирование таблицы (full table scan), попутно приводя каждое имя к нижнему регистру.

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

Не гадайте, измеряйте

EXPLAIN SELECT * FROM pokemon WHERE name = 'Pikachu';

Index Scan означает, что используется индекс. Seq Scan - читается каждая строка. Используйте EXPLAIN ANALYZE, чтобы выполнить запрос и получить реальное время работы, а не только оценки планировщика.

Менее известные типы индексов

Функциональные индексы

Индексируют результат выражения вместо самой колонки:

CREATE INDEX ON pokemon (lower(name));

Теперь условие WHERE lower(name) = 'pikachu' задействует индекс. Подходит для любой детерминированной, неизменяемой (immutable) функции.

Оговорка: если вам постоянно приходится индексировать lower(name), стоит задуматься, почему имена сразу не хранятся в нижнем регистре.

Частичные индексы

Индексируют только строки, подходящие под условие:

CREATE INDEX ON pokemon (name) WHERE is_legendary = true;

80 записей вместо 1000. Меньше размер, быстрее поиск, дешевле поддержка. Запросы, не попадающие под условие (WHERE is_legendary = false), переходят на сканирование таблицы, и это нормально - они всё равно выбирают большую часть строк.

Хорошо подходит для паттерна с мягким удалением (soft delete):

CREATE INDEX ON users (email) WHERE deleted_at IS NULL;

Покрывающие индексы

Когда индекс содержит все необходимые запросу колонки, база данных может вернуть ответ прямо из индекса, вообще не обращаясь к таблице. В EXPLAIN это отображается как Index Only Scan.

Используйте INCLUDE, чтобы добавить колонки без их сортировки:

CREATE INDEX ON pokemon (name) INCLUDE (base_attack);

Теперь такой запрос вообще не трогает таблицу:

SELECT name, base_attack FROM pokemon WHERE name = 'Pikachu';

Почему бы просто не включить base_attack в список индексируемых колонок? Потому что базе пришлось бы сортировать по ней при каждой записи - лишняя работа без всякой пользы, если поиск идёт только по имени.

Что почитать

Use The Index, Luke - подробное руководство по индексации в SQL.

database-design-and-implementation - книга Эдварда Скьоре строит описанные здесь механизмы изнутри: B-дерево, буферный пул, менеджер блокировок, планировщик, глава за главой. Лучший способ перестать воспринимать вывод EXPLAIN как магию - самому написать создающий его планировщик.

readings-in-database-systems - Red Book, бесплатно онлайн. Читать, когда практические вопросы исчерпаны и хочется увидеть всю область целиком: сборник статей о хранении, оптимизации запросов и транзакциях с комментариями и спорами редакторов.

См. также inverted-index для полнотекстового поиска и lsm-tree для хранилищ, оптимизированных под запись, где поддержка индексов обходится особенно дорого.