SQL

БЛОК 8. Производительность — 21. Индексы

📚 23 вопросовПройти тест →
Лекция

БЛОК 8. Производительность — 21. Индексы

SQL

21. Индексы

🧭 Введение: почему «правильный индекс» часто важнее сложного SQL

На реальных проектах медленный запрос редко означает «плохой разработчик».
Чаще причина проще: SQL корректный, но у БД нет подходящего пути быстро найти нужные строки.
В этой теме разберём базовый production-фундамент по индексам:
  • зачем индекс нужен и какой риск он закрывает;
  • как работает B-tree (базово);
  • как индекс помогает в WHERE, JOIN, ORDER BY;
  • как проектировать составные индексы;
  • почему LIKE '%text%' обычно не использует B-tree;
  • в каких случаях индекс не помогает и даже вредит.
💡 Совет: Индекс проектируют под конкретные запросы, а не «на всякий случай».
Вывод: Индексы — это инструмент управления скоростью чтения, а не магическая кнопка ускорения всего подряд.

⚠️ Проблема -> решение

Типичная проблема:
  1. таблица растёт, а запросы становятся всё медленнее;
  2. в приложении пытаются «кешировать всё», не разобрав SQL-паттерн;
  3. индексы добавляют хаотично и получают лишнюю нагрузку на INSERT/UPDATE/DELETE.
Решение:
  1. фиксировать ключевые сценарии чтения (WHERE/JOIN/ORDER BY);
  2. добавлять целевые индексы под эти сценарии;
  3. проверять план выполнения (EXPLAIN) и держать баланс чтений/записей.
🟢 Если совсем просто: Нет нужного индекса -> БД читает лишние строки -> запрос медленный.
🎯 Как понять, что этап прошёл успешно: Запросы по горячим сценариям выполняются быстрее и предсказуемее без регресса записи.

🛠️ Чем помогает и как работает

Индексы нужны там, где объём данных уже не позволяет «быстро пройтись по всей таблице».
Они особенно важны для API-листингов, фильтров по пользователю, джойнов транзакционных таблиц и сортировки свежих записей.
🟢 Если совсем просто: Индекс — это структура поиска, которая помогает не сканировать всю таблицу.
🎯 Как понять, что этап прошёл успешно: Вы можете объяснить, какой индекс закрывает каждый критичный запрос и почему.
Чем помогает:
  • ускоряет фильтрацию в WHERE;
  • ускоряет сопоставление в JOIN;
  • иногда позволяет отдать ORDER BY без отдельной сортировки;
  • стабилизирует время ответа при росте таблицы.
Как это работает:
  • Шаг 1: БД анализирует условие запроса;
  • Шаг 2: выбирает между Seq Scan и доступом через индекс;
  • Шаг 3: находит подходящий диапазон ключей индекса;
  • Шаг 4: читает нужные строки из таблицы;
  • Шаг 5: возвращает результат с минимально возможным количеством лишних чтений.
Вывод: Индекс — это альтернативный путь доступа к данным, который БД использует только если он выгоднее полного сканирования.

📚 Ключевые термины (простыми словами)

Перед практикой синхронизируем словарь.
🟢 Если совсем просто: Без общей терминологии сложно обсуждать производительность в ревью и на инцидентах.
🎯 Как понять, что этап прошёл успешно: Вы различаете Seq Scan, Index Scan, cardinality, selectivity, composite index.
  • Index (индекс) — отдельная структура данных для быстрого поиска строк.
  • B-tree index — базовый универсальный тип индекса для равенства, диапазонов и сортировки.
  • Seq Scan — последовательное чтение всей таблицы.
  • Index Scan — чтение через индекс с доступом к подмножеству строк.
  • Selectivity (селективность) — насколько условие «редкое» и сужает выборку.
  • Cardinality (кардинальность) — количество уникальных значений в колонке.
  • Composite index (составной индекс) — индекс сразу по нескольким колонкам.
  • Leftmost prefix — правило левого префикса в составном индексе.
  • Covering index — индекс, из которого можно получить всё нужное без чтения таблицы (в некоторых планах/СУБД).
  • Functional index — индекс по выражению (lower(email)), а не по «сырой» колонке.
Вывод: Эти термины закрывают базовую инженерную коммуникацию об индексах.

🧪 Практический якорь: EXPLAIN (ANALYZE, BUFFERS)

Индексы оценивают не «по ощущениям», а по плану и факту выполнения.
🟢 Если совсем просто: Сначала смотрим, что БД собирается делать, потом что она реально сделала.
🎯 Как понять, что этап прошёл успешно: Вы сравниваете запрос до/после индекса по EXPLAIN (ANALYZE, BUFFERS) и можете объяснить разницу.
Назначение: Понять, помог ли индекс именно вашему запросу, а не «вообще».
Простыми словами: EXPLAIN = план, ANALYZE = факт, BUFFERS = где и сколько читали.
Для новичка: Индекс «сработал» только если план стал лучше и реально снизились чтения/сортировка/время.
Пример:
EXPLAIN (ANALYZE, BUFFERS)SELECT  o.id,  o.created_atFROM orders oWHERE o.status = 'paid'ORDER BY o.created_at DESC, o.id DESCLIMIT 20;
🔎 Как это происходит на практике:
  • Контекст: запрос с LIMIT на горячем API стал медленным.
  • Действия: сравниваем план до/после индекса.
  • Результат: подтверждаем улучшение только по факту выполнения, а не по наличию индекса в схеме.
Характеристики:
  • EXPLAIN показывает выбранный путь (Seq Scan, Index Scan, Index Only Scan, сортировки);
  • ANALYZE даёт фактическое время и фактическое число строк;
  • BUFFERS помогает понять, где реальный I/O bottleneck.
Когда использовать: Перед добавлением индекса и сразу после него, особенно на критичных запросах.
Вывод: Главный инструмент темы «индексы» — это не CREATE INDEX, а корректная проверка через EXPLAIN (ANALYZE, BUFFERS).

🧱 1. Зачем нужен индекс

Индекс нужен, когда критичный запрос не должен читать всю таблицу ради нескольких строк.
Это ключевой инструмент масштабирования чтений в OLTP-системах.
🟢 Если совсем просто: Индекс нужен, чтобы быстро найти «где лежат нужные строки».
🎯 Как понять, что этап прошёл успешно: Вы можете показать запрос, который без индекса делает полный проход, а с индексом — точечный доступ.
Назначение: Сократить объём чтения данных и время ответа запроса.
Простыми словами: Вместо просмотра всех записей БД смотрит в «указатель».
Для новичка: Чем больше таблица, тем заметнее выигрыш от корректного индекса.
Аналогия: Это как указатель в книге: по теме сразу переходите на нужные страницы.
Пример:
SELECT  u.id,  u.emailFROM users uWHERE u.email = 'alice@example.com';
CREATE INDEX idx_users_emailON users (email);
🔎 Как это происходит на практике:
  • Контекст: авторизация и поиск аккаунта по email.
  • Действия: добавляем индекс на users.email.
  • Результат: запрос не читает всю таблицу пользователей при каждом логине.
Характеристики:
  • индекс ускоряет чтения;
  • индекс занимает место;
  • индекс замедляет часть операций записи (нужно поддерживать структуру индекса).
Когда использовать: Когда колонка часто участвует в фильтрации или соединениях.
Вывод: Индекс — это инвестиция в скорость чтения, но с ценой по хранению и записи.

🌳 2. B-tree (базово): главный рабочий тип индекса

B-tree — базовый и самый часто используемый тип индекса в SQL-проектах.
Он хорошо работает для =, >, <, BETWEEN и многих сценариев сортировки.
🟢 Если совсем просто: B-tree умеет быстро искать значение и диапазон значений.
🎯 Как понять, что этап прошёл успешно: Вы знаете, что B-tree подходит для равенства/диапазонов, но не для LIKE '%text%'.
Назначение: Дать универсальный быстрый доступ к данным по упорядоченному ключу.
Простыми словами: Значения в B-tree организованы так, чтобы быстро «спускаться» к нужному диапазону.
Для новичка: Если не знаете, с какого типа индекса начать, обычно начинайте с B-tree.
Пример:
CREATE INDEX idx_orders_created_atON orders (created_at);
SELECT  o.id,  o.created_atFROM orders oWHERE o.created_at >= DATE '2026-03-01'  AND o.created_at < DATE '2026-04-01';
🔎 Как это происходит на практике:
  • Контекст: отчёт «за месяц».
  • Действия: фильтрация по диапазону даты.
  • Результат: B-tree быстро находит нужный интервал вместо полного прохода.
Характеристики:
  • хорошо для точного поиска и диапазонов;
  • хорошо сочетается с ORDER BY при подходящем порядке колонок;
  • не панацея для произвольного текстового поиска.
Когда использовать: Для большинства OLTP-фильтров и джойнов по ключам.
Вывод: B-tree — дефолтный рабочий инструмент индексации для SQL-приложений.

🔎 3. Индекс для WHERE / JOIN / ORDER BY

Индекс проектируют под форму реальных запросов.
Если запрос фильтрует, джойнится и сортирует — индекс должен учитывать эти паттерны.
🟢 Если совсем просто: Индекс должен совпадать с тем, как вы читаете данные.
🎯 Как понять, что этап прошёл успешно: Вы можете объяснить отдельные индексы под WHERE, JOIN и сортировку горячего списка.
Назначение: Ускорить ключевые пути чтения в API и сервисных джобах.
Простыми словами: Нет индекса на поле из фильтра/джойна — почти всегда платите лишним чтением.
Для новичка: Если есть FK orders.user_id -> users.id, индекс на orders.user_id (child) почти всегда обязателен: для JOIN, операций над parent и стабильных планов под нагрузкой.
Пример (WHERE):
CREATE INDEX idx_orders_statusON orders (status);
Пример (JOIN):
CREATE INDEX idx_orders_user_idON orders (user_id);
Пример (ORDER BY + LIMIT top-N):
CREATE INDEX idx_orders_status_created_idON orders (status, created_at DESC, id DESC);
SELECT  o.id,  o.created_atFROM orders oWHERE o.status = 'paid'ORDER BY o.created_at DESC, o.id DESCLIMIT 20;
Пример (covering index / Index Only Scan, PostgreSQL):
CREATE INDEX idx_orders_paid_feed_coverON orders (status, created_at DESC, id DESC)INCLUDE (total_amount, user_id);
SELECT  o.id,  o.user_id,  o.total_amount,  o.created_atFROM orders oWHERE o.status = 'paid'ORDER BY o.created_at DESC, o.id DESCLIMIT 20;
🔎 Как это происходит на практике:
  • Контекст: лента последних оплаченных заказов.
  • Действия: проектируем индекс под WHERE + ORDER BY + LIMIT, при необходимости добавляем покрытие через INCLUDE.
  • Результат: БД быстрее отдаёт верхние N строк, часто без тяжёлой сортировки и с меньшим числом чтений таблицы.
Характеристики:
  • индексы под JOIN особенно важны на больших таблицах;
  • сортировка выигрывает, если порядок колонок в индексе совпадает с ORDER BY;
  • для API-листингов ORDER BY ... LIMIT часто требует отдельного top-N индекса;
  • покрывающий индекс может дать Index Only Scan, если запрос читает нужные колонки из индекса.
  • один индекс не всегда покрывает все варианты запроса.
Когда использовать: Для запросов с высоким QPS и критичным SLA.
Вывод: Хороший индекс — это индекс под конкретный path чтения, а не под «колонку в вакууме».

🧩 4. Составные индексы и правило левого префикса

Когда запросы регулярно фильтруют по нескольким колонкам, одиночных индексов может быть недостаточно.
В таких случаях нужен составной индекс с правильным порядком колонок.
🟢 Если совсем просто: Порядок колонок в составном индексе критичен.
🎯 Как понять, что этап прошёл успешно: Вы умеете выбирать порядок колонок по частоте фильтров и сортировке.
Назначение: Ускорить многоколоночные фильтры и сортировку одним индексом.
Простыми словами: Индекс (a, b) лучше всего работает, когда запрос использует сначала a, затем b.
Для новичка: Правило левого префикса: (a, b, c) эффективно для (a) и (a, b), но не для (b) без a.
💡 Совет: Если в первой колонке индекса начинается диапазон (a > ..., a BETWEEN ...), польза следующих колонок (b, c) часто становится заметно ниже.
Пример:
CREATE INDEX idx_orders_user_status_createdON orders (user_id, status, created_at DESC);
SELECT  o.id,  o.status,  o.created_atFROM orders oWHERE o.user_id = 42  AND o.status = 'paid'ORDER BY o.created_at DESCLIMIT 20;
SELECT  o.id,  o.statusFROM orders oWHERE o.status = 'paid';
🔎 Как это происходит на практике:
  • Контекст: профиль пользователя показывает его последние оплаченные заказы.
  • Действия: индексируем (user_id, status, created_at).
  • Результат: запрос читает только нужный сегмент, а не всю таблицу заказов.
Характеристики:
  • порядок колонок задаёт полезность индекса;
  • (a, b) обычно не заменяет индекс для частого фильтра только по b;
  • для диапазонов на первой колонке нужно отдельно проверять план и фактический выигрыш;
  • слишком широкие составные индексы могут быть дорогими в поддержке;
  • лучше один точный составной, чем много случайных одиночных.
Когда использовать: Когда фильтры по нескольким колонкам повторяются в ключевых запросах.
Вывод: Составной индекс эффективен только при правильном порядке колонок под реальные запросы.

🔍 5. Почему LIKE '%text%' обычно не использует B-tree

Это одна из самых частых ловушек начинающих: есть индекс, но поиск всё равно медленный.
Причина: шаблон с ведущим % не даёт B-tree использовать упорядоченный старт поиска.
🟢 Если совсем просто: LIKE 'text%' и LIKE '%text%' — это два разных мира по производительности.
🎯 Как понять, что этап прошёл успешно: Вы сразу распознаёте, когда B-tree поможет, а когда нужен другой подход.
Назначение: Понять ограничения B-tree для текстового поиска.
Простыми словами: Если строка может начинаться с чего угодно (%text%), B-tree не знает, откуда стартовать быстро.
Для новичка: Для подстрочного поиска обычно нужны специализированные индексы/поисковые механизмы (например, триграммы/FTS в PostgreSQL).
Пример (префиксный поиск):
SELECT  p.id,  p.nameFROM products pWHERE p.name LIKE 'iph%';
SELECT  p.id,  p.nameFROM products pWHERE p.name LIKE '%phone%';
Пример (contains через триграммы в PostgreSQL):
CREATE EXTENSION IF NOT EXISTS pg_trgm;CREATE INDEX idx_products_name_trgmON products USING gin (name gin_trgm_ops);
🔎 Как это происходит на практике:
  • Контекст: поиск по каталогу.
  • Действия: сравниваем префиксный и подстрочный шаблон.
  • Результат: префиксный поиск обычно быстрее на B-tree, подстрочный требует отдельного инструмента.
Характеристики:
  • префиксный шаблон лучше дружит с B-tree;
  • ведущий wildcard ломает типичную оптимизацию B-tree;
  • для «contains» обычно нужны pg_trgm/GIN или FTS;
  • для сложного поиска по ранжированию и морфологии иногда нужен внешний поисковый движок.
Когда использовать: Применяйте B-tree для префиксных/точных строковых фильтров, а не для общего full-text поведения.
Вывод: LIKE '%text%' — частый индикатор, что нужен не B-tree, а специализированный поиск.

🚫 6. Когда индекс не помогает (и когда мешает)

Индекс не ускоряет всё подряд. Иногда планировщику дешевле полный скан, иногда индекс «портит» запись, а выигрыша в чтении почти нет.
🟢 Если совсем просто: Индекс полезен только там, где он реально снижает стоимость запроса.
🎯 Как понять, что этап прошёл успешно: Вы умеете объяснить, почему БД выбрала Seq Scan даже при наличии индекса.
Назначение: Избежать «индексного шума» и лишних затрат на запись.
Простыми словами: Наличие индекса не означает, что БД обязана его использовать.
Для новичка: Проверяйте план выполнения и не оценивайте индекс «на глаз».
Примеры, когда индекс часто не помогает:
  • таблица очень маленькая (полный проход дешевле);
  • условие с низкой селективностью (is_active = true для 95% строк);
  • функция над колонкой без функционального индекса (WHERE lower(email)=...);
  • LIKE '%text%' для B-tree;
  • запрос возвращает слишком большую долю таблицы;
  • статистика устарела и планировщик ошибается;
  • сильный перекос данных (skew) ломает оценку селективности;
  • параметризованный запрос может получать неудачный универсальный план.
Пример диагностики (estimate vs actual):
EXPLAIN (ANALYZE, BUFFERS)SELECT  o.id,  o.created_atFROM orders oWHERE o.status = 'pending'ORDER BY o.created_at DESCLIMIT 50;
💡 Совет: Если в плане rows сильно расходится с actual rows, сначала обновляйте статистику (ANALYZE/autovacuum), потом принимайте решение по индексам.
Пример (функция над колонкой):
SELECT  u.id,  u.emailFROM users uWHERE lower(u.email) = 'alice@example.com';
Варианты решения:
CREATE INDEX idx_users_emailON users (email);
CREATE INDEX idx_users_email_lowerON users (lower(email));
Пример (partial index, PostgreSQL):
CREATE INDEX idx_orders_pending_createdON orders (created_at DESC)WHERE status = 'pending';
🔎 Как это происходит на практике:
  • Контекст: запрос «вдруг» стал медленным после роста таблицы.
  • Действия: проверяем EXPLAIN (ANALYZE, BUFFERS), расхождение estimate vs actual, состояние статистики и форму фильтра.
  • Результат: либо добавляем точный индекс (обычный/functional/partial), либо удаляем бесполезный.
Характеристики:
  • лишние индексы замедляют INSERT/UPDATE/DELETE;
  • индекс потребляет память и место на диске;
  • индексы со временем могут раздуваться (bloat) и требуют обслуживания;
  • индекс нужно периодически переоценивать под текущий workload.
Когда использовать: Когда есть подтверждённый bottleneck и ясный read-pattern, который индекс реально ускоряет.
⚙️ Для продакшена: Создавайте тяжёлые индексы online-режимом, где СУБД позволяет (в PostgreSQL: CREATE INDEX CONCURRENTLY), чтобы не блокировать запись надолго.
Вывод: Хорошая индексация — это осознанный баланс: ускоряем критичное чтение, не ломая запись.

🆚 Сравнение: B-tree полезен vs B-tree неэффективен

СценарийB-tree обычно помогаетB-tree обычно не помогает
WHEREemail = ..., created_at BETWEEN ...lower(email)=... без functional index
LIKEname LIKE 'abc%'name LIKE '%abc%'
JOINorders.user_id = users.id при индексе на orders.user_idJOIN по неиндексированным/неподходящим полям
ORDER BYПорядок совпадает с индексомПорядок не совпадает или выборка слишком широкая
СтатистикаАктуальная статистика даёт предсказуемый планУстаревшая статистика ведёт к ошибочному Seq Scan
Низкая селективностьPartial index на «горячий сегмент»Индекс по «почти одинаковым» значениям
Вывод: Эффективность индекса определяется формой запроса и селективностью, а не фактом его существования.

🧠 Must-Know (запомнить)

  • Индекс ускоряет чтение, но стоит ресурсов на запись и хранение.
  • Базовый рабочий тип индекса — B-tree.
  • В первую очередь индексируйте поля из WHERE, JOIN, ORDER BY горячих запросов.
  • Для FK-колонок на стороне child индекс чаще всего обязателен.
  • Для составных индексов критичен порядок колонок (leftmost prefix).
  • Для (a, b, c) фильтр только по b обычно не использует этот индекс.
  • Если по первой колонке идёт диапазон (a > ...), эффект следующих колонок часто снижается.
  • LIKE 'abc%' и LIKE '%abc%' требуют разной стратегии.
  • Для %text% обычно нужен pg_trgm/GIN или FTS, а не обычный B-tree.
  • Индекс не гарантирует использование: БД выбирает план по стоимости.
  • Низкая селективность и маленькие таблицы часто ведут к Seq Scan.
  • Устаревшая статистика и skew-данных могут «ломать» выбор плана.
  • Функции над колонкой могут «сломать» обычный индекс (нужен functional index).
  • Для частых условных сегментов полезны partial indexes.
  • Covering-индекс может дать Index Only Scan и ускорить листинги.
  • Индексы проверяют и обосновывают через EXPLAIN (ANALYZE, BUFFERS), а не по интуиции.
  • Слишком много индексов ухудшают INSERT/UPDATE/DELETE.
  • Индексы нужно обслуживать и периодически чистить от «мёртвых».
  • Хорошая индексация всегда привязана к реальным запросам из прод-нагрузки.
Вывод: Индексировать нужно не «колонки», а конкретные бизнес-сценарии чтения.

❌ Частые мифы

Миф: Индекс всегда ускоряет любой запрос. ✅ Как правильно: Индекс помогает только если соответствует шаблону чтения и выгоден планировщику. 📎 Почему это важно: Иначе можно получить больше накладных расходов на запись без ускорения.
Миф: Если добавить индекс, LIKE '%text%' станет быстрым. ✅ Как правильно: Для ведущего % обычный B-tree обычно не подходит. 📎 Почему это важно: Неверный тип поиска приводит к полной проверке больших таблиц.
Миф: Чем больше индексов, тем лучше. ✅ Как правильно: Лишние индексы замедляют запись и усложняют сопровождение. 📎 Почему это важно: Перебор индексов повышает стоимость транзакций записи.
Миф: Два одиночных индекса всегда заменяют один составной. ✅ Как правильно: Для частого фильтра по нескольким колонкам обычно нужен составной индекс с правильным порядком. 📎 Почему это важно: Неверная структура индексов ведёт к нестабильной производительности.
Миф: Если индекс создан, планировщик обязан его использовать. ✅ Как правильно: БД выбирает путь по оценке стоимости, статистике и селективности. 📎 Почему это важно: Без проверки EXPLAIN (ANALYZE, BUFFERS) легко принять ложное решение.

🎤 Часто спрашивают на собеседованиях

Вопрос: Зачем вообще нужен индекс в SQL? ✅ Ответ: Чтобы сократить объём читаемых строк и ускорить критичные сценарии чтения.
Вопрос: Что такое B-tree индекс? ✅ Ответ: Это базовый упорядоченный индекс, эффективный для точного поиска, диапазонов и части сортировок.
Вопрос: Какие поля индексируют в первую очередь? ✅ Ответ: Поля из горячих WHERE, JOIN, ORDER BY, особенно FK на стороне дочерней таблицы.
Вопрос: Почему LIKE '%text%' обычно не использует B-tree? ✅ Ответ: Потому что при ведущем % индекс не может быстро определить стартовый диапазон поиска.
Вопрос: Что важно в составных индексах? ✅ Ответ: Порядок колонок и правило левого префикса: индекс должен соответствовать реальному порядку фильтрации.
Вопрос: На что смотреть в плане кроме времени выполнения? ✅ Ответ: На выбранные узлы (Seq Scan/Index Scan), расхождение estimate rows vs actual rows и метрики чтения в BUFFERS.
Вопрос: Как понять, что индекс реально помогает? ✅ Ответ: Проверить EXPLAIN (ANALYZE, BUFFERS) и сравнить план, фактическое время, чтения и сортировки до/после.
Вопрос: Почему индекс может не использоваться, даже если он есть? ✅ Ответ: Из-за низкой селективности, устаревшей статистики, перекоса данных, функций над колонкой или неподходящей формы фильтра.
Вопрос: Что такое partial index и зачем он нужен? ✅ Ответ: Это индекс на подмножество строк (WHERE ... в CREATE INDEX), который ускоряет «горячий сегмент» и стоит дешевле полного индекса.
Вопрос: Почему нельзя «индексировать всё»? ✅ Ответ: Потому что каждый индекс увеличивает стоимость записи и расход ресурсов.

🚨 Типичные ошибки

  • Добавлять индексы без анализа реальных SQL-запросов.
  • Индексировать колонку с почти одинаковыми значениями и ждать сильного ускорения.
  • Не индексировать FK-поля в крупных дочерних таблицах.
  • Писать LIKE '%text%' и ожидать ускорения от обычного B-tree.
  • Игнорировать порядок колонок в составном индексе.
  • Ожидать, что индекс (a, b) ускорит частый фильтр только по b.
  • Считать, что функция в WHERE автоматически использует обычный индекс.
  • Оценивать индексацию без EXPLAIN (ANALYZE, BUFFERS).
  • Не проверять расхождение estimate rows и actual rows.
  • Не обновлять статистику после крупных изменений данных.
  • Держать много старых «мертвых» индексов после изменений функционала.
Вывод: Большинство проблем с индексами возникают не из-за SQL-синтаксиса, а из-за отсутствия стратегии под workload.

✅ Best Practices

  • Начинайте с карты горячих запросов (WHERE/JOIN/ORDER BY) из прод-метрик.
  • Индексируйте сначала самые частые и дорогие запросы, а не «всю схему».
  • Для JOIN по FK почти всегда держите индекс на child-стороне.
  • Проектируйте составные индексы под реальный порядок фильтров и сортировки.
  • Для листингов ORDER BY ... LIMIT делайте отдельные top-N индексы с tie-breaker (id).
  • Для «горячего меньшинства» используйте partial indexes.
  • Для функций в фильтре используйте функциональные индексы, если паттерн устойчивый.
  • Если возможно, храните нормализованные данные (например, email в lower/trim) и индексируйте «чистое» поле.
  • Проверяйте эффект индекса через EXPLAIN (ANALYZE, BUFFERS).
  • Смотрите не только время, но и оценку/факт строк (estimate vs actual).
  • В проде создавайте тяжёлые индексы онлайн-способом (CREATE INDEX CONCURRENTLY в PostgreSQL).
  • Регулярно пересматривайте индексы при изменении бизнес-сценариев.
  • Удаляйте индексы, которые не используются и только замедляют запись.
Вывод: Production-ready индексация — это непрерывная инженерная практика, а не разовая настройка.

📋 Чек-лист: как проектировать индекс под запрос

  1. Зафиксируйте реальный запрос из прод-сценария (WHERE/JOIN/ORDER BY/LIMIT).
  2. Запустите EXPLAIN (ANALYZE, BUFFERS) и определите узкое место: полный скан, сортировка, дорогой JOIN, большой I/O.
  3. Спроектируйте индекс: равенства — первыми, затем сортировка, затем покрытие (INCLUDE) при необходимости.
  4. Проверьте, не нужен ли special-case индекс: partial (по сегменту) или functional (по выражению).
  5. Повторно запустите EXPLAIN (ANALYZE, BUFFERS) и сравните план/время/чтения до и после.
  6. Оцените цену записи: не добавили ли вы индекс, который ускоряет редкий запрос, но тормозит массовый INSERT/UPDATE/DELETE.
Вывод: Индекс проектируют как инженерный цикл «гипотеза -> измерение -> корректировка», а не как одноразовый DDL.

🏁 Заключение

Индексы — основа производительности чтения в реляционных БД.
Если вы понимаете B-tree, умеете проектировать индексы под WHERE/JOIN/ORDER BY, учитываете составные ключи и ограничения LIKE '%text%', вы уже принимаете архитектурные решения уровня production.
Вывод: Скорость SQL в больших таблицах определяется не только запросом, но и качеством индексации под реальные сценарии.
🎯

Проверьте знания

Закрепите материал — пройдите тест по теме «БЛОК 8. Производительность — 21. Индексы»

Пройти тест →