关于 go-database
go-database 是面向 Go 服务开发者的数据库实践技能,涵盖查询、事务、并发、性能、工具链与测试,并提供按需参考资料。
主要功能
- 01配置数据库连接池
- 02安全执行查询并处理结果
- 03事务与锁定模式
- 04SQL 工具链与迁移
- 05性能与测试指导
适用场景
- 01审查 Go 数据库访问代码
- 02构建事务与重试逻辑
- 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.mdperformance.mdquery-patterns.mdtesting.mdtooling.md详细介绍
Go 数据库模式
数据库访问往往是 Go 服务复杂度最高的部分。 正确处理连接管理、事务和查询模式。
社区默认规则。如果项目技能或 Agent 配置规则明确取代了
go-database,则以该规则为准。
详细参考资料,按需加载:
references/query-patterns.md— 完整的查询、扫描和行处理模式,空值 处理、避免 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
}
| 设置 | 指南 |
|---|---|
MaxOpenConns | 与数据库的最大连接数 / 应用实例数匹配 |
MaxIdleConns | MaxOpenConns 的 40-50% |
ConnMaxLifetime | 5-10 分钟(防止负载均衡器后的连接变陈旧) |
ConnMaxIdleTime | 1-2 分钟 |
2. 查询规则
- 仅使用参数化查询 — 将字符串拼接到 SQL 中会造成 注入漏洞,没有例外。
- 始终传入上下文 — 使用
*Context变体 (QueryContext、QueryRowContext、ExecContext),确保查询遵循 取消和超时设置。 - 在检查错误后立即执行
defer rows.Close(),并在迭代 循环后检查rows.Err()。 - 使用
errors.Is显式处理sql.ErrNoRows,将其映射为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 内部发生的 panic,否则事务会一直保持打开状态,且其连接会一直留在连接池之外,直到上下文结束。errors.Join 可让两个错误都能被检查。
对关键操作显式设置隔离级别:
sql.TxOptions{Isolation: sql.LevelSerializable}。
4. 并发控制和错误
- 在一个事务中先读后写,使用
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),每次变更对应一个迁移,在生产环境中只向前迁移,并提供downSQL;作为独立步骤运行,而非在服务器启动时运行。
实现和示例:references/tooling.md。
6. 常见陷阱
- **可空列:**使用
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——绝不要每行单独往返一次。 - 使用键集游标分页(
WHERE (created_at, id) < ($1, $2)),不要使用OFFSET,因为它会扫描被跳过的行,并且在插入数据时导致结果偏移。 - 导出
db.Stats():WaitCount持续上升意味着瓶颈在连接池或占用连接的其他因素。 sql.Null[T](Go 1.22+)可替代各类型的NullString系列。
示例:references/performance.md。
8. 测试
服务逻辑的测试中,应伪造由服务消费的仓库接口;对真实数据库中的 SQL 进行测试,并在容器中运行数据库(使用 testcontainers,通过
//go:build integration 控制),同时运行生产环境迁移。通过每个测试使用一个事务、模板数据库或清空数据来隔离测试。
示例:references/testing.md。
验证清单
- 使用明确的限制配置连接池(
MaxOpenConns、MaxIdleConns、生命周期) - 所有查询都使用参数化占位符,绝不拼接字符串
- 所有
QueryContext结果在错误检查后立即执行defer rows.Close() - 在行迭代循环结束后检查
rows.Err() - 使用
errors.Is显式处理sql.ErrNoRows - 事务使用能确保出错时回滚的辅助函数
- 将上下文传递给所有数据库调用(
*Context变体) - 可空列使用
sql.NullString/sql.NullInt64或指针类型 - 不采用 N+1 查询模式——使用 JOIN 或批量查询
- 迁移有版本号、可逆转,并与应用启动分开运行
- 大型表不使用
OFFSET分页;批量写入需分批处理 - 对照真实数据库测试 SQL,而不是模拟查询字符串