21. Индексы
🧭 Введение: почему «правильный индекс» часто важнее сложного SQL
На реальных проектах медленный запрос редко означает «плохой разработчик».
Чаще причина проще: SQL корректный, но у БД нет подходящего пути быстро найти нужные строки.
Чаще причина проще: SQL корректный, но у БД нет подходящего пути быстро найти нужные строки.
В этой теме разберём базовый production-фундамент по индексам:
- зачем индекс нужен и какой риск он закрывает;
- как работает
B-tree(базово); - как индекс помогает в
WHERE,JOIN,ORDER BY; - как проектировать составные индексы;
- почему
LIKE '%text%'обычно не использует B-tree; - в каких случаях индекс не помогает и даже вредит.
💡 Совет:
Индекс проектируют под конкретные запросы, а не «на всякий случай».
✅ Вывод:
Индексы — это инструмент управления скоростью чтения, а не магическая кнопка ускорения всего подряд.
⚠️ Проблема -> решение
Типичная проблема:
- таблица растёт, а запросы становятся всё медленнее;
- в приложении пытаются «кешировать всё», не разобрав SQL-паттерн;
- индексы добавляют хаотично и получают лишнюю нагрузку на
INSERT/UPDATE/DELETE.
Решение:
- фиксировать ключевые сценарии чтения (
WHERE/JOIN/ORDER BY); - добавлять целевые индексы под эти сценарии;
- проверять план выполнения (
EXPLAIN) и держать баланс чтений/записей.
🟢 Если совсем просто:
Нет нужного индекса -> БД читает лишние строки -> запрос медленный.
🎯 Как понять, что этап прошёл успешно:
Запросы по горячим сценариям выполняются быстрее и предсказуемее без регресса записи.
🛠️ Чем помогает и как работает
Индексы нужны там, где объём данных уже не позволяет «быстро пройтись по всей таблице».
Они особенно важны для API-листингов, фильтров по пользователю, джойнов транзакционных таблиц и сортировки свежих записей.
Они особенно важны для 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-системах.
Это ключевой инструмент масштабирования чтений в 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 обычно не помогает |
|---|---|---|
WHERE | email = ..., created_at BETWEEN ... | lower(email)=... без functional index |
LIKE | name LIKE 'abc%' | name LIKE '%abc%' |
JOIN | orders.user_id = users.id при индексе на orders.user_id | JOIN по неиндексированным/неподходящим полям |
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 индексация — это непрерывная инженерная практика, а не разовая настройка.
📋 Чек-лист: как проектировать индекс под запрос
- Зафиксируйте реальный запрос из прод-сценария (
WHERE/JOIN/ORDER BY/LIMIT). - Запустите
EXPLAIN (ANALYZE, BUFFERS)и определите узкое место: полный скан, сортировка, дорогойJOIN, большой I/O. - Спроектируйте индекс: равенства — первыми, затем сортировка, затем покрытие (
INCLUDE) при необходимости. - Проверьте, не нужен ли special-case индекс: partial (по сегменту) или functional (по выражению).
- Повторно запустите
EXPLAIN (ANALYZE, BUFFERS)и сравните план/время/чтения до и после. - Оцените цену записи: не добавили ли вы индекс, который ускоряет редкий запрос, но тормозит массовый
INSERT/UPDATE/DELETE.
✅ Вывод:
Индекс проектируют как инженерный цикл «гипотеза -> измерение -> корректировка», а не как одноразовый DDL.
🏁 Заключение
Индексы — основа производительности чтения в реляционных БД.
Если вы понимаете B-tree, умеете проектировать индексы под
Если вы понимаете B-tree, умеете проектировать индексы под
WHERE/JOIN/ORDER BY, учитываете составные ключи и ограничения LIKE '%text%', вы уже принимаете архитектурные решения уровня production.✅ Вывод:
Скорость SQL в больших таблицах определяется не только запросом, но и качеством индексации под реальные сценарии.