微服务中应根据数据用途选择:纯内存结构用struct,需落库或跨服务共享则用带生命周期管理的GORM模型或sqlc生成模型;避免同一struct混用为DTO与数据库模型。

微服务里该用 struct 还是 GORM 模型?
取决于数据是否跨服务共享和变更频率。如果只是内部内存结构(比如树形组织架构、缓存中的层级关系),直接用 struct + slice 就够了;一旦要落库、要被其他服务查询、要支持软删除或审计字段,就得上带生命周期管理的模型层。
常见错误现象:把 internal/todo.go 里的纯业务 Todo struct 直接传给 PostgreSQL 驱动,结果时间字段类型错乱、零值被忽略、更新时漏掉非空约束。
- 纯内存建模(如货架→货箱树):用嵌套
struct+[]*Node,不带 ORM 标签,不依赖数据库驱动 - 需持久化的领域实体:定义独立模型(如
Task),加gorm:"column:name"或sqlc兼容标签,与 API DTO 分离 - 避免混用:不要让同一个
struct既当 HTTP 请求体又当数据库模型——字段语义和校验逻辑完全不同
PostgreSQL 连接池配置不生效的典型原因
sql.Open() 只初始化连接池,不建连;真正出问题往往在 db.SetMaxOpenConns() 调用太晚,或没调 db.Ping() 触发首次连接验证。
性能影响明显:默认 SetMaxOpenConns(0)(无上限),高并发下可能瞬间打爆 PostgreSQL 的 max_connections,报错 too many clients already。
立即学习“go语言免费学习笔记(深入)”;
- 必须在
sql.Open()后立即设置:db.SetMaxOpenConns(25)、db.SetMaxIdleConns(25)、db.SetConnMaxLifetime(5*time.Minute) - 务必调用
db.Ping(),否则连接池可能静默失败,直到第一个请求才暴露问题 - 监控不能只看
db.Stats().OpenConnections,还要比对InUse和Idle—— 如果InUse长期接近MaxOpenConns,说明事务没及时Close()或Commit()
GORM 分表逻辑写在 TableName() 里为什么总查错表?
因为 TableName() 是模型级静态方法,GORM 在第一次注册模型时就缓存表名,后续所有实例都复用这个值。传入不同 user_id 时,TableName() 返回的还是第一次计算的结果。
典型错误现象:db.Where("id = ?", 123).First(&u) 查到的是 user_001 表的数据,但 123 实际属于 user_003;或者 record not found 却明明存在。
- 正确做法是用
Scopes:写一个返回func(*gorm.DB) *gorm.DB的函数,里面调db.Table("user_003") - 分片键(如
user_id)必须作为参数传入 scope,不能从context或全局变量读 —— 并发下会串表 - 别在 scope 里调
db.Unscoped()或改FullSaveAssociations,这些状态会污染后续链式调用
sqlc 生成代码和手写 SQL 该怎么选?
sqlc 适合稳定、结构清晰的 CRUD 场景;手写 SQL 更适合复杂 JOIN、窗口函数、或需要精细控制执行计划的查询。
容易踩的坑:用 sqlc 生成后,发现某个查询要加 FOR UPDATE SKIP LOCKED,但 sqlc 不支持自定义 hint,硬改生成文件会被下次生成覆盖。
- CRUD 主体走
sqlc:定义queries.sql,生成类型安全的GetTaskByID等函数 - 复杂查询单独手写:放在
internal/postgresql/raw_queries.go,用db.QueryRowContext()手动 scan - 别把 sqlc 生成的
*sqlc.Queries直接暴露给 service 层 —— 它绑定了*sql.DB,不利于测试 mock
AutoMigrate() 在生产环境被禁用,结果启动就 panic。这类问题不会出现在单元测试里,只会在部署那一刻爆发。


















