トランザクションの実行

データベーストランザクションは、トランザクションを表すsql.Txを使用して実行できます。トランザクション固有のセマンティクスを表すCommitメソッドとRollbackメソッドに加えて、sql.Txには、一般的なデータベース操作を実行するために使用するすべてのメソッドがあります。 sql.Txを取得するには、DB.BeginまたはDB.BeginTxを呼び出します。

データベーストランザクションは、複数の操作をより大きな目標の一部としてグループ化します。すべての操作が成功するか、いずれも成功しないかのどちらかであり、いずれの場合もデータの整合性が維持されます。通常、トランザクションのワークフローには、次のものが含まれます。

  1. トランザクションの開始。
  2. 一連のデータベース操作の実行。
  3. エラーが発生しない場合は、トランザクションをコミットしてデータベースの変更を行います。
  4. エラーが発生した場合は、トランザクションをロールバックしてデータベースを変更しないままにします。

sqlパッケージは、トランザクションの開始と終了、および中間データベース操作を実行するためのメソッドを提供します。これらのメソッドは、上記のワークフローの4つのステップに対応します。

ベストプラクティス

トランザクションが必要とする複雑なセマンティクスと接続管理をより適切にナビゲートするには、以下のベストプラクティスに従ってください。

次の例のコードでは、トランザクションを使用してアルバムの新しい顧客注文を作成します。その過程で、コードは次のことを行います。

  1. トランザクションを開始します。
  2. トランザクションのロールバックを遅延させます。トランザクションが成功した場合、関数が終了する前にコミットされるため、遅延されたロールバック呼び出しはno-opになります。トランザクションが失敗した場合はコミットされないため、関数が終了するときにロールバックが呼び出されます。
  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
}