sqlc 是当前最稳的 go crud 代码生成方案,它将 sql 编译为强类型 go 函数,零反射、ide 可跳转、字段改名即报错,专注 sql 即契约,无需手写模板或使用 orm。

用 sqlc 生成 Go CRUD,比手写快且类型安全
直接结论:别自己写模板生成器,sqlc 是当前最稳的 Go CRUD 代码生成方案。它不生成 ORM,而是把 SQL 查询编译成强类型的 Go 函数,零运行时反射、无魔法字符串、IDE 可跳转、字段改名会直接报错。
常见错误是试图用 genny 或 text/template 手搓生成器——结果维护成本高、SQL 和 Go 类型脱节、JOIN 场景下结构体嵌套难处理。而 sqlc 让你专注写 SQL,它负责把 SELECT id, name FROM users 翻译成 func (q *Queries) GetUsers(ctx context.Context) ([]User, error)。
- 必须用 PostgreSQL 或 MySQL(SQLite 支持有限),且表要有明确主键和非空约束,否则生成的 struct 字段可能为指针,增加 nil 判断负担
-
sqlc.yaml中emit_json_tags: true要打开,否则 struct 无法被json.Marshal正常序列化 - 查询文件后缀必须是
.sql,且每个语句前需加注释-- name: GetUsers :one,冒号后是:one/:many/:exec,类型不对会导致编译失败
怎么让 sqlc 支持软删除和时间戳自动填充
原生 sqlc 不处理业务逻辑,但可通过 SQL 层约定实现软删除与时间字段管理,避免在 Go 层重复写 CreatedAt: time.Now()。
例如定义用户表时,用 deleted_at TIMESTAMPTZ DEFAULT NULL,然后所有查询加 WHERE deleted_at IS NULL;插入语句显式写 created_at = NOW(), updated_at = NOW()。这样生成的 Go 函数天然带过滤,无需额外 wrapper。
- 不要在 Go 层做
if u.DeletedAt != nil过滤——这会让同一张表的多个查询逻辑分散,也破坏了sqlc“SQL 即契约”的设计初衷 - 更新操作统一用
UPDATE users SET name = $2, updated_at = NOW() WHERE id = $1,确保updated_at永远由数据库控制 - 如果需要物理删除,单独写一个
-- name: HardDeleteUser :exec查询,和常规查询隔离,避免误用
sqlc 生成的代码怎么接入 Gin/Gin-Zap 日志链路
生成的 Queries 结构体本身不含 context 透传或日志能力,必须靠外层封装。直接把 *sql.DB 塞进 handler 是错的——它没带 trace ID、没日志上下文、出错时无法定位到具体 SQL。
正确做法是在调用生成函数前,用 ctx = logger.WithContext(ctx, zap.String("sql_op", "GetUsers")) 注入字段,再把 ctx 传给 q.GetUsers(ctx)。注意:所有生成函数第一个参数都是 context.Context,别漏传。
- 别用全局
log.Printf打印 SQL 参数——既不安全(可能泄露敏感数据),也无法关联请求生命周期 - 如果用了
pgxpool,记得在sqlc配置里设engine: "postgresql"且emit_db_sql: true,否则生成的函数不会接收pgx.Tx类型事务对象 - 对慢查询加
zap.Duration("sql_duration", time.Since(start)),放在 defer 里,不是放在生成代码内部
为什么不用 ent 或 gorm 自动生成 CRUD
它们不是“生成器”,而是运行时 ORM。ent 的 ent generate 看似生成代码,实则输出的是 builder 模式接口,最终执行仍依赖动态 SQL 构建;gorm 更是全程反射+字符串拼接,字段名写错只在运行时报错,IDE 无法跳转,单元测试难 mock。
sqlc 的核心优势在于「SQL 优先」:DDL 变了 → SQL 文件改了 → sqlc generate → 编译失败 → 你立刻知道哪块 Go 代码要同步改。这种反馈闭环是 ORM 给不了的。
真正容易被忽略的是迁移成本:已有项目加 sqlc,只需补全 SQL 查询文件,不用动数据库、不用改现有 DAO 层调用方式——只要把原来手写的 rows.Scan() 替换成生成的函数即可。很多团队卡在这一步,不是技术问题,是没想清楚“生成器”该生成什么:它生成的是确定性、可验证、可调试的胶水代码,不是抽象层。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











