Контекст (context) в транзакциях database/sql

Контекст (context.Context) - это механизм в Go для управления временем жизни операций, отмены и передачи значений между вызовами функций. В работе с базой данных через database/sql контекст играет ключевую роль.

Основные цели использования контекста в транзакциях

  1. Отмена операций - можно прервать долгий запрос
  2. Таймауты - установка максимального времени выполнения
  3. Распространение значений - передача метаданных через цепочку вызовов

Методы с поддержкой контекста

В database/sql большинство операций имеют две версии:

  • Обычная (Begin, Exec, Query и т.д.)
  • С контекстом (BeginTx, ExecContext, QueryContext и т.д.)

Пример использования контекста с таймаутом

ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()

// Начинаем транзакцию с таймаутом
tx, err := db.BeginTx(ctx, nil)
if err != nil {
    log.Fatal(err)
}
defer tx.Rollback() // Безопасный откат при ошибках

// Выполняем запрос в транзакции
_, err = tx.ExecContext(ctx, "UPDATE accounts SET balance = balance - 100 WHERE id = 1")
if err != nil {
    // Если контекст истек, получим context.DeadlineExceeded
    log.Printf("Update failed: %v", err)
    return
}

// Коммитим транзакцию
err = tx.Commit()
if err != nil {
    log.Printf("Commit failed: %v", err)
}

Особенности работы контекста в транзакциях

  1. Каскадная отмена - отмена контекста прерывает все операции в транзакции
  2. Изоляция транзакций - контекст не влияет на другие транзакции
  3. Ресурсы - отмена не освобождает соединение автоматически

Практические сценарии использования

  1. HTTP-обработчики - привязка к времени жизни запроса:
func handler(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    tx, err := db.BeginTx(ctx, nil)
    // ...
}
  1. Долгие отчеты - возможность отмены:
ctx, cancel := context.WithCancel(context.Background())
go func() {
    time.Sleep(10*time.Second)
    cancel() // Принудительная отмена через 10 сек
}()

rows, err := db.QueryContext(ctx, "SELECT * FROM big_table")
  1. Распределенные транзакции - передача идентификаторов:
ctx := context.WithValue(context.Background(), "txID", generateID())
tx, _ := db.BeginTx(ctx, nil)

Важные нюансы

  • Всегда проверяйте ошибки на context.Canceled и context.DeadlineExceeded
  • Освобождайте ресурсы с помощью defer даже при отмене контекста
  • Не передавайте один контекст в несколько независимых транзакций

Использование контекста делает ваши транзакции более управляемыми и устойчивыми к долгим операциям.