执行事务

您可以使用表示事务的 sql.Tx, 执行数据库事务。除了表示特定于事务的语义的 CommitRollback 方法之外,sql.Tx 具有您用于执行常见数据库操作的所有方法。获取 sql.Tx,你调用DB.Begin or DB.BeginTx

数据库事务将多个操作分组为更大目标的一部分。所有操作都必须成功或不能成功,在任何一种情况下都保持数据的完整性。通常,事务工作流包括:

  1. 开始事务.
  2. 执行一组数据库操作.
  3. 如果没有发生错误,则提交事务以进行数据库更改.
  4. 如果发生错误,则回滚事务以使数据库保持不变。

sql包提供了开始和结束事务的方法,以及执行中间数据库操作的方法。这些方法对应于上述工作流程中的四个步骤。

最佳实践

遵循以下最佳实践,以更好地指引事务有时需要的复杂语义和连接管理。

例子

以下示例中的代码使用事务为专辑创建新的客户订单。在此过程中,代码将:

  1. 开始一个事务.
  2. 推迟事务的回滚。如果事务成功,它将在函数退出之前提交,从而使延迟回滚调用成为无操作。如果事务失败,它将不会被提交,这意味着将在函数退出时调用回滚。
  3. 确认客户订购的专辑有足够的库存。
  4. 如果有足够的,更新库存计数,减少订购专辑的数量。
  5. 创建新订单并检索为客户端新生成的订单ID。
  6. 提交事务并返回 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
}