> Почему запрос с индексами может тормозить и как это решать (Go)
Уровень: senior · Роль: backend · Категория: Технические вопросы
Компании: sferaplatform.ru
Стек: Go
> Пример ответа
Короткий ответ
Индекс ускоряет поиск, но не гарантирует скорость запроса. Тормоза возникают из-за неселективных индексов, covering index отсутствия, сортировок, range-сканов, а также из-за устаревшей статистики или неверного плана. Решение - анализ через EXPLAIN, проверка селективности, добавление составных индексов, избегание функций в WHERE и пересмотр структуры запроса.
Подробное объяснение
Индекс - это структура данных (B-tree, hash), которая позволяет найти строки без полного сканирования таблицы. Но запрос с индексом может быть медленным по нескольким причинам:
-
Низкая селективность: если индекс покрывает столбец, где большинство значений одинаковы (например,
is_deleted = false), оптимизатор может решить, что дешевле просканировать таблицу целиком, чем читать индекс и затем делать random access к таблице. -
Несовпадение порядка столбцов в составном индексе: индекс
(a, b, c)не поможет для запроса с условием только поbилиc. -
SARGability: использование функций или вычислений над столбцом в WHERE (
WHERE DATE(created_at) = '2024-01-01') делает индекс бесполезным - оптимизатор не может применить range scan. -
Covering index отсутствует: если запрос выбирает столбцы, не входящие в индекс, приходится делать lookup в таблицу (heap fetch). При большом количестве строк это становится дорого.
-
Сортировка и группировка: если ORDER BY или GROUP BY не покрыты индексом, база делает filesort или temp table.
-
Устаревшая статистика: оптимизатор строит план на основе статистики. Если она не обновлялась, план может быть неоптимальным.
-
Random I/O: при большом количестве rowid из индекса, каждый fetch - это отдельное чтение страницы. Это может быть медленнее, чем последовательное сканирование.
На практике
Для Go-разработчика важно понимать, что проблема часто не в коде, а в SQL. Типичный сценарий: запрос с WHERE user_id = ? AND status = 'active' - есть индекс по user_id, но нет составного. База находит все строки по user_id, затем фильтрует по status в памяти. При большом количестве строк - тормоза.
Решение:
- Смотреть
EXPLAIN ANALYZE(в PostgreSQL) илиEXPLAIN QUERY PLAN(в SQLite). - Проверять
rowsиfiltered- еслиrowsбольшое, аfilteredмаленькое, индекс неселективный. - Добавлять составные индексы под конкретные запросы.
- Использовать
INCLUDE(PostgreSQL) для covering index. - Переписывать запросы, чтобы они были SARGable.
Пример кода
SQL-- Плохо: функция на столбцеSELECT * FROM orders WHERE DATE(created_at) = '2024-01-01';-- Хорошо: range scanSELECT * FROM orders WHERE created_at >= '2024-01-01' AND created_at < '2024-01-02';-- Плохо: индекс только по user_idCREATE INDEX idx_orders_user ON orders(user_id);-- Хорошо: составной индексCREATE INDEX idx_orders_user_status ON orders(user_id, status);-- Covering index (PostgreSQL)CREATE INDEX idx_orders_user_status_cover ON orders(user_id, status) INCLUDE (amount);
В Go это выглядит как:
GO// Плохо: N+1 запросовfor _, userID := range userIDs {rows, _ := db.Query("SELECT * FROM orders WHERE user_id = ?", userID)// обработка}// Хорошо: один запрос с INrows, _ := db.Query("SELECT * FROM orders WHERE user_id IN (?, ?, ?)", userIDs[0], userIDs[1], userIDs[2])
Как отвечать на собеседовании
Начни с короткого определения, затем перечисли основные причины. Обязательно упомяни EXPLAIN как первый инструмент диагностики. Приведи конкретный пример из практики: как ты нашел проблему и что сделал. Покажи понимание trade-off: индекс ускоряет чтение, но замедляет запись и занимает место. Если спросят про Go - скажи, что важно не делать N+1 запросов и использовать batch-операции.
Что проверяет интервьюер
- Понимание внутреннего устройства индексов (B-tree, heap, rowid).
- Умение читать план запроса.
- Знание SARGability и селективности.
- Практический опыт оптимизации.
- Понимание trade-off между скоростью чтения и записи.
Типичные ошибки
- Ответ "индекс - это всегда быстро" - неверно.
- Игнорирование
EXPLAIN- сразу предлагать "добавить индекс" без анализа. - Предложение индекса на каждый столбец - это не работает, нужны составные.
- Не учитывать, что
LIKE '%text%'не использует обычный B-tree индекс. - Забывать про устаревшую статистику -
ANALYZEпосле массовых изменений.
> Похожие задачи по backend
Как устроена пагинация в Go проектах
Как бороться с большими объемами данных в запросах, например шардирование
Почему Pulsar не может достичь такого же уровня масштабируемости как Kafka
Почему Kafka такая быстрая
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью