понедельник, 6 сентября 2021 г.

Выполнение транзакций с пакетом database/sql в Golang

Вы можете выполнять транзакции базы данных, используя 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 экспортирует следующие методы:

  • Exec и ExecContext для внесения изменений в базу данных с помощью операторов SQL, таких как INSERT, UPDATE и DELETE.
  • Query, QueryContext, QueryRow и QueryRowContext для операций, возвращающих строки.
  • Prepare, PrepareContext, Stmt и StmtContext для предварительного определения подготовленных операторов.

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

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

Лучшие практики

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

  • Используйте API, описанные в этом разделе, для управления транзакциями. Не используйте операторы SQL, связанные с транзакциями, такие как BEGIN и COMMIT, напрямую - это может привести к непредсказуемому состоянию вашей базы данных, особенно в конкурентных программах.
  • При использовании транзакции старайтесь не вызывать напрямую методы sql.DB, не связанные с транзакцией, так как они будут выполняться вне транзакции, давая вашему коду непоследовательное представление о состоянии базы данных или даже вызывая взаимоблокировки.

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

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

  • Начинать транзакцию.
  • Откладывать (defer) откат транзакции. Если транзакция завершится успешно, она будет зафиксирована до выхода из функции, что сделает отложенный откатный вызов бездействующим. Если транзакция завершится неудачно, она не будет зафиксирована, а это означает, что откат будет вызываться при выходе из функции.
  • Убедится, что для альбома, который заказывает клиент, имеется достаточное количество штук.
  • Если товара достаточно, обновит запас, уменьшив его на количество заказанных альбомов.
  • Создаст новый заказ и получит сгенерированный идентификатор нового заказа для клиента.
  • Зафиксирует транзакцию и вернет идентификатор.

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

// CreateOrder создает заказ для альбома 
// и возвращает идентификатор нового заказа.
func CreateOrder(ctx context.Context, albumID, quantity, custID int) (orderID int64, err error) {

    // Создаем вспомогательную функцию 
    // для подготовки результатов сбоя.
    fail := func(err error) (int64, error) {
        return 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)
    }
    // Получаем идентификатор только что созданной позиции заказа.
    orderID, err := result.LastInsertId()
    if err != nil {
        return fail(err)
    }

    // Фиксируем транзакцию.
    if err = tx.Commit(); err != nil {
        return fail(err)
    }

    // Возвращаем идентификатор заказа.
    return orderID, nil
}


Читайте также:


Купить gopher

Комментариев нет:

Отправить комментарий