> Почему запрос с индексами может тормозить и как это решать (Go)

Уровень: senior · Роль: backend · Категория: Технические вопросы

Компании: sferaplatform.ru

Стек: Go

> Пример ответа

Короткий ответ

Индекс ускоряет поиск, но не гарантирует скорость запроса. Тормоза возникают из-за неселективных индексов, covering index отсутствия, сортировок, range-сканов, а также из-за устаревшей статистики или неверного плана. Решение - анализ через EXPLAIN, проверка селективности, добавление составных индексов, избегание функций в WHERE и пересмотр структуры запроса.

Подробное объяснение

Индекс - это структура данных (B-tree, hash), которая позволяет найти строки без полного сканирования таблицы. Но запрос с индексом может быть медленным по нескольким причинам:

  1. Низкая селективность: если индекс покрывает столбец, где большинство значений одинаковы (например, is_deleted = false), оптимизатор может решить, что дешевле просканировать таблицу целиком, чем читать индекс и затем делать random access к таблице.

  2. Несовпадение порядка столбцов в составном индексе: индекс (a, b, c) не поможет для запроса с условием только по b или c.

  3. SARGability: использование функций или вычислений над столбцом в WHERE (WHERE DATE(created_at) = '2024-01-01') делает индекс бесполезным - оптимизатор не может применить range scan.

  4. Covering index отсутствует: если запрос выбирает столбцы, не входящие в индекс, приходится делать lookup в таблицу (heap fetch). При большом количестве строк это становится дорого.

  5. Сортировка и группировка: если ORDER BY или GROUP BY не покрыты индексом, база делает filesort или temp table.

  6. Устаревшая статистика: оптимизатор строит план на основе статистики. Если она не обновлялась, план может быть неоптимальным.

  7. 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 scan
SELECT * FROM orders WHERE created_at >= '2024-01-01' AND created_at < '2024-01-02';
-- Плохо: индекс только по user_id
CREATE 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)
// обработка
}
// Хорошо: один запрос с IN
rows, _ := 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 после массовых изменений.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью