> Что такое селективность индекса и почему не всегда индекс используется? (Go)
Уровень: junior · Роль: backend · Категория: Технические вопросы
Компании: Сбер, Employcity
Стек: Go
> Пример ответа
Короткий ответ
Селективность индекса - это доля строк таблицы, которые отбирает запрос при использовании индекса. Высокая селективность означает, что индекс отбирает мало строк (например, 1-5%), низкая - много (например, 50% и более). Индекс не используется, когда оптимизатор считает, что чтение таблицы целиком (full scan) дешевле, чем чтение индекса плюс обращения к таблице. Это происходит при низкой селективности, малом размере таблицы или когда запрос возвращает большую часть строк.
Подробное объяснение
Селективность вычисляется как отношение количества уникальных значений в колонке к общему количеству строк. Например, колонка id с уникальными значениями имеет селективность 1.0 (максимальная), а колонка is_active с двумя значениями - около 0.5 (низкая).
Оптимизатор оценивает стоимость плана выполнения:
- при использовании индекса - стоимость чтения индексных страниц + стоимость random access к таблице (для каждого найденного rowid);
- при full scan - стоимость последовательного чтения всех страниц таблицы.
Если индекс отбирает 30% строк, random access к 30% страниц таблицы часто дороже, чем последовательное чтение всех страниц. Поэтому оптимизатор выбирает full scan.
Также индекс не используется, если:
- колонка участвует в выражении или функции (например,
WHERE year(created_at) = 2023); - в запросе используется
LIKE '%text%'(префиксный wildcard); - оптимизатор считает таблицу слишком маленькой (меньше нескольких страниц);
- индекс не покрывает запрос, и требуется много обращений к таблице.
В PostgreSQL и MySQL оптимизатор принимает решение на основе статистики (гистограммы, количество страниц, плотность данных). Если статистика устарела, решение может быть неоптимальным.
На практике
Для backend-разработчика на Go важно понимать, что селективность - это не свойство индекса, а свойство пары "индекс + конкретный запрос". Один и тот же индекс может быть высокоселективным для одного запроса и низкоселективным для другого.
Практические следствия:
- не создавать индексы на колонках с малым количеством уникальных значений (булевы, enum с 2-3 значениями);
- для составных индексов ставить первой колонку с наибольшей селективностью;
- использовать
EXPLAINдля проверки, использует ли запрос индекс; - в Go-приложениях следить за тем, чтобы параметры запросов не приводили к неожиданным full scan (например, передача пустого фильтра).
Пример: запрос WHERE status = 'active' при 90% активных записей не будет использовать индекс по status - это нормально. А вот WHERE user_id = ? при миллионе пользователей - будет.
Пример кода
SQL-- таблица с низкой селективностью колонки statusCREATE TABLE orders (id BIGSERIAL PRIMARY KEY,status TEXT NOT NULL,user_id BIGINT NOT NULL);-- индекс на status - низкая селективность, часто не используетсяCREATE INDEX idx_orders_status ON orders(status);-- индекс на user_id - высокая селективность, используетсяCREATE INDEX idx_orders_user ON orders(user_id);-- запрос, который не будет использовать idx_orders_statusEXPLAIN SELECT * FROM orders WHERE status = 'completed';-- вероятно, Seq Scan, если completed составляет большую часть строк-- запрос, который будет использовать idx_orders_userEXPLAIN SELECT * FROM orders WHERE user_id = 12345;-- Index Scan, так как user_id уникален для большинства записей
В Go-коде это проявляется так:
GO// плохо: фильтр по низкоселективной колонкеrows, _ := db.Query("SELECT * FROM orders WHERE status = $1", "completed")// хорошо: фильтр по высокоселективной колонкеrows, _ := db.Query("SELECT * FROM orders WHERE user_id = $1 AND status = $2", userID, "completed")
Как отвечать на собеседовании
Начни с определения селективности как доли строк, отбираемых запросом. Затем объясни, что оптимизатор сравнивает стоимость index scan и full scan. Приведи пример: колонка с двумя значениями - индекс бесполезен, колонка с уникальными значениями - полезен. Упомяни, что решение зависит от статистики и размера таблицы. Если спросят про Go - скажи, что разработчик должен проверять планы запросов через EXPLAIN и не полагаться на индексы, которые "вроде бы должны работать". Заверши практическим советом: для составных индексов первая колонка - самая селективная.
Что проверяет интервьюер
- понимание разницы между селективностью индекса и селективностью колонки;
- знание факторов, влияющих на выбор плана (стоимость random access, размер таблицы, статистика);
- умение объяснить, почему
WHERE status = 'active'при 90% активных строк не использует индекс; - понимание роли
EXPLAINв диагностике; - способность связать теорию с практикой backend-разработки (Go, SQL).
Типичные ошибки
- утверждение, что индекс не используется "потому что он плохой" - индекс не бывает плохим, он бывает неподходящим для запроса;
- игнорирование стоимости random access - даже при селективности 10% index scan может быть дороже full scan;
- забывание про покрывающие индексы (covering index) - если индекс покрывает запрос, он может использоваться даже при низкой селективности;
- утверждение, что индекс всегда ускоряет запрос - для маленьких таблиц full scan быстрее;
- отсутствие упоминания статистики и устаревших
ANALYZEданных.
> Похожие задачи по backend
Как происходит обмен сертификатами в HTTPS
Что происходит с внешним сервисом при повторных обращениях во время сбоя
Как реализовать паттерн fan-out fan-in в Go с использованием горутин?
Что происходит в программе при отсутствии доступной памяти
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью