> Какой индекс построить для запроса с условиями на несколько столбцов (Go)
Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы
Компании: Ozon
Стек: Go
> Пример ответа
Короткий ответ
Для запроса с условиями на несколько столбцов оптимальный вариант - композитный (составной) B-tree индекс, где порядок столбцов определяется селективностью и паттерном запросов. Сначала ставятся столбцы с условиями равенства (=), затем - с диапазонными условиями (>, <, BETWEEN). Если все условия равенства - порядок менее критичен, но важно учитывать покрытие запроса индексом (covering index) для избежания table lookup.
Подробное объяснение
Композитный индекс работает как упорядоченная структура по всем включённым столбцам. Ключевое правило - левосторонний префикс: индекс эффективен, если условия в запросе покрывают столбцы слева направо без пропусков. Например, индекс (a, b, c) эффективен для WHERE a = ? AND b = ?, но не для WHERE b = ? AND c = ?.
Порядок столбцов определяется:
- Селективность - более селективные столбцы (с большим количеством уникальных значений) ставятся первыми при условиях равенства, чтобы быстрее сузить диапазон сканирования.
- Тип условия - столбцы с
=всегда идут раньше столбцов с диапазоном, потому что после первого диапазонного условия индекс перестаёт быть эффективным для последующих столбцов. - Сортировка - если запрос содержит
ORDER BY, индекс может покрыть сортировку, если порядок столбцов в индексе совпадает с порядком сортировки.
Для WHERE a = ? AND b BETWEEN ? AND ? индекс (a, b) идеален: сначала точное совпадение по a, затем диапазон по b - это один последовательный скан листьев.
Если запрос выбирает только столбцы, входящие в индекс, можно использовать covering index - тогда не потребуется обращение к таблице (index-only scan). Это особенно важно в Go-приложениях с высоким RPS, где каждый лишний disk read критичен.
Для OR-условий композитный индекс обычно не помогает - лучше использовать UNION ALL или отдельные индексы (в PostgreSQL - bitmap OR).
На практике
В PostgreSQL и MySQL синтаксис одинаков: CREATE INDEX idx_name ON table (col1, col2). В Go-приложениях миграции обычно выполняются через golang-migrate или goose.
Практический подход:
- Сначала соберите реальные запросы из продакшена (через pg_stat_statements или slow query log).
- Для каждого запроса определите паттерн условий: равенство, диапазон, сортировка.
- Сгруппируйте запросы по общим префиксам столбцов - один индекс может обслуживать несколько запросов.
- Проверяйте план через
EXPLAIN ANALYZE: ищитеIndex ScanвместоSeq Scan, обращайте внимание наRows Removed by Index Recheck.
Для Go-сервисов с типичным CRUD: если есть таблица orders с фильтрацией по user_id и created_at, индекс (user_id, created_at) покроет запросы "все заказы пользователя за период" и "последние заказы пользователя".
Пример кода
SQL-- Таблица заказовCREATE TABLE orders (id BIGSERIAL PRIMARY KEY,user_id BIGINT NOT NULL,status TEXT NOT NULL,created_at TIMESTAMPTZ NOT NULL DEFAULT now());-- Запрос: все заказы пользователя по статусу за период-- WHERE user_id = ? AND status = ? AND created_at BETWEEN ? AND ?CREATE INDEX idx_orders_user_status_createdON orders (user_id, status, created_at);-- Запрос: последние заказы пользователя (сортировка)-- WHERE user_id = ? ORDER BY created_at DESC LIMIT 10-- Этот же индекс работает, так как created_at - последний столбец
В Go-коде запрос остаётся стандартным:
GOrows, err := db.Query(ctx, `SELECT id, status, created_atFROM ordersWHERE user_id = $1 AND status = $2 AND created_at BETWEEN $3 AND $4ORDER BY created_at DESC`, userID, status, from, to)
Как отвечать на собеседовании
Начните с главного: композитный B-tree индекс, порядок столбцов - сначала равенство, потом диапазон. Затем объясните левосторонний префикс и почему нельзя пропускать столбцы. Приведите пример из реальной практики: таблица заказов, индекс (user_id, created_at). Упомяните covering index как оптимизацию для частых запросов. Если спросят про OR - честно скажите, что композитный индекс не поможет, нужен bitmap OR или UNION. Завершите упоминанием EXPLAIN ANALYZE для проверки.
Что проверяет интервьюер
- Понимание внутренней структуры B-tree и принципа левостороннего префикса.
- Умение определять порядок столбцов на основе типов условий (равенство vs диапазон).
- Знание trade-off: лишние индексы замедляют запись, поэтому один композитный индекс лучше нескольких одиночных.
- Практический опыт: умение читать план запроса и видеть, использует ли СУБД индекс.
- Понимание covering index и index-only scan.
Типичные ошибки
- Ставить столбцы в порядке, указанном в
WHERE, без учёта селективности и типа условия. - Создавать отдельные индексы на каждый столбец - СУБД сможет использовать только один из них для одного запроса (кроме bitmap-сканов).
- Игнорировать
ORDER BY- если индекс не покрывает сортировку, будет дополнительная сортировка. - Забывать про стоимость поддержки индекса при
INSERT/UPDATE- каждый индекс замедляет запись. - Считать, что индекс с диапазоном по первому столбцу эффективен для последующих - это не так, после первого диапазона остальные столбцы бесполезны.
> Похожие задачи по Go
Как модифицировать архитектуру микросервиса, чтобы обеспечить надежность обработки заказов при падении аналитического сервиса
Как написать запрос, который выберет названия всех книг с более чем тремя авторами
Как понять, что код отработал правильно
Как программа определяет URL для выполнения запросов по списку
> Похожие задачи по backend
Как модифицировать архитектуру микросервиса, чтобы обеспечить надежность обработки заказов при падении аналитического сервиса
Как написать запрос, который выберет названия всех книг с более чем тремя авторами
Как понять, что код отработал правильно
Как программа определяет URL для выполнения запросов по списку
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью