Go: отменили HTTP-запрос, а база всё ещё работает

Представим ситуацию. Клиент отправил запрос в go api. Сервер начал выполнять тяжёлый sql-запрос, но клиент не дождался ответа и отключился. Кажется, что работа должна остановиться. Ведь у нас есть context.Context. Но не всё так просто. Например:

func getOrders(ctx context.Context, db *sql.DB) error { rows, err := db.Query("SELECT * FROM orders") if err != nil { return err } defer rows.Close() // Обработка результата return nil }

Здесь ctx вообще не участвует в выполнении sql-запроса. Даже если он отменится, база может продолжить работу.

Правильнее использовать контекстный метод:

func getOrders(ctx context.Context, db *sql.DB) error { rows, err := db.QueryContext( ctx, "SELECT * FROM orders", ) if err != nil { return err } defer rows.Close() // Обработка результата return nil }

Теперь database/sql передаёт отмену контекста драйверу. Если драйвер и СУБД поддерживают отмену, запрос можно прервать.

Но есть нюанс: отмена контекста не гарантирует мгновенную остановку работы на стороне базы. Всё зависит от драйвера, базы данных и того, как именно выполняется запрос.

И ещё одна распространённая ошибка: db.QueryContext(context.Background(), query)

Формально метод контекстный. Но context.Background() не отменится при отключении http-клиента и не имеет дедлайна.

В обработчике http-запроса обычно стоит передавать контекст самого запроса или его производный контекст с таймаутом.

Например: ctx, cancel := context.WithTimeout( r.Context(), 2*time.Second, ) defer cancel() rows, err := db.QueryContext(ctx, query)

Так мы ограничиваем время ожидания и связываем операцию с жизненным циклом http-запроса.

Вывод: наличие context.Context в функции ещё не означает, что операция умеет отменяться. Контекст нужно передать именно туда, где выполняется работа, а поддержка отмены зависит от конкретной реализации.

В высоконагруженном сервисе это важно: клиент уже ушёл, а ресурсы базы всё ещё могут расходоваться.