Go: Goのデータベース操作:database/sql、CRUD、インジェクション防止のためのプリコンパイル済みステート…
Goの
database/sqlパッケージは、SQLデータベース操作のための統一されたインターフェースを提供しています。ドライバーを変更するだけで、MySQL、SQLite、PostgreSQLを切り替えることができます。
SQLインジェクションの防止、トランザクションの管理、接続プールの設定など、データベースを安全に扱う必要がある場合、database/sql 標準ライブラリは、完全かつ安全なツール一式を提供します。
1. 学習内容
database/sql: ドライバの登録と接続の確立- 3つのクエリ方法:
db.Query、db.QueryRow、およびdb.Exec db.Prepare: プリコンパイルを使用してSQLインジェクションを防ぐ- 取引:
Begin/Commit/Rollback - 接続プールの設定:
SetMaxOpenConns/SetMaxIdleConns - SQLite および MySQL ドライバーの使用
2. バックエンドエンジニアの実話
(1) 課題:SQLにおける文字列連結が、ハッカーによるインジェクション攻撃を招き、データベースの削除につながった
ボブは、あるECプラットフォームのバックエンドエンジニアです。彼は、商品検索APIを実装する必要があります:
「私は
fmt.Sprintfを使って、商品に関するSQLクエリを作成しました:SELECT * FROM products WHERE name LIKE '%" + search + "%'。サービス開始から2日目、誰かが検索ボックスに' OR 1=1; DROP TABLE products;--と入力しました。すると、私の商品テーブルが消えてしまいました。上司から『データベースにあった10万件の商品はどこへ消えたんだ?』と尋ねられました。」
// Bad code: string concatenation SQL, fatal vulnerability
func searchProducts(w http.ResponseWriter, r *http.Request) {
search := r.URL.Query().Get("q")
// If search = "' OR 1=1; DROP TABLE products;--"
// Final SQL: SELECT * FROM products WHERE name LIKE '%' OR 1=1; DROP TABLE products;--%'
query := fmt.Sprintf("SELECT * FROM products WHERE name LIKE '%%%s%%'", search)
rows, err := db.Query(query) // Nightmare begins
}
(2) Goの解決策:プリコンパイル済みのPreparedStatement
// Good code: precompiled, parameters automatically escaped
func searchProducts(w http.ResponseWriter, r *http.Request) {
search := r.URL.Query().Get("q")
// ? is a placeholder; the database driver automatically escapes parameters
// The search value is always treated as a string, never as part of SQL syntax
stmt, err := db.Prepare("SELECT * FROM products WHERE name LIKE ?")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer stmt.Close()
rows, err := stmt.Query("%" + search + "%")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer rows.Close()
// Process results...
}
(3) パフォーマンス:文字列連結とプリコンパイル済みSQLの比較
| 次元 | 文字列の連結 | PreparedStatement |
|---|---|---|
| SQLインジェクション | ❌ 高リスク | ✅ 自動エスケープ |
| パフォーマンス | 実行ごとのSQL解析 | ✅ 解析は1回のみ。その後の実行では高速化 |
| 読みやすさ | 分かりにくい | ✅ 分かりやすい |
| パラメータの型 | すべて文字列に変換 | ✅ 型を維持 |
3. データベースへの接続
▶ サンプル:SQLiteへの接続
⚙️ 前提条件:
go get github.com/mattn/go-sqlite3を実行する ⚠️ 注意:go-sqlite3 には CGO が必要です。Windows では gcc(MinGW-w64)が必要ですが、macOS/Linux ではデフォルトで含まれています。
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/mattn/go-sqlite3" // Import driver (_ means only init is executed, not directly used)
)
func main() {
// Open database (actual connection is established on first query)
db, err := sql.Open("sqlite3", "./test.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Test if connection is reachable
err = db.Ping()
if err != nil {
log.Fatal("Cannot connect to database:", err)
}
fmt.Println("Database connected successfully")
}
▶ サンプル:MySQLへの接続
⚙️ 前提条件:
go get github.com/go-sql-driver/mysqlを実行する
package main
import (
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// DSN format: user:password@tcp(host:port)/dbname?params
dsn := "root:password@tcp(127.0.0.1:3306)/shop?charset=utf8mb4&parseTime=true"
db, err := sql.Open("mysql", dsn)
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Connection pool configuration
db.SetMaxOpenConns(25) // Maximum open connections
db.SetMaxIdleConns(10) // Maximum idle connections
db.SetConnMaxLifetime(5 * time.Minute) // Maximum connection lifetime
db.SetConnMaxIdleTime(2 * time.Minute) // Maximum idle connection lifetime
if err = db.Ping(); err != nil {
log.Fatal("Cannot connect to database:", err)
}
fmt.Println("MySQL connected successfully")
}
(3) database/sql パッケージの主要なメソッド
| メソッド | 目的 | 戻り値 |
|---|---|---|
sql.Open(driver, dsn) |
データベースを開く(遅延読み込み) | *DB, error |
db.Ping() |
接続が実際に可能かどうかをテストします | error |
db.Close() |
データベースを閉じる | error |
db.Exec(sql, args...) |
INSERT/UPDATE/DELETE を実行 | Result, error |
db.Query(sql, args...) |
SELECT文を実行し、複数の行を返す | *Rows, error |
db.QueryRow(sql, args...) |
SELECT文を実行し、1行を返す | *Row |
db.Prepare(sql) |
SQLのプリコンパイル | *Stmt, error |
sql.Open は実際には接続を確立するものではなく、単に DSN の形式を検証するだけです。実際の接続は、最初の Ping または Query / Exec の実行時に確立されます。 したがって、sql.Open が nil error を返すからといって、データベースにアクセスできるとは限りません。
4. CRUD操作
▶ サンプル:テーブルの作成とデータの挿入
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/mattn/go-sqlite3"
)
func main() {
db, err := sql.Open("sqlite3", "./shop.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Create table
createSQL := `
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
price REAL NOT NULL,
stock INTEGER NOT NULL DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`
_, err = db.Exec(createSQL)
if err != nil {
log.Fatal(err)
}
fmt.Println("Table created successfully")
// Insert data (Exec returns Result)
result, err := db.Exec(
"INSERT INTO products (name, price, stock) VALUES (?, ?, ?)",
"Laptop", 999.99, 10,
)
if err != nil {
log.Fatal(err)
}
id, _ := result.LastInsertId()
affected, _ := result.RowsAffected()
fmt.Printf("Inserted: id=%d, rows affected=%d\n", id, affected)
}
▶ サンプル:データの照会
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/mattn/go-sqlite3"
)
type Product struct {
ID int
Name string
Price float64
Stock int
}
func main() {
db, err := sql.Open("sqlite3", "./shop.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// QueryRow: query a single row
var p Product
err = db.QueryRow("SELECT id, name, price, stock FROM products WHERE id = ?", 1).
Scan(&p.ID, &p.Name, &p.Price, &p.Stock)
if err == sql.ErrNoRows {
fmt.Println("Product not found")
} else if err != nil {
log.Fatal(err)
} else {
fmt.Printf("Product: %+v\n", p)
}
// Query: query multiple rows
rows, err := db.Query("SELECT id, name, price, stock FROM products WHERE price < ?", 500)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var pr Product
err := rows.Scan(&pr.ID, &pr.Name, &pr.Price, &pr.Stock)
if err != nil {
log.Fatal(err)
}
fmt.Printf(" Product: %+v\n", pr)
}
// Check if iteration encountered an error
if err = rows.Err(); err != nil {
log.Fatal(err)
}
}
(3) CRUDメソッドの比較
| 操作 | 方法 | 戻り値 | ユースケース |
|---|---|---|---|
| 作成 | db.Exec(INSERT...) |
Result (LastInsertId + RowsAffected) |
挿入/更新/削除 |
| 1行を読み込む | db.QueryRow(SELECT...).Scan() |
*Row + 自動閉じ |
1行クエリ |
| 複数行の読み取り | db.Query(SELECT...); rows.Scan() |
*Rows(反復処理と閉じ処理が必要) |
複数行クエリ |
| 更新 | db.Exec(UPDATE...) |
Result (影響行数) |
データの更新 |
| 削除 | db.Exec(DELETE...) |
Result (影響を受ける行数) |
データを削除 |
rows.Close() は、たとえ for rows.Next() のすべての行をすでに処理し終えていたとしても、必ず呼び出す必要があります。閉じないと、接続リーク(接続がプールに解放されない)が発生します。defer rows.Close() を使用して、確実に解放されるようにしてください。
5. PreparedStatement
▶ サンプル:プリコンパイルされたバッチ挿入
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/mattn/go-sqlite3"
)
type Product struct {
Name string
Price float64
Stock int
}
func main() {
db, err := sql.Open("sqlite3", "./shop.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Precompile SQL statement
stmt, err := db.Prepare("INSERT INTO products (name, price, stock) VALUES (?, ?, ?)")
if err != nil {
log.Fatal(err)
}
defer stmt.Close()
// Batch insert (SQL compiled only once)
products := []Product{
{"Mouse", 29.99, 100},
{"Keyboard", 79.99, 50},
{"Monitor", 299.99, 20},
{"USB-C Hub", 49.99, 200},
}
for _, p := range products {
result, err := stmt.Exec(p.Name, p.Price, p.Stock)
if err != nil {
log.Printf("Insert failed %s: %v", p.Name, err)
continue
}
id, _ := result.LastInsertId()
fmt.Printf("Inserted successfully: %s (id=%d)\n", p.Name, id)
}
}
(2) プリコンパイルされたSQLと連結されたSQL
| 比較 | プリコンパイル Prepare |
SQL連結 |
|---|---|---|
| SQLインジェクション | ✅ パラメータの自動エスケープ | ❌ 高リスク |
| パフォーマンス(複数回の実行) | ✅ 解析は1回のみ | ❌ 毎回解析 |
| コードの可読性 | 明確(パラメータのプレースホルダーとして「?」を使用) | 分かりにくい(引用符が入れ子になっている) |
| 型安全性 | ✅ パラメータの型を維持 | ❌ すべて文字列に変換 |
| 適用されるシナリオ | すべてのユーザー入力 | 静的SQLのみ(テーブル名・カラム名。ユーザー入力は含まない) |
6. 取引
▶ サンプル:取引の振替
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/mattn/go-sqlite3"
)
func transferFunds(db *sql.DB, fromID, toID int, amount float64) error {
// Begin transaction
tx, err := db.Begin()
if err != nil {
return fmt.Errorf("failed to begin transaction: %w", err)
}
// Rollback on transaction failure
defer tx.Rollback() // If Commit succeeds, Rollback is a no-op
// 1. Deduct from fromID
result, err := tx.Exec(
"UPDATE accounts SET balance = balance - ? WHERE id = ? AND balance >= ?",
amount, fromID, amount,
)
if err != nil {
return fmt.Errorf("deduction failed: %w", err)
}
affected, _ := result.RowsAffected()
if affected == 0 {
return fmt.Errorf("insufficient balance or account does not exist")
}
// 2. Add to toID
result, err = tx.Exec(
"UPDATE accounts SET balance = balance + ? WHERE id = ?",
amount, toID,
)
if err != nil {
return fmt.Errorf("credit failed: %w", err)
}
affected, _ = result.RowsAffected()
if affected == 0 {
return fmt.Errorf("recipient account does not exist")
}
// Commit transaction
return tx.Commit()
}
func main() {
db, err := sql.Open("sqlite3", "./bank.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Initialize accounts
db.Exec("CREATE TABLE IF NOT EXISTS accounts (id INTEGER PRIMARY KEY, balance REAL)")
db.Exec("INSERT OR IGNORE INTO accounts VALUES (1, 1000.00)")
db.Exec("INSERT OR IGNORE INTO accounts VALUES (2, 500.00)")
// Transfer 200 from account 1 to 2
err = transferFunds(db, 1, 2, 200)
if err != nil {
log.Printf("Transfer failed: %v\n", err)
} else {
fmt.Println("Transfer successful!")
}
}
sequenceDiagram
participant App as Application
participant DB as Database
App->>DB: BEGIN
DB-->>App: OK
App->>DB: UPDATE SET balance = balance - ? WHERE id = 1
DB-->>App: 1 row affected
App->>DB: UPDATE SET balance = balance + ? WHERE id = 2
DB-->>App: 1 row affected
App->>DB: COMMIT
DB-->>App: OK (persisted)
Note over App,DB: If error midway → ROLLBACK<br/>All changes reverted
defer tx.Rollback() を使用するのは安全なパターンです。もし Commit が成功すれば、Rollback の呼び出しは安全(何もしない操作)です。もし Commit が失敗した場合、defer が自動的にトランザクションをロールバックします。Rollbackを絶対に省略しないでください!
7. コネクションプールの設定
package main
import (
"database/sql"
"fmt"
"time"
_ "github.com/go-sql-driver/mysql"
)
func configurePool(db *sql.DB) {
// Maximum open connections (new requests queue when this limit is reached)
db.SetMaxOpenConns(25)
// Maximum idle connections (kept open but unused)
db.SetMaxIdleConns(10)
// Maximum connection lifetime (prevents long-running connections from being dropped by the database)
db.SetConnMaxLifetime(5 * time.Minute)
// Maximum idle connection lifetime
db.SetConnMaxIdleTime(2 * time.Minute)
}
func main() {
db, _ := sql.Open("mysql", "user:pass@/dbname")
configurePool(db)
fmt.Println("Connection pool configured")
}
(1) 接続プールパラメータ
| パラメータ | デフォルト値 | 推奨値 | 説明 |
|---|---|---|---|
SetMaxOpenConns |
0 (無制限) | 25–100 | 同時接続数の上限 |
SetMaxIdleConns |
2 | 10–25 | アイドル接続の最大数(MaxOpenConns以下) |
SetConnMaxLifetime |
0(有効期限なし) | 5~30分 | 最大接続有効期間 |
SetConnMaxIdleTime |
0(有効期限なし) | 2~5分 | アイドル時の接続タイムアウト |
MaxIdleConns を MaxOpenConns より大きく設定することはできません。データベースやSQLが自動的に調整します。また、MaxOpenConns=0 を無制限に設定しないでください。同時アクセス数が多くなると、接続数が膨大になり、データベースに過負荷がかかります。常に妥当な上限値を設定してください。
8. 完全な例:Eコマースの在庫管理
⚙️ 前提条件:
go get github.com/mattn/go-sqlite3を実行する
// inventory.go
package main
import (
"database/sql"
"fmt"
"log"
"sync"
"time"
_ "github.com/mattn/go-sqlite3"
)
// ---------- Store ----------
type InventoryStore struct {
db *sql.DB
mu sync.RWMutex
}
func NewInventoryStore(dbPath string) (*InventoryStore, error) {
db, err := sql.Open("sqlite3", dbPath)
if err != nil {
return nil, fmt.Errorf("failed to open database: %w", err)
}
// Connection pool configuration
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(5 * time.Minute)
store := &InventoryStore{db: db}
if err := store.initSchema(); err != nil {
return nil, fmt.Errorf("failed to initialize schema: %w", err)
}
return store, nil
}
func (s *InventoryStore) initSchema() error {
schema := `
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
price REAL NOT NULL,
stock INTEGER NOT NULL DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
product_id INTEGER NOT NULL,
quantity INTEGER NOT NULL,
total REAL NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (product_id) REFERENCES products(id)
);`
_, err := s.db.Exec(schema)
return err
}
// ---------- Product Operations ----------
func (s *InventoryStore) AddProduct(name string, price float64, stock int) (int64, error) {
result, err := s.db.Exec(
"INSERT INTO products (name, price, stock) VALUES (?, ?, ?)",
name, price, stock,
)
if err != nil {
return 0, err
}
return result.LastInsertId()
}
func (s *InventoryStore) GetProduct(id int) (product, error) {
var p product
err := s.db.QueryRow(
"SELECT id, name, price, stock, created_at FROM products WHERE id = ?", id,
).Scan(&p.ID, &p.Name, &p.Price, &p.Stock, &p.CreatedAt)
if err == sql.ErrNoRows {
return p, fmt.Errorf("product not found")
}
return p, err
}
type product struct {
ID int
Name string
Price float64
Stock int
CreatedAt time.Time
}
// ---------- Order Operations (transaction) ----------
type OrderRequest struct {
ProductID int
Quantity int
}
func (s *InventoryStore) PlaceOrder(req OrderRequest) (int64, error) {
s.mu.Lock() // Prevent overselling: only one order deduction at a time
defer s.mu.Unlock()
tx, err := s.db.Begin()
if err != nil {
return 0, fmt.Errorf("failed to begin transaction: %w", err)
}
defer tx.Rollback() // Safe rollback
// 1. Query product and lock row (⚠️ SQLite does not support FOR UPDATE; use MySQL/PostgreSQL for true row locking)
var price float64
var stock int
err = tx.QueryRow(
"SELECT price, stock FROM products WHERE id = ? FOR UPDATE",
req.ProductID,
).Scan(&price, &stock)
if err == sql.ErrNoRows {
return 0, fmt.Errorf("product not found")
}
if err != nil {
return 0, err
}
// 2. Check stock
if stock < req.Quantity {
return 0, fmt.Errorf("insufficient stock: need %d, have %d", req.Quantity, stock)
}
// 3. Deduct stock
result, err := tx.Exec(
"UPDATE products SET stock = stock - ? WHERE id = ? AND stock >= ?",
req.Quantity, req.ProductID, req.Quantity,
)
if err != nil {
return 0, err
}
affected, _ := result.RowsAffected()
if affected == 0 {
return 0, fmt.Errorf("concurrent stock shortage")
}
// 4. Create order
total := price * float64(req.Quantity)
result, err = tx.Exec(
"INSERT INTO orders (product_id, quantity, total) VALUES (?, ?, ?)",
req.ProductID, req.Quantity, total,
)
if err != nil {
return 0, err
}
orderID, _ := result.LastInsertId()
// 5. Commit
if err := tx.Commit(); err != nil {
return 0, fmt.Errorf("failed to commit transaction: %w", err)
}
return orderID, nil
}
// ---------- Report ----------
func (s *InventoryStore) LowStockReport(threshold int) ([]product, error) {
rows, err := s.db.Query(
"SELECT id, name, price, stock, created_at FROM products WHERE stock < ? ORDER BY stock ASC",
threshold,
)
if err != nil {
return nil, err
}
defer rows.Close()
var products []product
for rows.Next() {
var p product
if err := rows.Scan(&p.ID, &p.Name, &p.Price, &p.Stock, &p.CreatedAt); err != nil {
return nil, err
}
products = append(products, p)
}
return products, rows.Err()
}
// ---------- Main ----------
func main() {
store, err := NewInventoryStore("./inventory.db")
if err != nil {
log.Fatal(err)
}
// Add products
laptopID, _ := store.AddProduct("Laptop", 999.99, 10)
mouseID, _ := store.AddProduct("Mouse", 29.99, 100)
fmt.Printf("Products added: laptop=%d, mouse=%d\n", laptopID, mouseID)
// Place order (transaction-safe)
orderID, err := store.PlaceOrder(OrderRequest{ProductID: int(laptopID), Quantity: 2})
if err != nil {
log.Printf("Order failed: %v\n", err)
} else {
fmt.Printf("Order placed successfully: order=%d\n", orderID)
}
// Low stock report
lowStock, _ := store.LowStockReport(20)
fmt.Printf("Low stock products (%d items):\n", len(lowStock))
for _, p := range lowStock {
fmt.Printf(" %s: stock=%d\n", p.Name, p.Stock)
}
}
FOR UPDATE 行ロックをサポートしていません。この句は黙って無視されます。上記のコードは MySQL/PostgreSQL でのみ真の行ロックを実現します。SQLite では、mu.Lock() ミューテックスによって安全性が確保されます。データベース間の互換性を確保するには、行ロックの代わりに条件付き更新 (UPDATE ... WHERE stock >= ?) を使用してください。
❓ よくある質問
database/sqlとクエリビルダー(squirrelなど)を併用することをお勧めします。' OR 1=1は単なる文字列であり、WHERE句にはなりません。tx.Rollback() を使用してすべての変更をロールバックします。defer tx.Rollback() + tx.Commit()は標準的なパターンです。つまり、Commitが成功した場合、Rollbackはノーオペレーションとなり、Commitが失敗した場合はトランザクションが自動的にロールバックされます。これにより、原子性が確保されます。MaxOpenConns は同時接続数の 2~3 倍に、MaxIdleConns を MaxOpenConns/2 に設定することをお勧めします。また、データベースミドルウェアによって接続が切断されないように、SetConnMaxLifetime を 5 分に設定してください。database/sqlインターフェースを通じて操作されます。違いは以下の通りです:(1) プレースホルダー:SQLiteは?、MySQLは?、PostgreSQLは$1を使用します; (2) SQL構文の違い(自動インクリメントされる主キーの構文など); (3) SQLiteの並行処理性能はMySQLほど良くありません。rows.Next() をすべて処理した後でも、rows.Close() を呼び出す必要がありますか?rowsはデータベース接続を保持したままです。rows.Close()は接続をプールに解放します。rowsが作成された直後にdefer rows.Close()を使用してこれを登録することで、どのような状況でもリソースが確実に解放されるようにしてください。db.SetMaxOpenConnsとdb.SetMaxIdleConnsを設定し、接続プールがボトルネックにならないようにします; (2) データベースに組み込まれているスロークエリログを使用する; (3) EXPLAIN ANALYZE を使用してクエリプランを分析する; (4) すべてのクエリの実行時間を記録するために、SQL ロギングミドルウェアの導入を検討する。📖 まとめ
sql.Openが接続を確立(遅延読み込み) +db.Pingが接続状態を確認- 3種類のクエリ:
db.Exec、db.Query、およびdb.QueryRow Prepare: プリコンパイルによるSQLインジェクションの防止- 取引:
Begin→Commit/Rollback defer tx.Rollback()セーフモード- 接続プール:
SetMaxOpenConns/SetMaxIdleConns/SetConnMaxLifetime - SQLiteでは
?、MySQLでは?、PostgreSQLでは$1が使用されます
📝 練習問題
-
基本問題(難易度 ⭐):SQLiteデータベースを作成し、
tasksというテーブル(id、title、done、created_at)を作成してください。InsertTask、ListTasks(日付順)、MarkDone(idによる更新)の3つの関数を実装してください。 -
上級問題(難易度 ⭐⭐):ブログシステムのデータベース層を実装してください。要件:(1)
postsテーブル (id,title,content,author_id,created_at); (2) ページネーション付きクエリ (LIMIT/OFFSET) をサポートすること;(3) インジェクション攻撃を防ぐため、挿入時にはプリペアードステートメントを使用すること;(4) トランザクション:投稿が公開された際に、著者の投稿数を更新すること;(5)-raceを使用して並行処理の安全性を検証すること。 -
課題(難易度:⭐⭐⭐):在庫管理システム向けの並行処理に安全な操作を実装してください。要件:(1) 100個のgoroutineが同時に注文を行う(それぞれが異なる商品の在庫を減少させる); (2) 過剰販売を防ぐために、database/sqlのトランザクションとアプリケーション層のミューテックスを使用すること;(3) すべてのSQL文をプリコンパイルすること;(4) 接続プールを適切に設定すること;(5) 成功した注文と失敗した注文の数をカウントし、最終的な在庫数を算出すること。