> Какая команда помогает понять, что происходит при запросе к базе данных для поиска узких мест по производительности (Go)

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

Компании: Wildberries

Стек: Go

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

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

Основная команда - EXPLAIN ANALYZE (в PostgreSQL) или её аналоги в других СУБД (EXPLAIN QUERY PLAN в SQLite, EXPLAIN с фактическими планами в MySQL). Она показывает план выполнения запроса и фактическое время каждого узла. Для Go-разработчика это первый шаг перед профилированием на уровне приложения: сначала убеждаемся, что запрос оптимален, затем смотрим на N+1, индексы и блокировки.

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

EXPLAIN ANALYZE выполняет запрос и возвращает:

  • план выполнения (последовательность операций: seq scan, index scan, hash join и т.д.);
  • фактическое время каждого шага (actual time);
  • количество строк на каждом шаге (rows);
  • количество буферов, прочитанных из кэша/диска (buffers).

Это позволяет найти узкие места:

  • Seq Scan на большой таблице - нужен индекс;
  • большое расхождение между estimated rows и actual rows - устаревшая статистика;
  • высокое время на конкретном узле - проблема в сортировке, join или фильтрации.

Для Go-приложений важно также:

  • включить логирование медленных запросов (slow query log);
  • использовать pg_stat_statements для агрегированной статистики по запросам;
  • проверять, не выполняется ли запрос в цикле (N+1) - это видно по количеству вызовов из кода, а не по плану.

На практике

  1. Выполняем EXPLAIN ANALYZE в psql или через database/sql в Go (только для чтения, не в production с записью).
  2. Смотрим на самый дорогой узел плана.
  3. Проверяем, используется ли индекс (Index Scan vs Seq Scan).
  4. Если индекс не используется - проверяем типы данных в WHERE (например, varchar vs int), наличие функций над колонками.
  5. Для Go: добавляем middleware для логирования запросов с длительностью, затем ищем запросы с аномальным временем.

Пример кода

GO
// Логирование запросов в Go через database/sql
db, err := sql.Open("postgres", dsn)
if err != nil {
log.Fatal(err)
}
// Обёртка для логирования медленных запросов
type queryLogger struct {
db *sql.DB
}
func (q *queryLogger) QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error) {
start := time.Now()
rows, err := q.db.QueryContext(ctx, query, args...)
elapsed := time.Since(start)
if elapsed > 100*time.Millisecond {
log.Printf("slow query: %s, elapsed: %s", query, elapsed)
}
return rows, err
}

Как отвечать на собеседовании

Начните с EXPLAIN ANALYZE, затем поясните, что смотрите в плане: тип сканирования, фактическое время, количество строк. Упомяните, что для Go-приложений важно отличать проблему на уровне БД от проблемы на уровне приложения (например, N+1 запросов). Приведите пример: если видите Seq Scan на таблице с миллионами строк - это сигнал к добавлению индекса. Добавьте, что в production используете pg_stat_statements и slow query log.

Что проверяет интервьюер

  • Понимание разницы между EXPLAIN и EXPLAIN ANALYZE.
  • Умение читать план выполнения и находить узкие места.
  • Знание типичных проблем: Seq Scan, отсутствие индексов, устаревшая статистика.
  • Понимание, как профилировать запросы из Go-приложения, а не только в psql.
  • Способность отличать проблемы БД от проблем кода (N+1, отсутствие connection pooling).

Типичные ошибки

  • Называть только EXPLAIN без ANALYZE - план без фактических данных не показывает реальное время.
  • Смотреть только на время, игнорируя количество строк и тип сканирования.
  • Предлагать добавить индекс без проверки, используется ли он в плане.
  • Забывать, что EXPLAIN ANALYZE выполняет запрос - в production с тяжёлыми запросами это опасно.
  • Не учитывать, что в Go проблема может быть в коде (например, запрос в цикле), а не в самой СУБД.

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

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