awesome-everything EN
↑ Обратно к восхождению

Базы данных

Что такое индекс и как он ускоряет запросы

Суть Индекс — это отсортированная копия значений столбца, которая позволяет Postgres пропускать полное сканирование таблицы. Каждый индекс обменивает скорость записи и место на диске на скорость чтения.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на junior-высоте — поверхность
◷ 12 min

Дашборд команды «фильтр по status» выполняется за 50 мс на 100k строк в staging. В продакшне с 50M строк — 8 секунд. Схема корректна. Запрос корректен. Одна DDL-строка — CREATE INDEX CONCURRENTLY ON orders(status) — возвращает 20 мс. Эта строка — весь смысл урока.

Что такое индекс

Индекс — это отдельная структура данных (обычно B-tree), которая хранит отсортированную копию значений одного или нескольких столбцов плюс указатели на фактические строки. Когда запрос фильтрует по индексированному столбцу, Postgres проходит дерево за O(log n) шагов вместо сканирования каждой строки.

Метафора книжного индекса точна: предметный указатель книги — это отсортированный список терминов с номерами страниц. Чтобы найти «MVCC», открываешь указатель (один поиск O(log n)), читаешь номер страницы и переходишь туда. Без указателя — сканируешь каждую страницу. Индекс базы данных идентичен: копия значений столбца, организованная для быстрого поиска по одному конкретному вопросу.

Без индексаС индексом
Sequential scan — каждая строка просматриваетсяB-tree walk — O(log n), затем fetch matching rows
1M строк: ~10мс; 100M строк: ~2–10с1M или 100M строк: ~1–5мс
Нет доп. хранилища, нет overhead на запись~10–30% размера таблицы на индекс; каждая запись поддерживает индекс

Цена: индексы облагают налогом каждую запись

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

Типичный overhead на запись одного индекса: 5–50 мкс на операцию записи. При 10 000 inserts/сек с десятью индексами это дополнительные 0,5–5 мс overhead в секунду — измеримо, и часто узкое место при избыточной индексации.

Размер индекса на диске: примерно bytes_per_key × row_count × 1.3. Для BIGINT-ключа (8 байт) и таблицы из 100M строк получается ~1 GB индекс. Каждый дополнительный индекс умножает это.

Конкретный сценарий

Запрос: SELECT * FROM orders WHERE user_id = 42 на таблице с 10M строк. Без индекса: sequential scan, ~2 секунды. После CREATE INDEX ON orders(user_id): index lookup плюс heap fetch, ~3 миллисекунды. Тот же запрос, ускорение в 600 раз, одна DDL-строка.

Postgres поставляется с шестью типами индексов. B-tree — дефолт и покрывает 95% продакшн-использования: равенство, диапазон, ORDER BY, LIKE 'abc%'. Остальные пять (GIN, GiST, BRIN, Hash, Bloom) служат для специфических форм данных и рассматриваются в уроке 04.

Продакшн-правило: всегда использовать CONCURRENTLY

CREATE INDEX берёт блокировку ACCESS EXCLUSIVE — все чтения и записи заблокированы на всё время построения. На таблице с активным трафиком даже 5-минутная блокировка — это инцидент.

CREATE INDEX CONCURRENTLY использует многофазное построение без эксклюзивной блокировки. Он сканирует таблицу дважды и ждёт in-flight транзакций между фазами. Он в 2–3 раза медленнее и не может быть обёрнут в транзакцию, но не создаёт видимого воздействия на трафик приложения.

Если построение CONCURRENTLY завершается неудачей (например, уникальный индекс находит дубликат или процесс убивается), индекс остаётся в состоянии INVALID (indisvalid = false в pg_indexes). Планировщик рассматривает его как несуществующий. Исправление: DROP INDEX CONCURRENTLY и повторная попытка.

Почему это работает

Почему Postgres не индексирует каждый столбец по умолчанию? Потому что каждый индекс на write-heavy таблице — это налог на запись. Таблица с 20 индексами и 50k inserts/сек может тратить больше времени на поддержку индексов, чем на запись строк. Дисциплина намеренная: измеряй горячие запросы, добавляй индексы для них, проверяй и удаляй неиспользуемые. Избыточная и недостаточная индексация — одинаково разрушительные продакшн-паттерны.

Расставь шаги по порядку

Упорядочи шаги для правильного добавления индекса в продакшне:

  1. 1 Запусти EXPLAIN ANALYZE на медленном запросе, чтобы подтвердить seq scan как узкое место
  2. 2 Определи столбцы, по которым происходит фильтрация или сортировка
  3. 3 Выбери тип индекса (B-tree по умолчанию; GIN для JSONB; GiST для гео)
  4. 4 Используй CREATE INDEX CONCURRENTLY, чтобы не блокировать таблицу
  5. 5 Дождись завершения построения (может занять минуты или часы на больших таблицах)
  6. 6 Снова запусти EXPLAIN ANALYZE, чтобы убедиться, что планировщик теперь использует индекс
  7. 7 Следи за задержкой записи после деплоя — каждый индекс добавляет небольшую стоимость записи
Викторина

Создан индекс на столбец users(email). Какой запрос выигрывает от него?

Викторина

В чём цена добавления индекса?

Закончи аналогию

Заполни пропуск: индекс базы данных для SQL-запроса — то же, что _______ книги для поиска темы: копия части содержимого, организованная для быстрого поиска.

Вспомните перед уходом
  1. 01
    В двух предложениях: почему индексы не бесплатны и почему продакшн-команды не индексируют каждый столбец?
  2. 02
    Что происходит, если CREATE INDEX CONCURRENTLY падает посередине, и как восстановиться?
  3. 03
    Запрос делает sequential scan на таблице из 50M строк. Ты добавляешь индекс. Что показывает EXPLAIN ANALYZE до и после?
Итог

Индекс — это отдельная отсортированная структура данных (как правило B-tree), которая хранит копии значений одного или нескольких столбцов с указателями на соответствующие heap-строки. Он превращает полное табличное sequential scan (O(n)) в проход по дереву (O(log n)), сокращая 2-секундный запрос на 10M строк до 3мс. Цена: каждый индекс стоит 10–30% размера таблицы в хранилище и 5–50 мкс на запись на каждый индекс. Всегда добавляй индексы с CREATE INDEX CONCURRENTLY, чтобы не блокировать таблицу в продакшне. Если построение падает, проверь pg_indexes на indisvalid = false и повтори после удаления неверной записи.

Связанные уроки
встречается в174
Продолжить восхождение ↑Leading-column rule: почему порядок столбцов в composite-индексе важен
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.