执行事务
您可以使用表示事务的 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
失败,则Tx
上Query
和Exec
的所有结果都应被丢弃为无效。 -
使用
Tx.Rollback
回滚事务。即使
Tx.Rollback
失败,事务也不会再有效,也不会提交到数据库。
-
最佳实践
遵循以下最佳实践,以便更好地了解事务有时需要的复杂语义和连接管理。
- 使用本部分中描述的 API 来管理事务。不要直接使用事务相关的 SQL 语句,如 `BEGIN` 和 `COMMIT`,这样做可能会使你的数据库处于不可预测的状态,尤其是在并发程序中。
- 在使用事务时,也要注意不要直接调用非事务 `sql.DB` 方法,因为这些方法将在事务外部执行,从而使你的代码对数据库状态产生不一致的视图,甚至导致死锁。
示例
以下示例中的代码使用事务为专辑创建新客户订单。在此过程中,代码将
- 开始事务。
- 延迟事务回滚。如果事务成功,它将在函数退出之前提交,从而使延迟回滚调用成为无操作。如果事务失败,它将不会提交,这意味着回滚将在函数退出时调用。
- 确认客户订购的专辑有足够的库存。
- 如果有足够的库存,更新库存数量,将其减少订购的专辑数量。
- 创建一个新订单并为客户端检索新订单的生成 ID。
- 提交事务并返回 ID。
此示例使用带有 `context.Context` 参数的 `Tx` 方法。如果函数执行(包括数据库操作)运行时间过长或客户端连接关闭,这使得可以取消函数执行。
// CreateOrder creates an order for an album and returns the new order ID.
func CreateOrder(ctx context.Context, albumID, quantity, custID int) (orderID int64, err error) {
// Create a helper function for preparing failure results.
fail := func(err error) (int64, error) {
return 0, fmt.Errorf("CreateOrder: %v", err)
}
// Get a Tx for making transaction requests.
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return fail(err)
}
// Defer a rollback in case anything fails.
defer tx.Rollback()
// Confirm that album inventory is enough for the order.
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"))
}
// Update the album inventory to remove the quantity in the order.
_, err = tx.ExecContext(ctx, "UPDATE album SET quantity = quantity - ? WHERE id = ?",
quantity, albumID)
if err != nil {
return fail(err)
}
// Create a new row in the album_order table.
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)
}
// Get the ID of the order item just created.
orderID, err = result.LastInsertId()
if err != nil {
return fail(err)
}
// Commit the transaction.
if err = tx.Commit(); err != nil {
return fail(err)
}
// Return the order ID.
return orderID, nil
}