Зачем добавлять контекст в запросы к базе данных?
Что проверяют
Понимаете ли вы, что запрос к базе должен жить не дольше, чем нужен вызывающему, и как это обеспечивает context.Context. Следом спрашивают, что происходит с запросом, когда клиент закрыл вкладку, и что будет с транзакцией при отмене контекста.
Как отвечать
В database/sql у всех операций есть варианты с контекстом: QueryContext, ExecContext, QueryRowContext, BeginTx, PrepareContext, PingContext. Методы без суффикса используют context.Background() и не отменяются никогда. В pgx контекст первым аргументом принимают все методы.
Что даёт контекст:
- отмена по запросу клиента: в HTTP-обработчике
r.Context()отменяется, когда клиент разорвал соединение или обработчик завершился. Передав его в запрос, вы не занимаете соединение и не грузите базу работой, которую уже никто не прочитает. В зависимости от драйвера это отмена запроса на сервере или закрытие соединения; - таймаут:
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)и сразуdefer cancel(). Таймаут действует и на ожидание свободного соединения, когда пул упёрся вSetMaxOpenConns, а без контекста горутина может ждать там бесконечно; - транзакции: если контекст, переданный в
BeginTx, отменён до коммита, пакетdatabase/sqlсам откатит транзакцию; - трассировка: OpenTelemetry-инструментация драйвера берёт текущий спан из контекста, поэтому запросы попадают в трейс нужного HTTP-запроса. Туда же кладут request id для логов.
Ошибки проверяйте через errors.Is(err, context.DeadlineExceeded) и context.Canceled: первое стоит логировать как медленный запрос, второе обычно штатная ситуация.
Где спотыкаются
Создают context.Background() внутри репозитория. Цепочка отмены рвётся, и таймаут обработчика на запрос в базу уже не действует.
Забывают defer cancel() после WithTimeout. Контекст и его таймер живут до истечения срока, а go vet находит такой код проверкой lostcancel.
Говорят, что контекст защищает от гонок при конкурентном доступе. Никакой синхронизации он не даёт: это только сигнал отмены, дедлайн и значения уровня запроса. От долгих запросов на стороне самой базы по-прежнему защищает statement_timeout.