ベストプラクティス

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

  • このセクションで説明されているAPIを使用してトランザクションを管理してください。BEGINCOMMITのようなトランザクション関連のSQL文を直接使用しないでください。そうすると、特に同時プログラムでは、データベースが予測不可能な状態になる可能性があります。
  • トランザクションを使用する際は、非トランザクションのsql.DBメソッドを直接呼び出さないように注意してください。これらはトランザクションの外で実行され、データベースの状態に対してコードに一貫性のないビューを与えたり、デッドロックを引き起こしたりする可能性があります。

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

  • 1. トランザクションを開始します。
  • 2. トランザクションのロールバックを遅延させます。トランザクションが成功した場合、関数が終了する前にコミットされ、遅延ロールバック呼び出しは何もしない操作になります。トランザクションが失敗した場合、コミットされず、関数が終了する際にロールバックが呼び出されます。
  • 3. 顧客が注文しているアルバムの在庫が十分であることを確認します。
  • 4. 十分な在庫がある場合、在庫数を更新し、注文されたアルバムの数だけ減少させます。
  • 5. 新しい注文を作成し、クライアントのために新しい注文の生成されたIDを取得します。
  • 6. トランザクションをコミットし、IDを返します。

この例では、Txメソッドがcontext.Context引数を取ります。これにより、関数の実行(データベース操作を含む)が長すぎる場合やクライアント接続が閉じた場合にキャンセル可能になります。詳細については、進行中の操作のキャンセルを参照してください。

  1. // CreateOrder creates an order for an album and returns the new order ID.
  2. func CreateOrder(ctx context.Context, albumID, quantity, custID int) (orderID int64, err error) {
  3. // Create a helper function for preparing failure results.
  4. fail := func(err error) (int64, error) {
  5. return 0, fmt.Errorf("CreateOrder: %v", err)
  6. }
  7. // Get a Tx for making transaction requests.
  8. tx, err := db.BeginTx(ctx, nil)
  9. if err != nil {
  10. return fail(err)
  11. }
  12. // Defer a rollback in case anything fails.
  13. defer tx.Rollback()
  14. // Confirm that album inventory is enough for the order.
  15. var enough bool
  16. if err = tx.QueryRowContext(ctx, "SELECT (quantity >= ?) from album where id = ?",
  17. quantity, albumID).Scan(&enough); err != nil {
  18. if err == sql.ErrNoRows {
  19. return fail(fmt.Errorf("no such album"))
  20. }
  21. return fail(err)
  22. }
  23. if !enough {
  24. return fail(fmt.Errorf("not enough inventory"))
  25. }
  26. // Update the album inventory to remove the quantity in the order.
  27. _, err = tx.ExecContext(ctx, "UPDATE album SET quantity = quantity - ? WHERE id = ?",
  28. quantity, albumID)
  29. if err != nil {
  30. return fail(err)
  31. }
  32. // Create a new row in the album_order table.
  33. result, err := tx.ExecContext(ctx, "INSERT INTO album_order (album_id, cust_id, quantity, date) VALUES (?, ?, ?, ?)",
  34. albumID, custID, quantity, time.Now())
  35. if err != nil {
  36. return fail(err)
  37. }
  38. // Get the ID of the order item just created.
  39. orderID, err = result.LastInsertId()
  40. if err != nil {
  41. return fail(err)
  42. }
  43. // Commit the transaction.
  44. if err = tx.Commit(); err != nil {
  45. return fail(err)
  46. }
  47. // Return the order ID.
  48. return orderID, nil
  49. }