golang-database について
Goバックエンドエンジニアやコーディングエージェント向けのデータベース開発スキルです。明示的なSQLと安全なデータアクセスを重視し、パラメーター化クエリ、コンテキスト、トランザクション、接続プール、エラー処理、マイグレーションを扱います。ORMとAI生成のスキーマは避ける方針です。
主な機能
- 01パラメーター化クエリとSQLインジェクション対策
- 02コンテキスト伝播と行リソースの処理
- 03トランザクション、分離レベル、ロック
- 04接続プール設定とバッチ処理
- 05エラー処理とNULL可能列のスキャン
- 06外部マイグレーションツールとスキーマレビュー
活用シーン
- 01Goのリポジトリ層クエリの作成やレビュー
- 02複数文のデータベース更新の安全な処理
- 03接続プールの設定とデータベースの一括操作
- 04クエリエラーや該当行なしのケースの診断
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ samber/ cc-skills-golang --skill golang-databaseインストール後、Claude Code で/golang-databaseを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/evals.jsonreferences/performance.mdscanning.mdtesting.mdtransactions.md詳細
ペルソナ: 安全で、明示的かつ観測可能なデータベースコードを書く Go バックエンドエンジニアです。SQL を第一級の言語として扱い、ORM や魔法のような仕組みは使いません。また、データ整合性の問題はアプリケーションの深部ではなく、境界で検出します。
モード:
- 書き込みモード — 新しいリポジトリ関数、クエリヘルパー、トランザクションラッパーを生成する場合:スキルの手順に従ってください。新しいコードを生成する前に、バックグラウンドエージェントを起動して、コードベース内の既存のクエリパターンと命名規則を grep で検索してください。
- レビュー/デバッグモード — 既存のデータベースコードを監査またはデバッグする場合:ビジネスロジックを読むのと並行して、サブエージェントを使い、
rows.Close()の漏れ、パラメーター化されていないクエリ、コンテキスト伝播の漏れ、エラーチェックの欠落をスキャンしてください。
コミュニティのデフォルト。
samber/cc-skills-golang@golang-databaseスキルを明示的に上書きする会社のスキルがある場合は、そちらが優先されます。
Go データベースのベストプラクティス
Go の database/sql は、データベースアクセスの確かな基盤を提供します。使いやすさを高めるために、その上に sqlx または pgx を使用してください。ORM は決して使わないでください。
sqlx または pgx を使用する場合は、最新の API シグネチャとコード例について、ライブラリの公式ドキュメントを参照してください。
ベストプラクティスの概要
- ORM ではなく sqlx または pgx を使用する — ORM は SQL を隠し、予測不能なクエリを生成するため、デバッグを困難にします
- クエリには必ずパラメーター化されたプレースホルダーを使用してください。ユーザー入力を SQL 文字列に連結してはいけません
- すべてのデータベース操作にコンテキストを渡してください。
*Contextのメソッド(QueryContext、ExecContext、GetContext)を使用してください sql.ErrNoRowsは必ず明示的に処理してください。errors.Isを使って、「見つからない」状態と実際のエラーを区別してください- 反復処理後は必ず Rows を閉じてください。
QueryContextの呼び出し直後にdefer rows.Close()を指定してください - 行を返さないステートメントに
db.Queryを使ってはいけません。Queryは*Rowsを返し、これを閉じる必要があります。閉じ忘れると、コネクションが接続プールに返却されず、リークします。代わりにdb.Execを使用してください - 複数のステートメントを実行する操作にはトランザクションを使用する — 関連する書き込みを
BeginTxx/Commitで囲んでください - 変更する予定のデータを読み取る場合は
SELECT ... FOR UPDATEを使用する — 競合状態を防止します - デフォルトの READ COMMITTED では不十分な場合は、カスタムの分離レベルを設定してください(例:金融処理では serializable)
- NULL を許容する列は、ポインターフィールド(
*string、*int)またはsql.NullXxx型で扱ってください - 接続プールは必ず設定してください —
SetMaxOpenConns、SetMaxIdleConns、SetConnMaxLifetime、SetConnMaxIdleTime - マイグレーションには外部ツールを使用する — golang-migrate または Flyway を使用し、手書きや AI 生成のマイグレーション SQL は決して使わないでください
- 適切なサイズでバッチ処理する — 1 行ずつではなく(ラウンドトリップが多すぎます)、一度に数百万行でもなく(ロックとメモリーの問題が生じます)
- データベーススキーマを作成または変更してはいけません — テスト用データでは正しく見えるスキーマでも、実際の本番負荷ではホットスポット、ロック競合、インデックス不足を引き起こす可能性があります。スキーマ設計には、AI には把握できないデータ量、アクセスパターン、本番環境の制約を理解する必要があります
- 隠れた SQL 機能を避ける — アプリケーションコードでトリガー、ビュー、マテリアライズドビュー、ストアドプロシージャ、行レベルセキュリティに依存しないでください
ライブラリの選択
| ライブラリ | 最適な用途 | 構造体のスキャン | PostgreSQL 固有 |
|---|---|---|---|
database/sql | 移植性、最小限の依存関係 | 手動の Scan | いいえ |
sqlx | 複数データベースのプロジェクト | StructScan | いいえ |
pgx | PostgreSQL(30-50% 高速) | pgx.RowToStructByName | はい(COPY、LISTEN、配列) |
| GORM/ent | 避ける | 魔法のような仕組み | 抽象化される |
ORM を使わない理由:
- 予測不能なクエリ生成 — コード上では見えない N+1 問題が発生します
- 魔法のようなフックやコールバック(BeforeCreate、AfterUpdate)によってデバッグが困難になります
- スキーママイグレーションがアプリケーションコードと結合します
- ORM の API を学ぶほうが SQL を学ぶより難しく、抽象化にも漏れがあります
パラメーター化クエリ
// ✗ VERY BAD — SQL injection vulnerability
query := fmt.Sprintf("SELECT * FROM users WHERE email = '%s'", email)
// ✓ Good — parameterized (PostgreSQL)
var user User
err := db.GetContext(ctx, &user, "SELECT id, name, email FROM users WHERE email = $1", email)
// ✓ Good — parameterized (MySQL)
err := db.GetContext(ctx, &user, "SELECT id, name, email FROM users WHERE email = ?", email)
動的な IN 句
query, args, err := sqlx.In("SELECT * FROM users WHERE id IN (?)", ids)
if err != nil {
return fmt.Errorf("building IN clause: %w", err)
}
query = db.Rebind(query) // adjust placeholders for your driver
err = db.SelectContext(ctx, &users, query, args...)
動的なカラム名
ユーザー入力からカラム名を絶対に補間しないでください。許可リストを使用してください:
allowed := map[string]bool{"name": true, "email": true, "created_at": true}
if !allowed[sortCol] {
return fmt.Errorf("invalid sort column: %s", sortCol)
}
query := fmt.Sprintf("SELECT id, name, email FROM users ORDER BY %s", sortCol)
その他のインジェクション防止パターンについては、samber/cc-skills-golang@golang-security スキルを参照してください。
構造体スキャンと NULL 許容カラム
sqlx には db:"column_name" タグを、pgx には pgx.RowToStructByName を伴う pgx.CollectRows を使用してください。NULL 許容カラムはポインタフィールド(*string、*time.Time)で処理してください — これらはスキャンと JSON マーシャリングの両方で問題なく動作します。すべてのアプローチの例については、スキャンリファレンス を参照してください。
エラーハンドリング
func GetUser(id string) (*User, error) {
var user User
err := db.GetContext(ctx, &user, "SELECT id, name FROM users WHERE id = $1", id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrUserNotFound // translate to domain error
}
return nil, fmt.Errorf("querying user %s: %w", id, err)
}
return &user, nil
}
または:
func GetUser(id string) (u *User, exists bool, err error) {
var user User
err := db.GetContext(ctx, &user, "SELECT id, name FROM users WHERE id = $1", id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, false, nil // "no user" is not a technical error, but a domain error
}
return nil, false, fmt.Errorf("querying user %s: %w", id, err)
}
return &user, true, nil
}
必ず rows をクローズする
rows, err := db.QueryContext(ctx, "SELECT id, name FROM users")
if err != nil {
return fmt.Errorf("querying users: %w", err)
}
defer rows.Close() // prevents connection leaks
for rows.Next() {
// ...
}
if err := rows.Err(); err != nil { // always check after iteration
return fmt.Errorf("iterating users: %w", err)
}
一般的なデータベースエラーパターン
| エラー | 検出方法 | 対応 |
|---|---|---|
| 行が見つからない | errors.Is(err, sql.ErrNoRows) | ドメインエラーを返す |
| 一意制約違反 | ドライバー固有のエラーコードを確認する | 競合エラーを返す |
| 接続拒否 | db.PingContext で err != nil | 早期に失敗させ、ログを出力し、バックオフを伴ってリトライする |
| シリアライゼーション失敗 | PostgreSQL エラーコード 40001 | トランザクション全体をリトライする |
| コンテキストキャンセル | errors.Is(err, context.Canceled) | 処理を停止し、伝播させる |
コンテキストの伝播
デッドラインとキャンセルを伝播させるために、常に *Context メソッドのバリアントを使用してください:
// ✗ Bad — no context, query runs until completion even if client disconnects
db.Query("SELECT ...")
// ✓ Good — respects context cancellation and timeouts
db.QueryContext(ctx, "SELECT ...")
コンテキストパターンの詳細については、samber/cc-skills-golang@golang-context スキルを参照してください。
トランザクション、分離レベル、ロック
トランザクションパターン、分離レベル、SELECT FOR UPDATE、ロックのバリアントについては、トランザクション を参照してください。
コネクションプール
db.SetMaxOpenConns(25) // limit total connections
db.SetMaxIdleConns(10) // keep warm connections ready
db.SetConnMaxLifetime(5 * time.Minute) // recycle stale connections
db.SetConnMaxIdleTime(1 * time.Minute) // close idle connections faster
サイジングのガイダンスと計算式については、データベースパフォーマンス を参照してください。
マイグレーション
外部のマイグレーションツールを使用してください。スキーマ変更には、データ量、既存のインデックス、外部キー、本番環境の制約を理解した上での人によるレビューが必要です。
推奨ツール:
- golang-migrate — コマンドライン + Go ライブラリ、すべての主要データベースをサポート
- Flyway — JVM ベース、エンタープライズ環境で広く使用されている
- Atlas — モダンな宣言的スキーマ管理
マイグレーション SQL は人によって記述およびレビューされ、ソース管理でバージョン管理され、CI/CD パイプラインを通じて適用されるべきです。
隠れた SQL 機能を避ける
アプリケーションコードでトリガー、ビュー、マテリアライズドビュー、ストアドプロシージャ、行レベルセキュリティに依存しないでください — これらは目に見えない副作用を生み出し、デバッグを不可能にします。SQL は明示的に保ち、テストとバージョン管理が可能な Go のコード内で可視化してください。
スキーマ作成
このスキルはスキーマ作成を対象としていません。 AI が生成したスキーマは、しばしば微妙に誤っています — インデックスの欠落、不正確なカラム型、不適切な正規化、制約の欠落などです。スキーマ設計には、データ量、アクセスパターン、クエリプロファイル、ビジネス上の制約の理解が必要です。専用のデータベースツールと人によるレビューを使用してください。
詳細解説
- トランザクション — トランザクション境界、分離レベル、デッドロック防止、
SELECT FOR UPDATE - データベースコードのテスト — モック接続、コンテナを用いた統合テスト、フィクスチャ、スキーマのセットアップ/ティアダウン
- データベースパフォーマンス — コネクションプールのサイジング、バッチ処理、インデックス戦略、クエリ最適化
- 構造体スキャン — 構造体タグ、NULL 許容カラムの処理、JSON マーシャリングパターン
相互参照
- → SQL インジェクション防止パターンについては、
samber/cc-skills-golang@golang-securityスキルを参照してください - → データベース操作へのコンテキスト伝播については、
samber/cc-skills-golang@golang-contextスキルを参照してください - → データベースエラーのラッピングパターンについては、
samber/cc-skills-golang@golang-error-handlingスキルを参照してください - → データベース統合テストパターンについては、
samber/cc-skills-golang@golang-testingスキルを参照してください