mcpField
G
go-database

go-database

github.com/eduardo-slデータ・データベース83165 回インストール2 週間前に更新
リポジトリ

go-database について

go-databaseは、Goサービスのデータベースアクセスを設計する開発者向けのスキルです。クエリ、トランザクション、並行処理、性能、ツール、テストの指針をオンデマンドの資料とともに提供します。

主な機能

  1. 01接続プールの設定
  2. 02安全なクエリと行処理
  3. 03トランザクションとロックのパターン
  4. 04SQLツールとマイグレーション
  5. 05性能とテストの指針

活用シーン

  1. 01GoのDBアクセスコードのレビュー
  2. 02トランザクションと再試行ロジックの構築
  3. 03クエリや接続の問題の予防

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/eduardo-sl/go-agent-skills --skill go-database

インストール後、Claude Code で/go-databaseを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
locking-and-retries.md
performance.md
query-patterns.md
testing.md
tooling.md

詳細

Go データベースのパターン

Go サービスの複雑さの大半は、データベースアクセスに費やされます。 コネクション管理、トランザクション、クエリのパターンを適切に扱いましょう。

コミュニティの標準です。プロジェクトのスキルまたはエージェント設定ルールが go-database を明示的に置き換える場合は、そちらが優先されます。

詳細なリファレンス資料は、必要に応じて読み込んでください。

  • references/query-patterns.md — クエリ、スキャン、行の完全なパターン、NULL の 処理、N+1 の回避、コネクションリークの例。
  • references/tooling.md — リポジトリパターンの実装、sqlc の注釈付きクエリ、 マイグレーションツールとルール。
  • references/locking-and-retries.md — FOR UPDATE/SKIP LOCKED、 楽観的バージョン管理、40001/40P01 の再試行、制約エラー。
  • references/performance.md — COPY とバッチ挿入、キ​​ーセットページネーション、 プールの統計情報、sql.Null[T]。
  • references/testing.md — フェイクと実データベースの選択、 testcontainers のセットアップ、分離戦略。

以下の概要だけでは不十分な場合に限り、リファレンスファイルを読んでください。

1. コネクション管理

プールを明示的に設定してください。デフォルトではコネクション数に上限がありません。

func OpenDB(dsn string) (*sql.DB, error) {
    db, err := sql.Open("postgres", dsn)
    if err != nil {
        return nil, fmt.Errorf("open db: %w", err)
    }

    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(10)
    db.SetConnMaxLifetime(5 * time.Minute)
    db.SetConnMaxIdleTime(1 * time.Minute)

    if err := db.PingContext(context.Background()); err != nil {
        return nil, fmt.Errorf("ping db: %w", err)
    }

    return db, nil
}
設定ガイドライン
MaxOpenConnsDB の最大コネクション数/アプリインスタンス数に合わせます
MaxIdleConnsMaxOpenConns の 40~50%
ConnMaxLifetime5~10 分(ロードバランサーの背後で古くなったコネクションを防ぎます)
ConnMaxIdleTime1~2 分

2. クエリのルール

  1. パラメーター化クエリのみを使用します — SQL 文字列の連結は例外なく インジェクションの脆弱性につながります。
  2. 必ずコンテキストを渡します — *Context バリアント (QueryContext、QueryRowContext、ExecContext)を使用して、クエリがキャンセルと タイムアウトに従うようにします。
  3. エラーチェックの直後に defer rows.Close() を呼び出し、反復ループの後に rows.Err() を確認します。
  4. sql.ErrNoRows を errors.Is で明示的に処理し、ErrUserNotFound のような ドメインエラーにマッピングします。
var user User
err := db.QueryRowContext(ctx,
    "SELECT id, name, email FROM users WHERE id = $1", id,
).Scan(&user.ID, &user.Name, &user.Email)

if errors.Is(err, sql.ErrNoRows) {
    return nil, ErrUserNotFound
}
if err != nil {
    return nil, fmt.Errorf("get user %s: %w", id, err)
}

複数行の反復パターン:references/query-patterns.md。

3. トランザクション

エラー時にロールバックを保証するヘルパーを使用します。

func WithTx(ctx context.Context, db *sql.DB, fn func(tx *sql.Tx) error) (err error) {
    tx, err := db.BeginTx(ctx, nil)
    if err != nil {
        return fmt.Errorf("begin tx: %w", err)
    }
    defer func() {
        if p := recover(); p != nil {
            _ = tx.Rollback() // release the connection, then keep panicking
            panic(p)
        }
        if err != nil {
            if rbErr := tx.Rollback(); rbErr != nil && !errors.Is(rbErr, sql.ErrTxDone) {
                err = errors.Join(err, fmt.Errorf("rollback: %w", rbErr))
            }
        }
    }()

    if err := fn(tx); err != nil {
        return err
    }
    if err := tx.Commit(); err != nil {
        return fmt.Errorf("commit tx: %w", err)
    }
    return nil
}

遅延ロールバックは fn 内でのパニックも処理します。これがない場合、コンテキストが 終了するまでトランザクションが開いたままとなり、コネクションもプールに戻りません。 errors.Join を使うと、両方のエラーを検査できます。

重要な操作では分離レベルを明示的に設定します。 sql.TxOptions{Isolation: sql.LevelSerializable}。

4. 並行制御とエラー

  • 1 つのトランザクション内で読み取り後に書き込みを行う場合は SELECT ... FOR UPDATE を使用します。ジョブ キューでは FOR UPDATE SKIP LOCKED で行を取得します。デッドロックを避けるため、行を 一貫した順序でロックします。
  • ユーザー向けの編集には楽観的並行制御を使用します。WHERE 句に version 列を含め、 影響を受けた行が 0 件なら競合とします。
  • 40001(シリアライゼーション失敗)と 40P01(デッドロック)は再試行可能です。 ジッターを加えたバックオフと上限を設け、トランザクション関数全体を再試行します。 関数はトランザクション外に副作用を持たないようにしてください。
  • 制約違反をドメインエラーにマッピングします(23505 の一意制約違反 → ErrEmailTaken → 409)。一意性の保証はデータベースに任せます。事前に確認してから 挿入すると競合が発生します。

コード:references/locking-and-retries.md。

5. 構成とツール

  • リポジトリパターン: インターフェースは利用側で定義し、 具体的なデータベースアクセスで実装します。この境界でドライバーのエラーを ドメインエラーにマッピングします。
  • sqlc: 生 SQL を使うプロジェクトでは優先して使用します。注釈付き SQL から 型安全な Go コードを生成し、ビルド時にクエリとスキーマの不一致を検出します。
  • マイグレーション: ツール(goose、golang-migrate、atlas)を使い、変更ごとに 1 つのマイグレーションを作成します。本番環境では前進のみとし、down SQL を用意して、 サーバー起動時ではなく別の手順として実行します。

実装と例:references/tooling.md。

6. よくある落とし穴

  • NULL 列: sql.NullString/sql.NullInt64 またはポインターフィールド(*string、nil は SQL NULL)を使用します。NULL を通常の文字列にスキャンすると、実行時エラーになります。
  • N+1 クエリ: クエリ結果をループ処理する中でクエリを実行することです。JOIN またはバッチクエリ(WHERE id = ANY($1))に置き換えます。
  • 接続リーク: Query と defer rows.Close() の間に早期 return があると、プールから接続がリークします。行を返さない文に対する db.Query も同様です。その場合は ExecContext を使用します。

各落とし穴の具体例:references/query-patterns.md。

7. パフォーマンス

  • 一括書き込みには、pgx の CopyFrom、または数百行単位の複数行 INSERT を使用します。行ごとにラウンドトリップする方法は避けてください。
  • OFFSET ではなく、キーセットカーソル(WHERE (created_at, id) < ($1, $2))を使用してページネーションします。OFFSET はスキップした行をスキャンし、挿入があると位置がずれます。
  • db.Stats() をエクスポートします。WaitCount の増加は、プールまたは接続を保持している何かがボトルネックであることを意味します。
  • sql.Null[T](Go 1.22 以降)は、型ごとに用意された NullString 系の型に代わるものです。

例:references/performance.md。

8. テスト

サービスのロジックでは、利用側のリポジトリーインターフェースをフェイクにします。SQL はコンテナー内の実データベースに対してテストし(//go:build integration の背後で testcontainers を使用)、本番用のマイグレーションを実行します。テストは、テストごとのトランザクション、テンプレートデータベース、またはテーブルの切り詰めによって分離します。例:references/testing.md。

確認チェックリスト

  1. 接続プールに明示的な上限(MaxOpenConns、MaxIdleConns、ライフタイム)を設定している
  2. すべてのクエリでパラメーター化プレースホルダーを使用し、文字列連結は使用していない
  3. すべての QueryContext の結果で、エラーチェック直後に defer rows.Close() を実行している
  4. 行の反復処理ループの後に rows.Err() を確認している
  5. sql.ErrNoRows を errors.Is で明示的に処理している
  6. トランザクションでは、エラー時のロールバックを保証するヘルパーを使用している
  7. すべてのデータベース呼び出しにコンテキストを引き継いでいる(*Context バリアント)
  8. NULL 許容列には sql.NullString/sql.NullInt64 またはポインター型を使用している
  9. N+1 クエリのパターンがない。JOIN またはバッチクエリを使用している
  10. マイグレーションはバージョン管理され、取り消し可能で、アプリの起動とは別に実行される
  11. 大規模なテーブルで OFFSET ページネーションを使用していない。一括書き込みはバッチ処理している
  12. SQL はモックのクエリ文字列ではなく、実データベースに対してテストしている

コメント