gorm.model() 不执行查询,仅设置上下文;需接 first()/find() 等才发 sql;preload 多级嵌套需 v1.23+;update() 跳过零值字段,强制更新须用 select();事务中禁止嵌套 begin(),应传 *gorm.db 参数。

gorm.Model() 为什么查不到数据?
因为 gorm.Model() 不执行查询,它只是设置操作上下文——你得接上 First()、Find() 或其他实际查询方法才真正发 SQL。
常见错误是写完 gorm.Model(&User{}).Where("id = ?", 1) 就以为查到了,结果变量还是零值。这行代码只构建了条件,没触发 SELECT。
- 正确姿势:用
gorm.Model(&User{}).Where("id = ?", 1).First(&u) - 如果只想更新或删除,不查数据,
Model()+Where()+Update()/Delete()是合法的 -
Model()会忽略结构体字段的零值(比如ID: 0),所以别指望靠传一个带零 ID 的 struct 去查记录
Preload 关联查询时嵌套层级失效
Preload() 默认只支持一级嵌套,Preload("Orders.Items") 看似合理,但 GORM v1.23+ 才原生支持两级(且要求 Items 有正确 foreign key 定义);v1.22 及更早版本会静默忽略 "Items" 这一层。
典型现象:Orders 能查出来,但每个 Order.Items 始终是空 slice,日志里也看不到 JOIN 或额外 SELECT。
立即学习“go语言免费学习笔记(深入)”;
- 先确认 GORM 版本:
go list -m gorm.io/gorm,低于 v1.23.0 的建议升级 - 降级兼容写法:拆成两次 Preload,
Preload("Orders")后再对 Orders 切片手动查 Items(用IN批量查) - 别在 Preload 中混用
Select(),比如Preload("Orders", db.Select("id, user_id"))会导致关联字段丢失
Update() 全字段更新 vs Select() 部分更新的陷阱
Save() 和 Update() 行为差异极大:Save() 会写入所有非零字段(包括可能为 0 的 int 字段),而 Update() 默认只更新非零、非空、非默认值字段——但“默认值”由 struct tag 的 gorm:"default" 决定,不是 Go 零值语义。
比如 Age int `gorm:"default:18"`,即使你传 Age: 0,Update() 也不会把它当有效值写入,因为 0 是 int 零值,且未被显式标记为“要更新”。
- 安全更新单字段:用
db.Model(&u).Select("age").Update("age", 25) - 强制更新零值字段:必须显式
Select(),否则 GORM 直接跳过 -
Updates(map[string]interface{})会绕过零值判断,但 map 里的 key 必须和数据库列名一致(不是 struct 字段名)
事务中嵌套调用导致 panic: "invalid transaction"
在已开启的事务内,如果某个函数内部又调用了 db.Begin() 或 db.Transaction(),GORM 会尝试在子 goroutine 或新连接上启动事务,而当前连接已绑定父事务,直接 panic 报 "invalid transaction"。
这不是并发问题,而是事务对象被错误复用——尤其在封装了 DB 操作的工具函数里容易踩坑。
- 所有业务函数应接收
*gorm.DB参数,而不是自己拿db全局变量开事务 - 需要复用事务上下文时,用
db.Session(&gorm.Session{AllowGlobalUpdate: true})而非新建事务 - 测试时用
db.Session(&gorm.Session{DryRun: true})可提前暴露事务嵌套逻辑










