golang-database

oleh samber

Panduan komprehensif untuk akses database Go — kueri terparameter, pemindaian struct, kolom NULLable, transaksi, tingkat isolasi, SELECT FOR UPDATE, kumpulan koneksi, pemrosesan batch, propagasi konteks, dan alat migrasi. Gunakan saat menulis, meninjau, atau men-debug kode Golang yang berinteraksi dengan PostgreSQL, MariaDB, MySQL, atau SQLite; untuk pengujian database; atau untuk pertanyaan tentang database/sql, sqlx, atau pgx. TIDAK menghasilkan skema database atau SQL migrasi.

npx skills add https://github.com/samber/cc-skills-golang --skill golang-database

Persona: You are a Go backend engineer who writes safe, explicit, and observable database code. You treat SQL as a first-class language — no ORMs, no magic — and you catch data integrity issues at the boundary, not deep in the application.

Modes:

  • Write mode — generating new repository functions, query helpers, or transaction wrappers: follow the skill's sequential instructions; launch a background agent to grep for existing query patterns and naming conventions in the codebase before generating new code.
  • Review/debug mode — auditing or debugging existing database code: use a sub-agent to scan for missing rows.Close(), un-parameterized queries, missing context propagation, and absent error checks in parallel with reading the business logic.

Community default. A company skill that explicitly supersedes samber/cc-skills-golang@golang-database skill takes precedence.

Go Database Best Practices

Go's database/sql provides a solid foundation for database access. Use sqlx or pgx on top of it for ergonomics — never an ORM.

When using sqlx or pgx, refer to the library's official documentation and code examples for current API signatures.

Best Practices Summary

  1. Use sqlx or pgx, not ORMs — ORMs hide SQL, generate unpredictable queries, and make debugging harder
  2. Queries MUST use parameterized placeholders — NEVER concatenate user input into SQL strings
  3. Context MUST be passed to all database operations — use *Context method variants (QueryContext, ExecContext, GetContext)
  4. sql.ErrNoRows MUST be handled explicitly — distinguish "not found" from real errors using errors.Is
  5. Rows MUST be closed after iteration — defer rows.Close() immediately after QueryContext calls
  6. NEVER use db.Query for statements that don't return rows — Query returns *Rows which must be closed; if you forget, the connection leaks back to the pool. Use db.Exec instead
  7. Use transactions for multi-statement operations — wrap related writes in BeginTxx/Commit
  8. Use SELECT ... FOR UPDATE when reading data you intend to modify — prevents race conditions
  9. Set custom isolation levels when default READ COMMITTED is insufficient (e.g., serializable for financial operations)
  10. Handle NULLable columns with pointer fields (*string, *int) or sql.NullXxx types
  11. Connection pool MUST be configured — SetMaxOpenConns, SetMaxIdleConns, SetConnMaxLifetime, SetConnMaxIdleTime
  12. Use external tools for migrations — golang-migrate or Flyway, never hand-rolled or AI-generated migration SQL
  13. Batch operations in reasonable sizes — not row-by-row (too many round trips), not millions at once (locks and memory)
  14. Never create or modify database schemas — a schema that looks correct on toy data can create hotspots, lock contention, or missing indexes under real production load. Schema design requires understanding of data volumes, access patterns, and production constraints that AI does not have
  15. Avoid hidden SQL features — do not rely on triggers, views, materialized views, stored procedures, or row-level security in application code

Library Choice

LibraryBest forStruct scanningPostgreSQL-specific
database/sqlPortability, minimal depsManual ScanNo
sqlxMulti-database projectsStructScanNo
pgxPostgreSQL (30-50% faster)pgx.RowToStructByNameYes (COPY, LISTEN, arrays)
GORM/entAvoidMagicAbstracted away

Why NOT ORMs:

  • Unpredictable query generation — N+1 problems you cannot see in code
  • Magic hooks and callbacks (BeforeCreate, AfterUpdate) make debugging harder
  • Schema migrations coupled to application code
  • Learning the ORM API is harder than learning SQL, and the abstraction leaks

Parameterized Queries

// ✗ 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)

Dynamic IN clauses

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...)

Dynamic column names

Never interpolate column names from user input. Use an allowlist:

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)

For more injection prevention patterns, see the samber/cc-skills-golang@golang-security skill.

Struct Scanning and NULLable Columns

Use db:"column_name" tags for sqlx, pgx.CollectRows with pgx.RowToStructByName for pgx. Handle NULLable columns with pointer fields (*string, *time.Time) — they work cleanly with both scanning and JSON marshaling. See Scanning Reference for examples of all approaches.

Error Handling

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
}

or:

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
}

Always close 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)
}

Common database error patterns

ErrorHow to detectAction
Row not founderrors.Is(err, sql.ErrNoRows)Return domain error
Unique constraintCheck driver-specific error codeReturn conflict error
Connection refusederr != nil on db.PingContextFail fast, log, retry with backoff
Serialization failurePostgreSQL error code 40001Retry the entire transaction
Context cancelederrors.Is(err, context.Canceled)Stop processing, propagate

Context Propagation

Always use the *Context method variants to propagate deadlines and cancellation:

// ✗ Bad — no context, query runs until completion even if client disconnects
db.Query("SELECT ...")

// ✓ Good — respects context cancellation and timeouts
db.QueryContext(ctx, "SELECT ...")

For context patterns in depth, see the samber/cc-skills-golang@golang-context skill.

Transactions, Isolation Levels, and Locking

For transaction patterns, isolation levels, SELECT FOR UPDATE, and locking variants, see Transactions.

Connection Pool

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

For sizing guidance and formulas, see Database Performance.

Migrations

Use an external migration tool. Schema changes require human review with understanding of data volumes, existing indexes, foreign keys, and production constraints.

Recommended tools:

  • golang-migrate — CLI + Go library, supports all major databases
  • Flyway — JVM-based, widely used in enterprise environments
  • Atlas — modern, declarative schema management

Migration SQL should be written and reviewed by humans, versioned in source control, and applied through CI/CD pipelines.

Avoid Hidden SQL Features

Do not rely on triggers, views, materialized views, stored procedures, or row-level security in application code — they create invisible side effects and make debugging impossible. Keep SQL explicit and visible in Go where it can be tested and version-controlled.

Schema Creation

This skill does NOT cover schema creation. AI-generated schemas are often subtly wrong — missing indexes, incorrect column types, bad normalization, or missing constraints. Schema design requires understanding data volumes, access patterns, query profiles, and business constraints. Use dedicated database tooling and human review.

Deep Dives

  • Transactions — Transaction boundaries, isolation levels, deadlock prevention, SELECT FOR UPDATE
  • Testing Database Code — Mock connections, integration tests with containers, fixtures, schema setup/teardown
  • Database Performance — Connection pool sizing, batch processing, indexing strategy, query optimization
  • Struct Scanning — Struct tags, NULLable column handling, JSON marshaling patterns

Cross-References

  • → See samber/cc-skills-golang@golang-security skill for SQL injection prevention patterns
  • → See samber/cc-skills-golang@golang-context skill for context propagation to database operations
  • → See samber/cc-skills-golang@golang-error-handling skill for database error wrapping patterns
  • → See samber/cc-skills-golang@golang-testing skill for database integration test patterns

References

Lebih banyak skill dari samber

golang-code-style
samber
Golang code style conventions — line length and breaking, variable declarations, control flow clarity, when comments help vs hurt. Use when writing or reviewing Go code, asking about style or clarity, or establishing project coding standards. Not for naming conventions (→ See `samber/cc-skills-golang@golang-naming` skill), linter configuration (→ See `samber/cc-skills-golang@golang-lint` skill), or doc comments (→ See `samber/cc-skills-golang@golang-documentation` skill).
developmentcode-review
golang-testing
samber
We need to translate the given text from English to Bahasa Indonesia. The instruction says to preserve product names, protocol names, URLs, numbers, technical terms. The name "golang-testing" is not in the text, so we don't include it. The text contains terms like "Golang", "testify", "goleak", "CI", "API", "samber/cc-skills-golang@golang-stretchr-testify" which should be preserved as is. Also numbers and technical terms like "table-driven tests", "parallel tests", "fuzzing", "fixtures", "goroutine leak detection", "snapshot testing", "code coverage", "integration tests", "idiomatic test naming" should be translated appropriately but keep the technical essence. The instruction says "Do not include the name unless it appears in the source text." The name "golang-testing" does not appear, so we ignore. Also no labels like "description". Just translate the text. Let's translate: "Production-ready Golang tests — table-driven tests, testify
developmenttestingcode-review
golang-design-patterns
samber
Pola desain Golang yang idiomatis — opsi fungsional, konstruktor, aliran dan kaskade kesalahan, manajemen sumber daya dan siklus hidup, penghentian secara bertahap, ketahanan, arsitektur, injeksi dependensi, penanganan data, streaming, dan lainnya. Terapkan saat secara eksplisit memilih antara pola arsitektural, mengimplementasikan opsi fungsional, mendesain API konstruktor, menyiapkan penghentian secara bertahap, menerapkan pola ketahanan, atau menanyakan pola Go idiomatis mana yang cocok untuk masalah tertentu.
developmentdesigncode-review
golang-error-handling
samber
We need to translate the given text from English to Bahasa Indonesia, preserving the name "golang-error-handling" but only if it appears in the source text. The source text does not contain the name "golang-error-handling" explicitly; it only appears in the instruction as the name to preserve. The instruction says: "Do not include the name unless it appears in the source text." So we should not add it. Also preserve product names, protocol names, URLs, numbers, technical terms. The text includes terms like "Golang", "%w", "errors.Is/As", "errors.Join", "slog", "HTTP", "samber/oops", "samber/cc-skills-golang@golang-samber-oops". These should remain as is. Translate the rest idiomatically. The text: "Idiomatic Golang error handling — creation, wrapping with %w, errors.Is/As, errors.Join, custom error types, sentinel errors, panic/recover, the single handling rule, structured logging with slog, HTTP request logging middleware, and
developmentcode-review
golang-performance
samber
Pola dan metodologi optimasi performa Golang - jika X bottleneck, maka terapkan Y. Mencakup pengurangan alokasi, efisiensi CPU, tata letak memori, penyesuaian GC, pooling, caching, dan optimasi jalur panas. Gunakan saat profiling atau benchmark telah mengidentifikasi bottleneck dan Anda membutuhkan pola optimasi yang tepat untuk memperbaikinya. Juga gunakan saat melakukan tinjauan kode performa untuk menyarankan perbaikan atau benchmark yang dapat membantu mengidentifikasi peningkatan performa cepat. Bukan untuk metodologi pengukuran (→...
developmentcode-review
golang-security
samber
Praktik terbaik keamanan dan pencegahan kerentanan untuk Golang. Mencakup injeksi (SQL, command, XSS), kriptografi, keamanan sistem file, keamanan jaringan, cookie, manajemen rahasia, keamanan memori, dan pencatatan log. Terapkan saat menulis, meninjau, atau mengaudit kode Go untuk keamanan, atau saat mengerjakan kode berisiko yang melibatkan kripto, I/O, manajemen rahasia, penanganan input pengguna, atau autentikasi. Termasuk konfigurasi alat keamanan.
securitycode-reviewdevelopment
golang-lint
samber
Praktik terbaik linting dan konfigurasi golangci-lint untuk proyek Golang — menjalankan linter, mengonfigurasi .golangci.yml, menekan peringatan dengan direktif nolint, menafsirkan keluaran lint, dan memilih linter. Gunakan saat mengonfigurasi golangci-lint, bertanya tentang peringatan lint atau penekanan nolint, menyiapkan alat kualitas kode, atau memilih linter. Juga gunakan saat pengguna menyebutkan golangci-lint, go vet, staticcheck, atau revive.
developmentcode-reviewtesting
golang-troubleshooting
samber
We need to translate the given text from English to Bahasa Indonesia. The text is a description of a skill for troubleshooting Golang programs. We must preserve the name "golang-troubleshooting" if it appears, but it does not appear in the text. The text includes technical terms like "pprof", "Delve debugger", "GODEBUG", etc. We should keep those as is. Also preserve URLs? There is no URL. Numbers? None. Technical terms: "Golang", "pprof", "Delve debugger", "race detection", "GODEBUG tracing", "benchmarking". Also "test-driven debugging" might be translated as "debugging berbasis pengujian" or similar. The instruction says to preserve product names, protocol names, URLs, numbers, and technical terms. So "Golang" is a product name, keep as is. "pprof" is a tool, keep. "Delve debugger" is a product, keep. "GODEBUG" is an environment variable, keep.
developmenttesting