技能创建器
skill-creator
创建、改进和评估自定义技能,把专业流程转化为可重复使用的 AI 能力。
go-database【数据与研究】Database patterns for Go services: database/sql, connection management, transactions, migrations, query builders, and ORM usage (sqlc, GORM, ent). Use when: "database access", "SQL query", "connection pool", "transactions", "database migration", "sqlc", "GORM", "ent", "prepared statement", "repository pattern". Not for: in-memory structures (go-data-…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Database access is where most Go services spend their complexity budget. Get connection management, transactions, and query patterns right.
Detailed reference material, loaded on demand:
references/query-patterns.md — full query/scan/rows patterns, null
handling, N+1 avoidance, connection-leak examples.references/tooling.md — repository pattern implementation, sqlc
annotated queries, migration tooling and rules.Read a reference file only when the summary below is not enough.
Configure the pool explicitly — the default is unbounded connections:
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
}
| Setting | Guideline |
|---|---|
MaxOpenConns | Match your DB's max connections / number of app instances |
MaxIdleConns | 40-50% of MaxOpenConns |
ConnMaxLifetime | 5-10 minutes (prevents stale connections behind load balancers) |
ConnMaxIdleTime | 1-2 minutes |
*Context variants
(QueryContext, QueryRowContext, ExecContext) so queries respect
cancellation and timeouts.defer rows.Close() immediately after the error check, and check
rows.Err() after the iteration loop.sql.ErrNoRows explicitly with errors.Is, mapping it to
a domain error like 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)
}
Multi-row iteration patterns: references/query-patterns.md.
Use a helper that guarantees rollback on error:
func WithTx(ctx context.Context, db *sql.DB, fn func(tx *sql.Tx) error) error {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return fmt.Errorf("begin tx: %w", err)
}
if err := fn(tx); err != nil {
if rbErr := tx.Rollback(); rbErr != nil {
return fmt.Errorf("rollback failed: %v (original: %w)", rbErr, err)
}
return err
}
if err := tx.Commit(); err != nil {
return fmt.Errorf("commit tx: %w", err)
}
return nil
}
Set isolation explicitly for critical operations:
sql.TxOptions{Isolation: sql.LevelSerializable}.
goose, golang-migrate, atlas), one
migration per change, forward-only in production, with down SQL, run
as a separate step — not at server startup.Implementations and examples: references/tooling.md.
sql.NullString/sql.NullInt64 or pointer
fields (*string, nil = SQL NULL). Scanning NULL into a plain string
errors at runtime.WHERE id = ANY($1)).return between Query and
defer rows.Close() leaks a connection from the pool.Worked examples of each pitfall: references/query-patterns.md.
MaxOpenConns, MaxIdleConns, lifetimes)QueryContext results have defer rows.Close() immediately after error checkrows.Err() checked after row iteration loopsql.ErrNoRows handled explicitly with errors.Is*Context variants)sql.NullString / sql.NullInt64 or pointer types