> Зачем добавлять контекст в запросы к базе данных? (Go)
Уровень: middle · Роль: backend · Язык: Go · Категория: Технические вопросы
Компании: InDrive
Стек: Go
> Пример ответа
Короткий ответ
Контекст в запросах к базе данных нужен для управления жизненным циклом операции: отмены, таймаутов и передачи метаданных (например, trace ID). В Go context.Context - стандартный механизм, который позволяет корректно завершать долгие запросы, освобождать ресурсы и избегать утечек горутин. Без контекста запрос будет выполняться до победного конца, даже если клиент уже отключился, что приводит к деградации сервиса.
Подробное объяснение
Основная задача контекста - распространение сигналов отмены и дедлайнов через границы вызовов. В контексте БД это означает:
- Отмена запроса - если клиент разорвал соединение или сервис завершает работу, запрос к БД должен быть прерван, а не продолжать выполняться в фоне.
- Таймауты - контекст позволяет задать максимальное время выполнения запроса. Это защищает от зависаний, когда БД не отвечает или запрос слишком тяжёлый.
- Проброс метаданных - через контекст можно передавать идентификаторы трассировки, заголовки аутентификации и другие данные, которые нужны на уровне драйвера БД или middleware.
В Go драйверы БД (например, database/sql, pgx, mongo-go-driver) принимают context.Context первым аргументом. Если передать context.Background() или context.TODO(), операция будет выполняться без ограничений по времени и без возможности отмены.
Важно понимать, что контекст - это не только про БД. Это общий паттерн для всех I/O операций: HTTP-запросов, файловых операций, очередей. В контексте БД он особенно критичен, потому что запросы могут быть долгими и ресурсоёмкими.
На практике
В реальном коде контекст обычно создаётся на границе запроса (например, в HTTP handler) и пробрасывается вниз по стеку вызовов. Для БД это выглядит так:
- В handler создаётся контекст с таймаутом через
context.WithTimeout. - Контекст передаётся в сервисный слой, затем в репозиторий, и наконец в метод драйвера БД.
- Если операция занимает больше времени, чем таймаут, контекст отменяется, и запрос к БД прерывается.
Также стоит учитывать, что контекст нужно передавать явно, а не хранить в структурах. Это требование из документации Go: context.Context должен быть первым параметром функции, а не полем структуры.
Пример кода
GOfunc GetUser(ctx context.Context, userID int) (*User, error) {// Создаём контекст с таймаутом 2 секундыctx, cancel := context.WithTimeout(ctx, 2*time.Second)defer cancel()var user Usererr := db.QueryRowContext(ctx,"SELECT id, name FROM users WHERE id = $1", userID,).Scan(&user.ID, &user.Name)if err != nil {if errors.Is(err, context.DeadlineExceeded) {return nil, fmt.Errorf("query timeout: %w", err)}return nil, err}return &user, nil}
Как отвечать на собеседовании
Начните с главного: контекст нужен для управления временем жизни запроса и его отмены. Затем перечислите конкретные сценарии: таймауты, отмена при отключении клиента, проброс метаданных. Упомяните, что в Go это стандартный механизм, и драйверы БД его поддерживают.
Если спросят про context.Background() - объясните, что это допустимо только для операций, которые не должны иметь ограничений (например, миграции), но в production-коде лучше всегда передавать контекст от вышестоящего вызова.
Хорошо показать понимание иерархии: контекст создаётся один раз на границе запроса и пробрасывается через все слои. Также можно упомянуть, что отмена контекста освобождает соединение из пула - это важный момент для производительности.
Что проверяет интервьюер
Интервьюер хочет убедиться, что вы понимаете:
- Зачем нужен контекст в принципе (не только для БД).
- Как контекст связан с управлением ресурсами и пулом соединений.
- Почему нельзя игнорировать контекст или использовать
context.Background()без причины. - Как правильно создавать и отменять контекст (через
defer cancel()). - Разницу между
context.WithTimeout,context.WithCancelиcontext.WithDeadline.
Также проверяется понимание того, что контекст - это не просто параметр, а часть контракта функции. Если функция принимает контекст, она обязана его использовать.
Типичные ошибки
- Использование
context.Background()вместо передачи контекста извне - приводит к тому, что запрос нельзя отменить или ограничить по времени. - Забывание
defer cancel()- утечка ресурсов, контекст не будет отменён после завершения функции. - Хранение контекста в структуре - нарушение рекомендаций Go, контекст должен передаваться явно.
- Игнорирование ошибки отмены - если контекст отменён, запрос вернёт ошибку, и её нужно обрабатывать, а не молча игнорировать.
- Создание контекста на каждый запрос внутри функции - если контекст уже передан, не нужно создавать новый, лучше использовать
context.WithTimeoutот родительского. - Передача
nilвместо контекста - это паника в большинстве драйверов, всегда передавайте хотя быcontext.Background().
> Похожие задачи по Go
Можно ли сделать длину канала для задач равной 10?
Как исправить SQL запрос, который собирает строку без контекста?
Что такое пагинация?
Какие стратегии пагинации можно использовать вместо LIMIT OFFSET?
> Похожие задачи по backend
Можно ли сделать длину канала для задач равной 10?
Как исправить SQL запрос, который собирает строку без контекста?
Что такое пагинация?
Какие стратегии пагинации можно использовать вместо LIMIT OFFSET?
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью