执行事务
您可以使用表示事务的 sql.Tx,
执行数据库事务。除了表示特定于事务的语义的 Commit
和 Rollback
方法之外,sql.Tx
具有您用于执行常见数据库操作的所有方法。获取 sql.Tx
,你调用DB.Begin
or 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 为专辑创建订单并返回新的订单 ID。
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)
}
// 获取刚创建的订单项的 ID。
orderID, err := result.LastInsertId()
if err != nil {
return fail(err)
}
// 提交事务.
if err = tx.Commit(); err != nil {
return fail(err)
}
// 返回订单编号.
return orderID, nil
}