在 GORM 中,多列唯一索引需所有参与字段使用相同 index:xxx,unique 标签;索引列顺序由结构体字段声明顺序决定,如 First 在前、Second 在后则索引为 (first, second);不可用 sql: 标签或分散的 unique 标签,必须共用索引名且显式标注 unique。

gorm标签里怎么写索引定义
GORM 通过结构体字段的 gorm 标签控制索引创建,不是靠 SQL 语句或迁移脚本硬写。核心就一条:用 index 或 uniqueIndex 参数声明,GORM 在 AutoMigrate 时自动建索引。
常见写法:
-
Name string `gorm:"index"`→ 普通单列索引 -
Email string `gorm:"uniqueIndex"`→ 唯一单列索引(等价于index:idx_email,unique) -
Status string `gorm:"index:idx_status_created_at"`+CreatedAt time.Time `gorm:"index:idx_status_created_at"`→ 同名索引名触发复合索引(顺序按字段在结构体中声明顺序)
⚠️ 注意:uniqueIndex 是快捷写法,但无法用于多列唯一约束;多列唯一必须显式共用同一 index:xxx 名并加 unique。
复合索引字段顺序为什么不能颠倒
GORM 不解析 SQL 执行计划,它只按结构体字段声明顺序生成索引定义。比如:
type User struct {
Status string `gorm:"index:idx_status_created_at"`
CreatedAt time.Time `gorm:"index:idx_status_created_at"`
}
生成的索引是 (status, created_at),不是 (created_at, status)。这个顺序直接影响 WHERE + ORDER BY 能否命中索引。
典型踩坑场景:
- 查询写
WHERE created_at > ? ORDER BY status,但索引是(status, created_at)→ 只能走 range scan,type变成index或ALL - 分页常用
WHERE status = ? ORDER BY created_at DESC→ 索引必须是(status, created_at, id)才高效
外键字段不加索引会直接拖垮 Preload
db.Preload("Orders").Find(&users) 看似只查一次,实际发两条 SQL:第一条查 users,第二条用 IN (1,2,3...) 查 orders。如果 orders.user_id 没索引,第二条就变成全表扫描。
必须确保:
-
user_id字段类型和users.id严格一致(比如都是BIGINT,不能一边INT一边BIGINT) -
user_id单独建索引,或作为复合索引最左前缀(如(user_id, status)) - 别依赖
foreignkey标签自动建索引 —— GORM v2 不会自动为外键建索引,必须显式声明
什么时候该放弃 tag 自动建索引
struct tag 灵活但有局限:不支持降序、函数索引、部分索引、表达式索引。比如这些需求只能手动管理:
- PostgreSQL 的
jsonb_path_ops索引加速 JSONB 查询 → 用db.Migrator().CreateIndex(&Order{}, "gin_index_on_data") - MySQL 8.0+ 的不可见索引或降序索引(
ORDER BY name DESC)→ 必须手写CREATE INDEX ... USING BTREE (name DESC) - 只想对非空值建索引(如
WHERE deleted_at IS NULL)→ 需数据库原生命令,tag 无解
真正决定查询快慢的从来不是 GORM 写了什么 tag,而是你跑没跑 EXPLAIN ANALYZE 看执行计划。tag 只负责把索引建上去,能不能用上,得看查询条件和索引结构是否匹配。











