执行事务

Executing transactions - 执行事务

原文:https://go.dev/doc/database/execute-transactions

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

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

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

数据库事务将多个操作分组为更大目标的一部分。所有操作都必须成功,或者都不能成功,并且无论哪种情况都保留数据的完整性。通常,事务工作流程包括:

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

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

最佳实践

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

  • 使用本节中描述的API来管理事务。不要直接使用与事务相关的SQL语句,如BEGINCOMMIT,这样做可能会使数据库处于不可预测的状态,特别是在并发程序中。
  • 在使用事务时,注意不要直接调用非事务性的sql.DB方法,因为这些方法将在事务之外执行,将会给您的代码提供一个不一致的数据库状态视图,甚至可能导致死锁。

示例

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

  1. 开始一个事务。
  2. 延迟该事务的回滚。如果事务成功,它将在函数退出前被提交,使延迟的回滚调用成为no-op(空操作)。如果事务失败,它将不会被提交,这意味着当函数退出时会调用回滚。
  3. 确认客户订购的专辑有足够的库存。
  4. 如果有足够的存货,更新存货计数,用订购的专辑数量来减少它。
  5. 创建一个新的订单,并为客户检索新订单的生成ID。
  6. 提交事务并返回ID。

​ 这个例子使用了Tx方法,这些方法需要一个context.Context实参。这使得函数的执行——包括数据库操作——在其运行时间过长或客户端连接关闭的情况下有可能被取消。更多信息请参见Canceling in-progress operations(取消正在进行的操作)

​ 这个示例使用了接受context.Context实参的Tx方法。这使得函数的执行(包括数据库操作)可以在运行时间过长或客户端连接关闭时被取消。有关更多信息,请参阅Canceling in-progress operations(取消正在进行的操作)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// CreateOrder 为 album 创建一个订单,并返回新的订单 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
}
最后修改 October 27, 2023: 更新 (d5ede1a)