gorm 不是“用不用”的问题,而是“怎么配、怎么写、怎么避坑”的问题;v2 初始化需用 mysql.open(dsn)、dsn 要含 parsetime=true 和 loc=local、驱动须用 gorm.io/driver/mysql、automigrate 只增不改字段、主键和时间字段名须严格遵循约定、preload 依赖正确外键和字段定义、db 实例应全局复用不可频繁 close。

直接说结论:GORM 不是“用不用”的问题,而是“怎么配、怎么写、怎么避坑”的问题。它在 Go 生态里仍是主流选择,但 V2 版本的初始化、日志、连接池、软删除默认行为这些地方,稍不注意就会卡住或线上出事。
gorm.Open 为什么总 panic 或报错 failed to initialize database
绝大多数连接失败不是密码或地址错了,而是 DSN 格式或驱动导入不匹配。
-
gorm.Open第一个参数必须是mysql.Open(dsn)(V2),不是字符串;老教程里写"mysql"字符串是 V1 写法,已失效 - DSN 中
parseTime=True和loc=Local必须同时存在,否则time.Time字段会解析失败,报invalid time或空值 - MySQL 驱动必须用
gorm.io/driver/mysql,不能混用github.com/go-sql-driver/mysql—— 后者只提供*sql.DB,不兼容gorm.Open的签名 - 如果用 Docker 或远程 MySQL,
tcp(127.0.0.1:3306)要换成实际 IP,localhost 在容器内可能解析失败
AutoMigrate 为什么没建表,或字段没更新
AutoMigrate 是开发期便利工具,不是生产迁移方案,它的行为边界非常明确。
- 只新增字段、索引、外键,**绝不会修改已有字段类型**(比如把
string改成*string,或int改成int64) - 不会删除字段或约束,哪怕结构体里已经删了该字段
- 必须传指针:
db.AutoMigrate(&User{}),传值db.AutoMigrate(User{})会静默失败 - 如果表名不是复数蛇形(如
User→users),要显式实现TableName()方法,否则找不到对应表
db.Create 插入后 ID 为 0,或时间字段为空
这通常不是数据库配置问题,而是模型定义没对上 GORM 的约定。
- 主键字段名必须是
ID(首字母大写),且类型支持自增(uint、int、int64等),id小写不会被识别为主键 - 想自动填充
CreatedAt/UpdatedAt,字段名必须严格为CreatedAt、UpdatedAt(大小写敏感),或用gorm.Model嵌入 - 软删除字段名必须是
DeletedAt(类型为*time.Time),否则Delete会变物理删除,且查不到数据 - 插入时传的是值不是指针(
db.Create(user)而非db.Create(&user)),会导致主键无法回写到原变量
Preload 关联查询总是 N+1,或返回空 slice
预加载不是“加个 Preload 就完事”,它和字段命名、外键、JOIN 条件强绑定。
-
Preload("Orders")要求User结构体里有Orders []Order字段,且Order里有UserID uint外键(或显式用foreignKeytag 标注) - 如果关联字段用了自定义名(比如
OwnerID),必须补 tag:Orders []Order `gorm:"foreignKey:OwnerID"` -
Preload默认走 LEFT JOIN,但如果关联表无记录,主对象仍会返回 —— 若想只返回有订单的用户,得改用Joins("Orders").Where("orders.id IS NOT NULL") - 多个
Preload(如Preload("Orders.Items"))必须确保中间结构体也正确定义了关联字段,否则静默跳过
最常被忽略的一点:GORM 的 DB 实例不是线程安全的“单例”,但也不是每次都要重连。它内部持有一个 *sql.DB 连接池,全局复用即可;手动调 db.Close() 会关掉整个池,后续所有操作都会卡死或 panic。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











