gorm 中 mysql enum 字段应通过 gorm:"type:enum('active','inactive','maintenance');default:'inactive'" 显式声明类型与默认值,go 端用字符串常量(如 type agentstatus string)保证类型安全与 json 可读性,迁移需同步 alter table 修改 enum 定义,避免仅改代码导致状态不一致。

GORM 里没有原生 enum 类型,但状态字段必须兼顾数据库约束、Go 类型安全、JSON 序列化可读性、默认值设置和迁移兼容性——这四个点没对齐,后续改起来全是坑。
MySQL ENUM 字段在 GORM 中怎么映射才不丢约束
直接用 ENUM('ACTIVE','INACTIVE','MAINTENANCE') 是最稳妥的方案,它把校验逻辑下推到数据库层,避免应用层写错值。
- 必须在 GORM tag 里显式声明类型:
gorm:"type:ENUM('ACTIVE','INACTIVE','MAINTENANCE');default:'INACTIVE'" - Go 端用字符串常量定义(不是 int):
type AgentStatus string+const ActiveAgentStatus AgentStatus = "ACTIVE" - 不能只靠 Go 层校验:一旦绕过 ORM 直接 SQL 插入非法值(比如
'UNKNOWN'),MySQL 会静默转成空字符串或报错,取决于 SQL mode - 迁移时新增枚举值需用
ALTER TABLE ... MODIFY COLUMN status ENUM(...),不能只改 Go 代码
用 int 枚举时 Status 值存进数据库是数字但 JSON 返回是字符串
这是常见需求:数据库存 1 节省空间,API 返回 "running" 方便前端理解。关键在于实现 MarshalJSON 和 UnmarshalJSON 方法。
- 定义类型别名:
type Status int,配合iota+1定义常量(避开 0) -
MarshalJSON里用switch映射到字符串,返回json.Marshal("running"),不是json.Marshal(struct{Status string}{...}) -
UnmarshalJSON必须补上:否则前端传{"status":"running"}会解析失败,panic 或静默为 0 - GORM 默认用
Value()写入数据库,所以Status(1)存的是整数,不需要额外实现
GORM 默认值在自定义类型上怎么生效
gorm:"default:1" 对 type Status int 有效,但对 type AgentStatus string 必须写成 gorm:"default:'ACTIVE'" —— 类型必须和数据库字段类型一致。
- int 枚举:用数字,默认值写
gorm:"default:1",对应Running Status = iota + 1 - string 枚举:用单引号包住字符串,默认值写
gorm:"default:'ACTIVE'" - 如果用了指针类型(如
*Status),默认值失效,GORM 不会自动赋值,得靠业务层判断 - 软删除字段(
DeletedAt)和枚举字段不要混用默认值逻辑,容易覆盖
状态变更时如何避免脏数据和并发冲突
状态字段常被高频更新(如心跳检测),单纯靠 Save() 会覆盖其他字段修改,且无乐观锁保护。
- 优先用
Model(&a).Where("id = ? AND status = ?", a.ID, oldStatus).Updates(map[string]interface{}{"status": newStatus})做条件更新 - 不要依赖
SELECT + UPDATE两步操作,中间可能被其他协程改掉 - MySQL 的
ENUM字段在UPDATE时不会触发索引失效,但联合索引(如idx_account_status)必须包含查询条件字段 - 如果用 int 枚举,注意
0值:Go struct 零值是 0,若没初始化就 Save,可能意外写入非法状态
最易忽略的是 JSON 反序列化路径:前端传字符串,后端要能转成自定义类型;数据库存数字,GORM 要能正确读取;迁移脚本还得同步更新 ENUM 定义——三个环节漏一个,状态就对不上。











