GORM v2中定义联合唯一索引需多个字段共用相同uniqueIndex:idx_name标签,索引列顺序由结构体字段声明顺序决定,迁移时通过AutoMigrate创建,数据库层面保证组合值唯一。

gorm.Model 里用 UniqueIndex 标签声明联合唯一索引
GORM v2(即 gorm.io/gorm)不支持像旧版那样在 struct tag 里写 unique_index,必须显式调用 db.Migrator().CreateIndex() 或在 gorm.Model 的 SetupJoinTable / TableName 之外,用 GORM 的 Index struct tag 配合 unique 属性。正确写法是:
在字段上加 gorm:"uniqueIndex:idx_user_email_status",多个字段共用同一个 index 名即可构成联合唯一索引。
-
uniqueIndex后面的名称(如idx_user_email_status)必须完全一致,否则 GORM 会为每个字段建独立索引 - 字段顺序会影响索引的最左前缀匹配行为,比如
email在前、status在后,则WHERE email = ?能走索引,但WHERE status = ?不能 - 该标签只影响迁移(
AutoMigrate),不会自动创建索引——必须确保调用了db.AutoMigrate(&User{})
联合唯一索引字段必须同时存在且非零值才生效
GORM 的 uniqueIndex 是数据库层面约束,但实际是否被触发,取决于你插入/更新时这些字段有没有值。常见坑:
- 如果某个字段是
*string或sql.NullString,且值为nil,MySQL/PostgreSQL 会把NULL当作“未知”,而UNIQUE约束对NULL不校验(即多行NULL不冲突) - 想让空字符串
""也参与唯一性判断?得靠应用层预处理,或数据库默认值(如gorm:"default:''")+ 字段设为NOT NULL - SQLite 对
UNIQUE中的NULL行为和其他数据库不一致,测试时建议用目标生产库
迁移时索引名冲突或重复创建报错怎么办
执行 AutoMigrate 时可能遇到类似错误:ERROR: relation "idx_user_email_status" already exists(PostgreSQL)或 MySQL 的 duplicate key name。这是因为:
立即学习“go语言免费学习笔记(深入)”;
- GORM 默认不会检查索引是否存在,而是直接
CREATE INDEX,失败就报错 - 手动建过同名索引、或之前迁移中断导致元数据残留,都会触发这个
- 解决方法不是删掉标签,而是改用
db.Migrator().CreateIndex(&User{}, "idx_user_email_status")—— 它内部会先判断再建,更安全 - 如果已上线服务要加索引,建议用
db.Migrator().CreateConstraint()配合Check或直接发db.Exec("CREATE UNIQUE INDEX CONCURRENTLY ...")(PostgreSQL)避免锁表
联合唯一索引和软删除字段一起用要特别小心
如果你模型嵌入了 gorm.DeletedAt(即启用软删除),而联合索引又包含业务字段(比如 tenant_id + name),就会出现一个经典问题:逻辑删除的记录仍占着唯一键,导致新记录无法插入同名项。
典型场景:租户系统中,用户删掉一个配置项后,另一个用户想新建同名配置,被唯一索引拦住。
- 根本原因:软删除只是把
DeletedAt设为非零时间,并不从表中移除数据,所以联合索引依然生效 - 解法一:把
DeletedAt加进联合索引,变成(tenant_id, name, deleted_at)—— 但这样会导致查询WHERE tenant_id = ? AND name = ? AND deleted_at IS NULL无法高效走索引(因为有 IS NULL) - 解法二:用部分索引(PostgreSQL)或函数索引(MySQL 8.0.13+),例如
CREATE UNIQUE INDEX idx_tenant_name ON configs (tenant_id, name) WHERE deleted_at IS NULL,GORM 不支持直接声明,得手写db.Exec
这事没银弹,得看数据库能力、QPS 和一致性要求;多数中小项目宁愿不用软删除,或者接受“删除后不可重名”的设计。


















