執行事務

您可以使用 sql.Tx 來執行資料庫事務,它代表一個事務。除了表示事務特定語義的 CommitRollback 方法外,sql.Tx 還包含所有用於執行常見資料庫操作的方法。要獲取 sql.Tx,您可以呼叫 DB.BeginDB.BeginTx

資料庫事務 將多個操作分組作為更大目標的一部分。所有操作必須成功,否則都不能成功,並且在這兩種情況下都必須保持資料完整性。通常,事務工作流程包括:

  1. 開始事務。
  2. 執行一組資料庫操作。
  3. 如果沒有發生錯誤,則提交事務以進行資料庫更改。
  4. 如果發生錯誤,則回滾事務以使資料庫保持不變。

sql 包提供了開始和結束事務的方法,以及執行中間資料庫操作的方法。這些方法對應於上述工作流程中的四個步驟。

  • 開始事務。

    DB.BeginDB.BeginTx 開始一個新的資料庫事務,並返回一個表示該事務的 sql.Tx

  • 執行資料庫操作。

    使用 sql.Tx,您可以透過一系列使用單個連線的操作來查詢或更新資料庫。為此,Tx 匯出了以下方法:

  • 以下列方法中的**一種**結束事務:

    • 使用 Tx.Commit 提交事務。

      如果 Commit 成功(返回 nil 錯誤),則所有查詢結果都將確認為有效,並且所有執行的更新都將作為單個原子更改應用到資料庫。如果 Commit 失敗,則 Tx 上的 QueryExec 的所有結果都應作為無效結果丟棄。

    • 使用 Tx.Rollback 回滾事務。

      即使 Tx.Rollback 失敗,事務也將不再有效,也不會提交到資料庫。

最佳實踐

遵循以下最佳實踐,以更好地駕馭事務有時需要的複雜語義和連線管理。

  • 使用本節中描述的 API 來管理事務。**不要**直接使用與事務相關的 SQL 語句,例如 BEGINCOMMIT——這樣做可能會使您的資料庫處於不可預測的狀態,尤其是在併發程式中。
  • 使用事務時,請注意不要直接呼叫非事務性的 sql.DB 方法,因為這些方法將在事務外部執行,從而導致您的程式碼對資料庫狀態的檢視不一致,甚至可能導致死鎖。

示例

以下示例中的程式碼使用事務為專輯建立新的客戶訂單。在此過程中,程式碼將:

  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
}