トランザクションの実行
データベーストランザクションは、トランザクションを表すsql.Tx
を使用して実行できます。トランザクション固有のセマンティクスを表すCommit
メソッドとRollback
メソッドに加えて、sql.Tx
には、一般的なデータベース操作を実行するために使用するすべてのメソッドがあります。 sql.Tx
を取得するには、DB.Begin
またはDB.BeginTx
を呼び出します。
データベーストランザクションは、複数の操作をより大きな目標の一部としてグループ化します。すべての操作が成功するか、いずれも成功しないかのどちらかであり、いずれの場合もデータの整合性が維持されます。通常、トランザクションのワークフローには、次のものが含まれます。
- トランザクションの開始。
- 一連のデータベース操作の実行。
- エラーが発生しない場合は、トランザクションをコミットしてデータベースの変更を行います。
- エラーが発生した場合は、トランザクションをロールバックしてデータベースを変更しないままにします。
sql
パッケージは、トランザクションの開始と終了、および中間データベース操作を実行するためのメソッドを提供します。これらのメソッドは、上記のワークフローの4つのステップに対応します。
-
トランザクションを開始します。
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を使用してトランザクションを管理します。
BEGIN
やCOMMIT
などのトランザクション関連のSQLステートメントを直接使用しないでください。特に同時実行プログラムでは、データベースが予測不可能な状態になる可能性があります。 - トランザクションを使用する場合は、トランザクション以外の
sql.DB
メソッドも直接呼び出さないように注意してください。それらはトランザクション外で実行されるため、コードにデータベースの状態の不整合なビューを与えたり、デッドロックを引き起こしたりする可能性があります。
例
次の例のコードでは、トランザクションを使用してアルバムの新しい顧客注文を作成します。その過程で、コードは次のことを行います。
- トランザクションを開始します。
- トランザクションのロールバックを遅延させます。トランザクションが成功した場合、関数が終了する前にコミットされるため、遅延されたロールバック呼び出しはno-opになります。トランザクションが失敗した場合はコミットされないため、関数が終了するときにロールバックが呼び出されます。
- 顧客が注文しているアルバムの在庫が十分であることを確認します。
- 十分な在庫がある場合は、注文したアルバムの数だけ在庫数を減らして更新します。
- 新しい注文を作成し、クライアントのために生成された新しい注文の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
}