執行交易
你可以使用表示交易的 sql.Tx,
來執行資料庫交易。除了表示交易特定語意的 Commit
和 Rollback
方法外,sql.Tx
還具有所有用於執行常見資料庫操作的方法。若要取得 sql.Tx
,請呼叫 DB.Begin
或 DB.BeginTx
。
一個 資料庫交易 將多個操作分組為較大目標的一部分。所有操作都必須成功,否則沒有任何操作可以成功,而且在任何情況下都必須保留資料的完整性。通常,交易工作流程包括
- 開始交易。
- 執行一組資料庫操作。
- 如果沒有發生錯誤,提交交易以進行資料庫變更。
- 如果發生錯誤,則回滾交易以使資料庫保持不變。
sql
套件提供開始和結束交易的方法,以及執行介入資料庫操作的方法。這些方法對應於上述工作流程中的四個步驟。
-
開始交易。
DB.Begin
或DB.BeginTx
開始新的資料庫交易,傳回表示交易的sql.Tx
。 -
執行資料庫操作。
使用
sql.Tx
,你可以使用單一連線在操作系列中查詢或更新資料庫。為了支援此功能,Tx
匯出下列方法-
Exec
和ExecContext
用於透過 SQL 陳述式(例如INSERT
、UPDATE
和DELETE
)進行資料庫變更。如需更多資訊,請參閱 執行不傳回資料的 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
}