← Назад к вопросам

Как использовать контекст, если в одном методе используется несколько транзакций?

1.7 Middle🔥 242 комментариев
#Базы данных#Основы Go

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Управление несколькими транзакциями через Context в Go

При работе с несколькими транзакциями в одном методе контекст (context) становится ключевым инструментом для обеспечения корректной обработки таймаутов, отмены и передачи метаданных. Вот основные подходы и практики:

Базовые принципы использования Context с транзакциями

В Go контекст должен прокидываться через все уровни приложения, включая слой работы с базой данных. При использовании нескольких транзакций в одном методе:

func ProcessOrder(ctx context.Context, orderID int) error {
    // Создаем контекст с таймаутом для всей операции
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()
    
    // Начинаем первую транзакцию
    tx1, err := db.BeginTx(ctx, nil)
    if err != nil {
        return fmt.Errorf("начать транзакцию 1: %w", err)
    }
    defer tx1.Rollback()
    
    // Выполняем операции в первой транзакции
    err = UpdateInventory(ctx, tx1, orderID)
    if err != nil {
        return err
    }
    
    // Вторая транзакция с тем же контекстом
    tx2, err := db.BeginTx(ctx, nil)
    if err != nil {
        return fmt.Errorf("начать транзакцию 2: %w", err)
    }
    defer tx2.Rollback()
    
    err = ProcessPayment(ctx, tx2, orderID)
    if err != nil {
        return err
    }
    
    // Коммит обеих транзакций
    if err := tx1.Commit(); err != nil {
        return fmt.Errorf("коммит транзакции 1: %w", err)
    }
    
    if err := tx2.Commit(); err != nil {
        // Здесь может потребоваться компенсирующее действие
        return fmt.Errorf("коммит транзакции 2: %w", err)
    }
    
    return nil
}

Ключевые стратегии для работы с множественными транзакциями

1. Использование вложенных контекстов для разных таймаутов

Если разные транзакции имеют разные требования к времени выполнения:

func ComplexOperation(ctx context.Context) error {
    // Общий таймаут для всей операции
    ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
    defer cancel()
    
    // Первая транзакция с более строгим таймаутом
    tx1Ctx, cancel1 := context.WithTimeout(ctx, 3*time.Second)
    defer cancel1()
    
    tx1, err := db.BeginTx(tx1Ctx, nil)
    if err != nil {
        return err
    }
    defer tx1.Rollback()
    
    // Вторая транзакция с другим таймаутом
    tx2Ctx, cancel2 := context.WithTimeout(ctx, 5*time.Second)
    defer cancel2()
    
    tx2, err := db.BeginTx(tx2Ctx, nil)
    if err != nil {
        return err
    }
    defer tx2.Rollback()
    
    // ... логика обработки ...
}

2. Координация отмены операций

При отмене одной транзакции может потребоваться отмена других:

func ProcessWithCoordination(ctx context.Context) error {
    // Создаем производный контекст для координации
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
    
    // Запускаем транзакции параллельно
    errCh := make(chan error, 2)
    
    go func() {
        tx, err := db.BeginTx(ctx, nil)
        if err != nil {
            errCh <- err
            return
        }
        defer tx.Rollback()
        
        // Если контекст отменен, операция прервется
        err = OperationA(ctx, tx)
        if err != nil {
            cancel() // Отменяем другие операции
            errCh <- err
            return
        }
        errCh <- tx.Commit()
    }()
    
    // Аналогично для других транзакций...
    
    // Ожидаем завершения
    for i := 0; i < 2; i++ {
        if err := <-errCh; err != nil {
            return err
        }
    }
    return nil
}

3. Паттерн Unit of Work для управления несколькими транзакциями

Для сложных сценариев можно использовать паттерн Unit of Work:

type TransactionCoordinator struct {
    transactions []*sql.Tx
    ctx          context.Context
}

func (tc *TransactionCoordinator) Begin() (*sql.Tx, error) {
    tx, err := db.BeginTx(tc.ctx, nil)
    if err != nil {
        tc.RollbackAll()
        return nil, err
    }
    tc.transactions = append(tc.transactions, tx)
    return tx, nil
}

func (tc *TransactionCoordinator) CommitAll() error {
    for _, tx := range tc.transactions {
        if err := tx.Commit(); err != nil {
            tc.RollbackAll()
            return err
        }
    }
    return nil
}

Важные рекомендации и предостережения

  • Всегда проверяйте контекст перед длительными операциями в транзакциях
  • Используйте defer tx.Rollback() для гарантированного отката при ошибках
  • Избегайте долгих операций после коммита первой транзакции до коммита последующих
  • Помните о deadlock'ах при работе с несколькими транзакциями, особенно если они взаимодействуют с одними и теми же данными
  • Для распределенных транзакций рассмотрите использование Saga Pattern вместо попыток координации через контекст

Практический пример с компенсирующими действиями

func ProcessOrderSaga(ctx context.Context, order Order) error {
    // Шаг 1: Резервирование товара
    tx1, err := db.BeginTx(ctx, nil)
    if err != nil {
        return err
    }
    defer tx1.Rollback()
    
    if err := ReserveItems(ctx, tx1, order); err != nil {
        return fmt.Errorf("резервирование товара: %w", err)
    }
    
    tx1.Commit()
    
    // Шаг 2: Списание платежа
    tx2, err := db.BeginTx(ctx, nil)
    if err != nil {
        // Компенсирующее действие: отмена резервирования
        go CompensateReservation(ctx, order)
        return err
    }
    defer tx2.Rollback()
    
    if err := ChargePayment(ctx, tx2, order); err != nil {
        // Компенсирующее действие при ошибке
        go CompensateReservation(ctx, order)
        return fmt.Errorf("списание платежа: %w", err)
    }
    
    return tx2.Commit()
}

Использование контекста с несколькими транзакциями требует тщательного проектирования, особенно в распределенных системах. Ключевой принцип — явное управление временем жизни и отменой операций, что позволяет создавать отказоустойчивые и предсказуемые приложения.