Получить доступ

Индексы и планы запросов

Устройство B-tree, hash vs B-tree, составной и covering индекс, цена записи и когда индекс не используется.

Зачем индекс и чем за него платят

Индекс — отдельная структура для быстрого поиска строк без полного сканирования таблицы. Он ускоряет выборки по подходящему условию, но имеет цену: каждый INSERT/UPDATE/DELETE должен обновить ещё и индекс, а сам индекс занимает место на диске и в памяти.

Поэтому индексировать «всё подряд» вредно: лишние индексы замедляют запись и могут не использоваться. Индексы добавляют под реальные паттерны запросов.

Как устроен B-tree

B-tree (точнее B+-tree) — сбалансированное дерево, где значения упорядочены, а листья связаны в список. Поиск, вставка и удаление — O(log n), а упорядоченность даёт бесплатно диапазоны (>, <, BETWEEN), сортировку (ORDER BY) и поиск по префиксу.

Именно из-за упорядоченности B-tree — индекс по умолчанию почти во всех СУБД: он покрывает и равенство, и диапазоны, и сортировку.

Сбалансированное B-дерево: путь поиска от корня через внутренние узлы к листу за O(log n) против полного сканирования таблицы.
B-tree спускается от корня к листу за O(log n) — намного дешевле полного сканирования.

Hash против B-tree

Hash-индекс хранит хэш значения и хорош только для строгого равенства, зато поиск близок к O(1). Он не поддерживает диапазоны, сортировку и префиксы.

Вывод: hash оправдан в узких сценариях равенства при особых нагрузках, но универсальный выбор — B-tree.

Где лежит строка: clustered, non-clustered и heap

Индекс — это структура поиска, но важно ещё, где лежат сами данные строки. Кластерный индекс (clustered) задаёт физический порядок строк на диске: строки хранятся прямо в листьях B-tree этого индекса. Кластерный индекс может быть только один (нельзя разложить данные двумя способами сразу). В MySQL/InnoDB кластерный индекс — это первичный ключ (поэтому выбор PK влияет на раскладку всей таблицы).

Некластерный индекс (non-clustered) — отдельная структура: в её листьях лежат не строки, а указатели на строку. Если есть кластерный индекс, указатель — это значение ключа кластерного индекса; иначе таблица хранится как heap (куча, без порядка) и указатель — это физический адрес строки (RID/ctid).

Отсюда bookmark lookup (он же key/RID lookup): найдя строку в некластерном индексе, база делает второй переход за остальными колонками в кластерный индекс или heap. Это лишние чтения — именно поэтому covering index (включающий все нужные колонки) так выгоден: он убирает этот второй переход. У PostgreSQL модель чуть иная: таблица всегда heap, а кластеризацию даёт только разовая команда CLUSTER, не поддерживаемая автоматически.

Составной индекс и левый префикс

Составной индекс строится по нескольким колонкам в заданном порядке, и B-tree эффективно использует только левый префикс. Индекс (user_id, created_at) помогает запросам по user_id и по user_id + created_at, но не обязан помочь запросу по одному created_at.

Практическое правило построения порядка: сначала колонки с условием равенства, затем колонка диапазона/сортировки. Реальный выбор зависит от селективности и частоты запросов.

create index idx_orders_user_created
on orders (user_id, created_at desc);

-- использует индекс целиком (равенство + сортировка по префиксу)
select * from orders
where user_id = ?
order by created_at desc
limit 50;

Covering index

Covering index содержит все колонки, нужные запросу (в ключе или через INCLUDE), и тогда база отвечает прямо из индекса, не читая строки таблицы — это index-only scan.

Цена — более широкий индекс и чуть более дорогая запись. Выгодно для горячих запросов, читающих немного колонок.

Когда индекс не используется

Функция над колонкой в условии (lower(email) = ?) обычно ломает использование обычного индекса — нужен функциональный индекс или нормализованное хранение. LIKE '%abc' с ведущим символом подстановки тоже не использует B-tree.

При низкой селективности (условие отбирает большую долю таблицы) планировщик может сознательно выбрать полный скан как более дешёвый. Поэтому план всегда стоит проверять через EXPLAIN, а не угадывать.

Как отвечать на собеседовании

Сильный ответ связывает идею «индекс ускоряет чтение ценой записи и памяти» с устройством B-tree (упорядоченность → диапазоны и сортировка) и с левым префиксом составного индекса. Упомяните, что выбор подтверждают через EXPLAIN.

Вопросы по теме

Читайте также

Закрытая бета

Знать тему — половина дела

На живом собеседовании sotto слышит вопрос интервьюера и показывает короткий ответ и план. Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем