gorm 中 addindex() 不创建唯一索引,必须通过结构体标签(如 gorm:"uniqueindex")配合 automigrate() 才能建库级唯一约束;gorm:"unique" 仅做应用层校验,不生成数据库索引。

gorm.Model().AddIndex() 不会创建唯一索引
直接调用 AddIndex() 只会建普通 B-tree 索引,哪怕你传入 unique: true 也不会生效——这是 GORM v1 和早期 v2 用户最常踩的坑。GORM 的索引定义必须通过结构体标签或迁移时显式声明,运行时调用 AddIndex() 是无效的。
正确做法是:在模型 struct 字段上用 gorm:"uniqueIndex" 或 gorm:"index,unique" 标签,再执行 AutoMigrate()。
-
uniqueIndex:生成全局唯一索引名(如idx_users_email),适合单字段 -
index,unique:可配合name:自定义索引名,也支持多字段联合唯一约束 - 若字段已存在且含重复数据,
AutoMigrate()会报错,需先清理数据或手动建索引
多字段联合唯一索引怎么写
用 gorm:"uniqueIndex:idx_user_org_role" 标签绑定到任一参与字段即可,GORM 会把所有打上同名 uniqueIndex 的字段合并为一个联合约束。
type UserRole struct {
UserID uint `gorm:"primaryKey"`
OrgID uint `gorm:"primaryKey"`
Role string `gorm:"uniqueIndex:idx_user_org_role"`
}
注意:Role 字段本身不一定是主键,但必须和 UserID、OrgID 一起被包含进同一个 uniqueIndex 名下;否则 GORM 只会对该字段单独建唯一索引。
- 联合索引顺序由字段在 struct 中声明顺序决定,影响查询效率,需按高频查询条件排序
- MySQL 会自动为
PRIMARY KEY和UNIQUE字段建索引,无需额外加index标签 - SQLite 不支持
ALTER TABLE ... ADD CONSTRAINT,联合唯一索引必须在建表时完成,即首次AutoMigrate()就得定义好
已有表如何补唯一索引
GORM 的 AutoMigrate() 默认不会删除或修改已有索引,只新增缺失的。如果表已存在,且你想加唯一约束,不能只改 struct 标签再跑一次迁移——它大概率静默跳过。
此时应使用原生 SQL 或 db.Migrator().CreateIndex() 显式操作:
db.Migrator().CreateIndex(&User{}, "idx_users_phone") // 普通索引
db.Migrator().CreateIndex(&User{}, "unique_email") // 注意:这个名称只是标识,不是 SQL 索引名
但更可靠的做法是绕过 GORM,直连数据库执行:
- PostgreSQL:
CREATE UNIQUE INDEX CONCURRENTLY idx_users_email ON users(email); - MySQL:
ALTER TABLE users ADD UNIQUE INDEX idx_users_email (email); - 务必先
SELECT COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1;确认无重复值
gorm:"unique" 和唯一索引不是一回事
gorm:"unique" 标签仅影响 GORM 的字段校验逻辑(比如插入时检查是否已存在),**不会触发数据库层的唯一约束**。它不建索引,也不防并发写入冲突。
真正防重复、保证数据一致性的,只有数据库的唯一索引(或主键)。漏掉这一步,高并发场景下极易出现脏数据。
-
unique标签 + 无数据库索引 = 假性防护,仅对单次 ORM 调用有效 - 即使加了
uniqueIndex,也要在应用层捕获ErrDuplicatedKey类错误,做降级处理 - 某些驱动(如 SQLite)对唯一索引的错误码返回不统一,建议统一用
errors.Is(err, gorm.ErrDuplicatedKey)判断
SHOW INDEX FROM xxx 或对应 DB 的系统表。











