執行事務
您可以使用 sql.Tx
來執行資料庫事務,它代表一個事務。除了表示事務特定語義的 Commit
和 Rollback
方法外,sql.Tx
還包含所有用於執行常見資料庫操作的方法。要獲取 sql.Tx
,您可以呼叫 DB.Begin
或 DB.BeginTx
。
資料庫事務 將多個操作分組作為更大目標的一部分。所有操作必須成功,否則都不能成功,並且在這兩種情況下都必須保持資料完整性。通常,事務工作流程包括:
- 開始事務。
- 執行一組資料庫操作。
- 如果沒有發生錯誤,則提交事務以進行資料庫更改。
- 如果發生錯誤,則回滾事務以使資料庫保持不變。
sql
包提供了開始和結束事務的方法,以及執行中間資料庫操作的方法。這些方法對應於上述工作流程中的四個步驟。
-
開始事務。
DB.Begin
或DB.BeginTx
開始一個新的資料庫事務,並返回一個表示該事務的sql.Tx
。 -
執行資料庫操作。
使用
sql.Tx
,您可以透過一系列使用單個連線的操作來查詢或更新資料庫。為此,Tx
匯出了以下方法:-
Exec
和ExecContext
用於透過INSERT
、UPDATE
和DELETE
等 SQL 語句進行資料庫更改。更多資訊,請參閱執行不返回資料的 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 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
}