Выполнение транзакций

Вы можете выполнять транзакции базы данных, используя sql.Tx, который представляет транзакцию. Помимо методов Commit и Rollback, представляющих семантику транзакций, sql.Tx содержит все методы, используемые для выполнения обычных операций с базой данных. Чтобы получить sql.Tx, вы вызываете DB.Begin или DB.BeginTx.

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

  1. Начало транзакции.
  2. Выполнение набора операций с базой данных.
  3. Если ошибок не возникло, подтверждение транзакции, чтобы изменения внеслись в базу данных.
  4. Если произошла ошибка, откат транзакции, чтобы база данных осталась без изменений.

Пакет sql предоставляет методы для начала и завершения транзакции, а также методы для выполнения промежуточных операций с базой данных. Эти методы соответствуют четырём шагам в описанном выше рабочем процессе.

  • Начало транзакции.

    DB.Begin или DB.BeginTx начинает новую транзакцию базы данных, возвращая sql.Tx, который её представляет.

  • Выполнение операций с базой данных.

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

  • Завершение транзакции с помощью одного из следующих методов:

    • Подтверждение транзакции с помощью Tx.Commit.

      Если Commit завершается успешно (возвращает nil ошибку), то все результаты запросов подтверждаются как корректные, а все выполненные обновления применяются к базе данных как одна атомарная операция. Если Commit завершается неудачей, то все результаты Query и Exec на Tx должны быть отброшены как недействительные.

    • Откат транзакции с помощью Tx.Rollback.

      Даже если Tx.Rollback завершается неудачей, транзакция больше не будет действительной, и она не будет зафиксирована в базе данных.

Рекомендации по использованию

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

Пример

В следующем примере код использует транзакцию для создания нового заказа клиента на альбом. По пути код:

  1. Начинает транзакцию.
  2. Откладывает откат транзакции. Если транзакция завершится успешно, она будет зафиксирована перед выходом из функции, делая отложенную инструкцию отката бесполезной. Если транзакция завершится неудачей, она не будет зафиксирована, что означает, что откат будет выполнен при выходе из функции.
  3. Проверяет, достаточно ли наличия альбома, который заказывает клиент.
  4. Если достаточно, обновляет количество альбомов на складе, уменьшая его на количество заказанных альбомов.
  5. Создаёт новый заказ и получает сгенерированный ID нового заказа для клиента.
  6. Фиксирует транзакцию и возвращает ID.

Этот пример использует методы Tx, которые принимают аргумент context.Context. Это позволяет отменить выполнение функции — включая операции с базой данных — если она выполняется слишком долго или клиент закрывает соединение. Подробнее см. в разделе Отмена выполняющихся операций.

<code>// CreateOrder создаёт заказ на альбом и возвращает новый ID заказа.
func CreateOrder(ctx context.Context, albumID, quantity, custID int) (orderID int64, err error) {
  // Создаём вспомогательную функцию для подготовки результатов ошибок.
  fail := func(err error) (int64, error) {
    return 0, fmt.Errorf("CreateOrder: %v", err)
  }
  // Получаем Tx для выполнения транзакционных запросов.
  tx, err := db.BeginTx(ctx, nil)
  if err != nil {
    return fail(err)
  }
  // Откладываем откат в случае возникновения ошибки.
  defer tx.Rollback()
  // Проверяем, достаточно ли альбомов на складе для заказа.
  var enough bool
  if err = tx.QueryRowContext(ctx, "SELECT (quantity >= ?) from album where id = ?",
  quantity, albumID).Scan(&enough); err != nil {
    if err == sql.ErrNoRows {
      return fail(fmt.Errorf("no such album"))
    }
    return fail(err)
  }
  if !enough {
    return fail(fmt.Errorf("not enough inventory"))
  }
  // Обновляем количество альбомов на складе, уменьшая его на количество в заказе.
  _, err = tx.ExecContext(ctx, "UPDATE album SET quantity = quantity - ? WHERE id = ?",
  quantity, albumID)
  if err != nil {
    return fail(err)
  }
  // Создаём новую запись в таблице album_order.
  result, err := tx.ExecContext(ctx, "INSERT INTO album_order (album_id, cust_id, quantity, date) VALUES (?, ?, ?, ?)",
  albumID, custID, quantity, time.Now())
  if err != nil {
    return fail(err)
  }
  // Получаем ID только что созданного заказа.
  orderID, err = result.LastInsertId()
  if err != nil {
    return fail(err)
  }
  // Фиксируем транзакцию.
  if err = tx.Commit(); err != nil {
    return fail(err)
  }
  // Возвращаем ID заказа.
  return orderID, nil
}
</code>
GoRu.dev Golang на русском

На сайте представлена адаптированная под русский язык документация языка программирования Golang