Выполнение транзакций
Вы можете выполнять транзакции базы данных, используя
sql.Tx, который представляет транзакцию.
Помимо методов Commit и Rollback, представляющих семантику транзакций,
sql.Tx содержит все методы, используемые для выполнения обычных операций с базой данных.
Чтобы получить sql.Tx, вы вызываете DB.Begin или DB.BeginTx.
Транзакция базы данных группирует несколько операций как часть более крупной цели. Все операции должны быть выполнены успешно или ни одна из них не должна быть, при этом целостность данных сохраняется в обоих случаях. Обычно рабочий процесс транзакции включает:
- Начало транзакции.
- Выполнение набора операций с базой данных.
- Если ошибок не возникло, подтверждение транзакции, чтобы изменения внеслись в базу данных.
- Если произошла ошибка, откат транзакции, чтобы база данных осталась без изменений.
Пакет sql предоставляет методы для начала и завершения транзакции, а также методы для выполнения промежуточных операций с базой данных.
Эти методы соответствуют четырём шагам в описанном выше рабочем процессе.
-
Начало транзакции.
DB.BeginилиDB.BeginTxначинает новую транзакцию базы данных, возвращаяsql.Tx, который её представляет. -
Выполнение операций с базой данных.
Используя
sql.Tx, вы можете выполнять запросы или обновления базы данных в серии операций, которые используют одну и ту же подключение. Для поддержки этогоTxэкспортирует следующие методы:-
ExecиExecContextдля выполнения изменений в базе данных через SQL-запросы, такие какINSERT,UPDATEиDELETE.Более подробно см. Выполнение SQL-запросов, не возвращающих данные.
-
Query,QueryContext,QueryRowиQueryRowContextдля операций, возвращающих строки.Более подробно см. Выборка данных.
-
Prepare,PrepareContext,StmtиStmtContextдля предварительного определения подготовленных операторов.Более подробно см. Использование подготовленных операторов.
-
-
Завершение транзакции с помощью одного из следующих методов:
-
Подтверждение транзакции с помощью
Tx.Commit.Если
Commitзавершается успешно (возвращаетnilошибку), то все результаты запросов подтверждаются как корректные, а все выполненные обновления применяются к базе данных как одна атомарная операция. ЕслиCommitзавершается неудачей, то все результатыQueryиExecнаTxдолжны быть отброшены как недействительные. -
Откат транзакции с помощью
Tx.Rollback.Даже если
Tx.Rollbackзавершается неудачей, транзакция больше не будет действительной, и она не будет зафиксирована в базе данных.
-
Рекомендации по использованию
Следуй приведённым ниже рекомендациям, чтобы лучше справляться со сложной семантикой и управлением соединениями, которое иногда требуют транзакции.
- Используйте API, описанные в этом разделе, для управления транзакциями. Не используйте напрямую SQL-инструкции, связанные с транзакциями, такие как
BEGINиCOMMIT— это может привести к непредсказуемому состоянию базы данных, особенно в параллельных программах. - При использовании транзакции следует соблюдать осторожность и не вызывать напрямую методы
sql.DB, не относящиеся к транзакциям, поскольку они будут выполняться вне транзакции, что может привести к несогласованному представлению состояния базы данных или даже к взаимоблокировкам.
Пример
В следующем примере код использует транзакцию для создания нового заказа клиента на альбом. По пути код:
- Начинает транзакцию.
- Откладывает откат транзакции. Если транзакция завершится успешно, она будет зафиксирована перед выходом из функции, делая отложенную инструкцию отката бесполезной. Если транзакция завершится неудачей, она не будет зафиксирована, что означает, что откат будет выполнен при выходе из функции.
- Проверяет, достаточно ли наличия альбома, который заказывает клиент.
- Если достаточно, обновляет количество альбомов на складе, уменьшая его на количество заказанных альбомов.
- Создаёт новый заказ и получает сгенерированный ID нового заказа для клиента.
- Фиксирует транзакцию и возвращает 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>