执行事务

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

数据库事务 将多个操作组合为更大目标的一部分。所有操作都必须成功,否则任何一个操作都无法成功,并且在任何情况下都必须保持数据的完整性。通常,事务工作流包括

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

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

最佳实践

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

示例

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

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