Контекст (context) в транзакциях database/sql
Контекст (context.Context) - это механизм в Go для управления временем жизни операций, отмены и передачи значений между вызовами функций. В работе с базой данных через database/sql контекст играет ключевую роль.
Основные цели использования контекста в транзакциях
- Отмена операций - можно прервать долгий запрос
- Таймауты - установка максимального времени выполнения
- Распространение значений - передача метаданных через цепочку вызовов
Методы с поддержкой контекста
В 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)
}
Особенности работы контекста в транзакциях
- Каскадная отмена - отмена контекста прерывает все операции в транзакции
- Изоляция транзакций - контекст не влияет на другие транзакции
- Ресурсы - отмена не освобождает соединение автоматически
Практические сценарии использования
- HTTP-обработчики - привязка к времени жизни запроса:
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
tx, err := db.BeginTx(ctx, nil)
// ...
}
- Долгие отчеты - возможность отмены:
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(10*time.Second)
cancel() // Принудительная отмена через 10 сек
}()
rows, err := db.QueryContext(ctx, "SELECT * FROM big_table")
- Распределенные транзакции - передача идентификаторов:
ctx := context.WithValue(context.Background(), "txID", generateID())
tx, _ := db.BeginTx(ctx, nil)
Важные нюансы
- Всегда проверяйте ошибки на
context.Canceled
иcontext.DeadlineExceeded
- Освобождайте ресурсы с помощью
defer
даже при отмене контекста - Не передавайте один контекст в несколько независимых транзакций
Использование контекста делает ваши транзакции более управляемыми и устойчивыми к долгим операциям.