GORM 的 gorm 标签是字段行为的唯一控制入口,偏离约定必须显式声明,否则建表错、查询漏、更新丢值;column 覆盖命名约定,default 仅作用于数据库插入空值时,not null 和 uniqueIndex 才真正保障性能与约束,"-" 彻底忽略字段而 "->:false" 仅禁读。

直接说结论:GORM 的 gorm 标签不是“可选装饰”,而是字段行为的**唯一控制入口**——不写标签时走约定(如 ID 自动为主键、CreatedAt 自动填充),但一旦需求偏离约定,就必须靠标签显式声明,否则建表错、查询漏、更新丢值。
字段映射不生效?先看 column 和命名约定冲突
很多人以为加了 gorm:"column:user_name" 就能改列名,结果迁移后还是 user_name → user_name(没变)。问题常出在两个地方:
- 结构体字段名本身是
UserName,GORM 默认转成user_name,再叠column:user_name看似冗余,实则必要——因为column是覆盖默认映射的开关,不写就按蛇形规则走 - 如果字段名已经是
user_name(小写下划线),GORM 仍会把它当驼峰处理,转成user__name(双下划线),此时必须用column:user_name强制锁定 -
column只影响建表和 SQL 生成,不影响 Go 层变量读写——它不改变字段名,只改变数据库列名
default 是数据库默认值,不是 Go 变量默认值
写 gorm:"default:0" 或 gorm:"default:'pending'",意思是「插入时若该字段为空(NULL),数据库自动填 0 或 'pending'」。它和 Go 代码里 Age int 初始化为 0 完全无关。
- 如果 Go 层传了
0或空字符串,GORM 会原样写入,default不触发 - 想让 Go 层也默认赋值,得配合指针类型或自定义 setter,比如
Status *string,传nil才触发数据库 default - 时间类 default 推荐用表达式:
gorm:"default:CURRENT_TIMESTAMP"或gorm:"default:NOW()",注意引号包裹,且 MySQL 版本需支持
not null 和 unique 必须配索引才真正生效
not null 直接加约束,但 unique 在 MySQL 中只建 UNIQUE 约束,不自动建索引;而 uniqueIndex 才既建约束又建索引——性能差别极大。
- 查唯一字段时,没索引的
unique会导致全表扫描,QPS 下降明显 -
uniqueIndex支持命名:gorm:"uniqueIndex:idx_email",方便后期删索引 - 复合唯一用法:
gorm:"uniqueIndex:idx_user_role;column:1"(column:1表示该字段在复合索引中排第 1 位) - 别混用
unique和uniqueIndex在同一字段上,GORM 不报错但数据库可能拒绝建表
- 和 ->:false 的区别常被搞反
gorm:"-" 是彻底忽略字段:不建列、不读、不写、不参与任何 GORM 操作;而 gorm:"->:false" 只禁读,仍会建列、仍可写入(比如密码哈希后存库,但查询时不返回明文)。
- 敏感字段如
Password,应该用gorm:"-",而不是gorm:"->:false"—— 后者仍会在INSERT语句中出现,只是 SELECT 不返回 gorm:" 常用于创建时间字段,但要注意:如果结构体里已经赋了值,GORM 仍会写入,不会跳过-
gorm:"autoCreateTime"和gorm:"autoUpdateTime"是更安全的替代方案,它们由 GORM 在内存中注入时间,不受传入值干扰
最易被忽略的点:所有标签都依赖反射解析,所以字段必须是导出的(首字母大写),且标签字符串必须用反引号 ` 包裹——写成双引号或漏掉反引号,GORM 完全看不到标签,静默回退到约定行为。


















