Go struct字段db tag未生效是因为reflect默认不穿透指针读取tag,需用.Elem().Type()获取结构体类型;Scan要求传指针,GORM操作必须用&u而非u,指针字段需注意nil与空值语义差异。

Go struct 字段加 db tag 为什么没生效?
因为 Go 的 reflect 包默认无法通过指针间接读取 struct 字段的 tag —— 如果你传的是 *User,但用 reflect.ValueOf(v).Elem() 后没再取 Type() 对应的字段信息,tag 就丢了。
- 必须确保反射操作的对象是 struct 类型本身(不是指针类型),或显式调用
.Elem().Type()获取被指向 struct 的类型 -
db:"name"这类 tag 只绑定在 struct 字段上,reflect.StructTag不会穿透指针自动查找 - 常见错误:直接对
*struct{ Name string `db:"name"` }调用v.Type().Field(i).Tag→ 返回空,因为v.Type()是*T,不是T
用指针接收数据库扫描结果时,Scan 报 sql: expected pointer, not struct
这是 rows.Scan() 或 db.QueryRow().Scan() 最典型的 panic 场景:它要求每个参数都是地址,而你传了 struct 值而非指针。
- 正确写法是
var u User; err := row.Scan(&u.Name, &u.Email),或者更常见地:var u User; err := db.QueryRow(sql, id).Scan(&u.ID, &u.Name) - 如果 ORM 自动映射(比如使用
sqlx.StructScan),则必须传&u,不能传u;否则StructScan内部无法写入字段 - 注意嵌套结构体字段:若
User里有Profile *Profile,且你想让Profile字段也被扫描填充,那Profile必须是非 nil 指针(提前u.Profile = &Profile{}),否则Scan会跳过它
gorm.Model(&u) 和 gorm.Model(u) 行为差异极大
GORM 对指针和值的处理逻辑完全不同:只有传指针,GORM 才能修改原变量、识别主键、触发钩子;传值等于“只读快照”。
-
db.First(&u, 1)✅ 正确:查到数据后写入u的内存地址 -
db.First(u, 1)❌ 无效:GORM 尝试往一个临时副本里写,原u不变,还可能 panic -
db.Create(&u)→ 自动生成 ID 并回填到u.ID;db.Create(u)→ ID 不会回填,且 GORM 可能误判为零值主键而报错 - 关联预加载(
Preload)也依赖指针:若User有Orders []Order,必须db.Preload("Orders").First(&u, 1),否则u.Orders永远为空切片
JSON 和 DB 字段映射共用 json/db tag 时,指针字段容易漏掉零值判断
比如 Name *string `json:"name" db:"name"`,当数据库该字段为 NULL,GORM 会把 Name 设为 nil;但前端传 JSON 时若 "name": "",反序列化后 Name 是非 nil 的空字符串指针 —— 两者语义不同,但代码里常混为一谈。
立即学习“go语言免费学习笔记(深入)”;
- 入库前检查:
if u.Name != nil && *u.Name == ""才算有意设为空字符串;u.Name == nil应理解为 “不更新” 或 “设为 NULL” - GORM 的
Update默认跳过零值字段,但指针的零值是nil,不是空字符串或 0,所以u.Name = new(string)后未赋值,Update仍会把它当 nil 跳过 - 用
map[string]interface{}做动态更新时,nil指针会被转成null,而空字符串是"",数据库行为完全取决于 column 是否允许 NULL










