> Что делать, если после добавления индекса селективность низкая и запрос остается медленным? (Go)
Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы
Компании: InDrive
Стек: Go
> Пример ответа
Короткий ответ
Низкая селективность индекса означает, что индекс не сужает выборку достаточно сильно - например, при селективности 50% оптимизатор скорее выберет full scan. Нужно проверить статистику, состав индекса, типы данных и план запроса. Часто помогает покрывающий индекс, переписывание запроса, изменение порядка колонок в составном индексе или использование partial index. Если индекс действительно бесполезен - его нужно удалить, чтобы не тратить ресурсы на запись и память.
Подробное объяснение
Селективность - это отношение количества уникальных значений колонки к общему числу строк. Низкая селективность (например, 0.1-0.5) означает, что индекс вернет большой процент таблицы, и оптимизатор может отказаться от него в пользу sequential scan - это дешевле по числу page reads.
Причины низкой селективности:
- колонка с малым числом уникальных значений (boolean, enum с 2-3 значениями)
- составной индекс, где первая колонка низкоселективна, а высокоселективная стоит позже
- устаревшая статистика - планировщик не знает реальное распределение данных
- несоответствие типов: индекс по varchar, а в запросе передается число - индекс не используется
- функциональные выражения:
WHERE lower(name) = ...без functional index
Что делать:
- Проверить план через
EXPLAIN ANALYZE- убедиться, что индекс вообще используется, и посмотреть оценку rows. - Обновить статистику (
ANALYZEв PostgreSQL,UPDATE STATISTICSв SQL Server). - Перестроить индекс - иногда он фрагментирован.
- Изменить порядок колонок в составном индексе - высокоселективные колонки должны идти первыми.
- Добавить покрывающий индекс (INCLUDE / covering index) - чтобы не было table lookup.
- Использовать partial index для часто фильтруемого значения.
- Переписать запрос: добавить дополнительные условия, изменить JOIN order, использовать EXISTS вместо IN.
- Рассмотреть другие структуры: BRIN для больших таблиц с коррелированным распределением, hash index для равенства.
Важно понимать: индекс - не панацея. Если запрос фильтрует 30% таблицы, full scan может быть быстрее. Иногда правильное решение - не индекс, а изменение схемы (денормализация, партиционирование) или кэширование.
На практике
Типичный сценарий: есть таблица orders с колонкой status (значения: new, paid, shipped, cancelled). Запрос SELECT * FROM orders WHERE status = 'new' - селективность низкая, если 40% заказов в статусе new. Индекс по status бесполезен.
Что делаем:
- если запрос всегда фильтрует по
status = 'new'- создаем partial index:CREATE INDEX idx_orders_new ON orders(id) WHERE status = 'new' - если запросы разные - добавляем в индекс другие колонки:
CREATE INDEX idx_orders_status_created ON orders(status, created_at)- тогда дляWHERE status = 'new' ORDER BY created_atиндекс полезен - если нужен только
id- делаем covering index:CREATE INDEX idx_orders_status_covering ON orders(status) INCLUDE (id, total)
Еще пример: составной индекс (city, status) - если запросы всегда фильтруют по status, а city редко, индекс бесполезен. Меняем порядок на (status, city).
Для Go-разработчика важно: не пытаться решить проблему на уровне приложения (например, добавить time.Sleep или искусственное ограничение), а работать с БД. Также стоит проверить, не создает ли ORM (GORM, sqlx) запросы с неявными приведениями типов.
Пример кода
SQL-- Плохой индекс: низкая селективностьCREATE INDEX idx_orders_status ON orders(status);-- Partial index для конкретного значенияCREATE INDEX idx_orders_new ON orders(id, created_at) WHERE status = 'new';-- Покрывающий индексCREATE INDEX idx_orders_status_covering ON orders(status) INCLUDE (total, created_at);-- Составной индекс с правильным порядкомCREATE INDEX idx_orders_status_created ON orders(status, created_at);-- Проверка планаEXPLAIN ANALYZE SELECT id, total FROM orders WHERE status = 'new' AND created_at > now() - interval '1 day';
GO// В Go: не подменяем проблему БД кодом// Плохо: вытащить все и фильтровать в памятиrows, _ := db.Query("SELECT * FROM orders WHERE status = 'new'")// фильтруем created_at в Go// Хорошо: пусть БД делает свою работуrows, _ := db.Query(`SELECT id, total FROM ordersWHERE status = 'new' AND created_at > now() - interval '1 day'`)
Как отвечать на собеседовании
Начни с определения селективности и почему она важна. Затем перечисли шаги диагностики: EXPLAIN, статистика, структура индекса. Обязательно упомяни, что низкая селективность - не всегда проблема: иногда full scan быстрее. Покажи понимание trade-off между скоростью чтения и стоимостью поддержки индекса. Приведи конкретный пример с partial index или covering index. В конце скажи, что решение зависит от паттерна запросов и распределения данных - нет универсального ответа.
Что проверяет интервьюер
- понимание того, как оптимизатор принимает решение об использовании индекса
- знание внутреннего устройства индексов (B-tree, page reads, cost model)
- умение читать план запроса и видеть проблему
- способность предложить несколько вариантов решения с обоснованием
- понимание trade-off: индекс ускоряет SELECT, но замедляет INSERT/UPDATE
- знание специфики конкретной СУБД (PostgreSQL, MySQL, etc.)
Типичные ошибки
- предложение "просто добавить индекс" без анализа селективности
- игнорирование статистики - сразу пересоздание индекса
- непонимание разницы между селективностью колонки и селективностью индекса
- утверждение, что индекс всегда ускоряет запрос
- забывание про covering index, когда нужен только id
- предложение использовать индекс для колонки с 2-3 уникальными значениями без дополнительных условий
- отсутствие проверки через EXPLAIN после изменений
> Похожие задачи по Go
Что такое пагинация?
Какие стратегии пагинации можно использовать вместо LIMIT OFFSET?
Приходилось ли менять уровни изоляции транзакций?
Для чего используется SELECT FOR UPDATE?
> Похожие задачи по backend
Что такое пагинация?
Какие стратегии пагинации можно использовать вместо LIMIT OFFSET?
Приходилось ли менять уровни изоляции транзакций?
Для чего используется SELECT FOR UPDATE?
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью