4. 主键与索引约束:primaryKey、index、uniqueIndex 标签、联合索引定义
在 GORM 中,主键与索引约束是数据完整性与查询性能的基石。本章将深入解析 primaryKey、index、uniqueIndex 标签的用法,以及联合索引的定义策略,帮助你在模型层精准控制数据库的约束行为。
4.1 主键约束:primaryKey 标签
GORM 默认将每个结构体的 ID 字段作为主键。当需要自定义主键字段或使用复合主键时,必须使用 primaryKey 标签显式声明。
| 场景 | 标签写法 | 说明 |
|---|---|---|
| 单字段主键 | gorm:"primaryKey" |
将指定字段设为主键,自动创建主键索引 |
| 复合主键 | 多个字段均标注 primaryKey |
联合多个字段构成复合主键,所有字段均不能为 NULL |
| 禁用默认主键 | gorm:"-" 或 gorm:"primaryKey;autoIncrement:false" |
跳过默认 ID 字段的主键行为 |
实战案例:复合主键定义
type OrderItem struct {
OrderID uint `gorm:"primaryKey"`
ProductID uint `gorm:"primaryKey"`
Quantity int
Price float64
}
// 生成的 DDL:CREATE TABLE order_items (
// order_id BIGINT UNSIGNED NOT NULL,
// product_id BIGINT UNSIGNED NOT NULL,
// quantity INT,
// price DOUBLE,
// PRIMARY KEY (order_id, product_id)
// );
提示:复合主键的字段顺序影响索引的 B+ 树结构。查询条件应尽量匹配主键定义的最左前缀,否则无法充分利用主键索引。
4.2 单列索引:index 标签
index 标签用于为单个字段创建普通索引,加速该字段上的查询、排序和 JOIN 操作。GORM 支持自定义索引名称和排序方式。
type User struct {
ID uint
Name string `gorm:"index:idx_name"`
Email string `gorm:"index:idx_email,type:unique"` // 唯一索引
Age int `gorm:"index:idx_age,sort:desc"`
CreatedAt time.Time `gorm:"index"`
}
// 生成的索引:
// idx_name (name)
// idx_email UNIQUE (email)
// idx_age (age DESC)
// idx_users_created_at (created_at)
| 参数 | 示例 | 作用 |
|---|---|---|
| 索引名称 | index:idx_name |
指定索引名称,不指定则自动生成 |
type |
type:unique |
将索引设为唯一索引(等价于 uniqueIndex) |
sort |
sort:desc |
指定索引排序方向(默认 ASC) |
length |
length:10 |
对字符串类型指定前缀索引长度 |
4.3 唯一索引:uniqueIndex 标签
uniqueIndex 标签是 index 标签的语义化变体,专门用于创建唯一索引。与 index:...,type:unique 效果相同,但代码可读性更强。
type Product struct {
ID uint
Code string `gorm:"uniqueIndex:idx_code"`
SKU string `gorm:"uniqueIndex:idx_sku"`
Name string
}
// 生成的 DDL:
// CREATE UNIQUE INDEX idx_code ON products (code);
// CREATE UNIQUE INDEX idx_sku ON products (sku);
注意:唯一索引允许 NULL 值重复(MySQL 等数据库行为)。如果业务要求非空唯一,请同时添加
NOT NULL 约束(gorm:"not null;uniqueIndex")。
4.4 联合索引定义
联合索引(复合索引)在多个字段上建立索引,遵循“最左前缀”原则。GORM 通过 index 标签的相同名称将多个字段组合成一个联合索引。
type Order struct {
ID uint
UserID uint `gorm:"index:idx_user_status"`
Status string `gorm:"index:idx_user_status"`
CreatedAt time.Time
TotalAmount float64
}
// 生成的联合索引:idx_user_status (user_id, status)
联合索引的字段顺序至关重要。以下表格展示了不同查询模式对联合索引 (user_id, status) 的利用情况:
| 查询条件 | 是否使用索引 | 原因 |
|---|---|---|
WHERE user_id = 1 |
✅ 完全使用 | 匹配最左前缀 |
WHERE user_id = 1 AND status = 'paid' |
✅ 完全使用 | 匹配全部字段 |
WHERE status = 'paid' |
❌ 无法使用 | 跳过了最左列 user_id |
WHERE user_id IN (1,2) AND status = 'paid' |
✅ 部分使用 | user_id 走索引,status 需回表过滤 |
最佳实践:将区分度高的字段放在联合索引左侧,将等值查询字段放在范围查询字段之前。
4.5 索引约束与迁移
GORM 的 AutoMigrate 会根据模型标签自动创建、更新索引。但需要注意:
- 索引名称变更:GORM 不会自动删除旧索引,需手动迁移或使用
Migrator().DropIndex()。 - 索引类型:通过
type参数可指定索引类型(如type:hash、type:btree),但需数据库支持。 - 索引注释:使用
comment标签为索引添加说明,便于团队维护。
// 手动管理索引迁移
db.Migrator().CreateIndex(&User{}, "idx_name")
db.Migrator().DropIndex(&User{}, "idx_name")
4.6 索引约束全景图
下图展示了 GORM 索引标签的完整分类与关系:
4.7 避坑指南
常见陷阱:
- 索引冗余:联合索引
(a, b)已包含(a)的索引能力,无需再单独为a建索引。 - 索引过多:每个索引都会增加写入开销和存储空间,OLTP 系统单表索引建议不超过 5 个。
- NULL 与索引:MySQL 中
NULL值在唯一索引中可重复,业务层需额外校验。 - 字符串索引长度:对长文本字段建索引时,务必使用
length参数限制前缀长度,避免索引过大。
4.8 小结
本章系统讲解了 GORM 中主键与索引约束的核心标签:
- primaryKey:定义单字段或复合主键,保障数据唯一性与快速定位。
- index:创建普通索引,支持自定义名称、类型、排序和前缀长度。
- uniqueIndex:语义化创建唯一索引,防止重复数据。
- 联合索引:通过相同索引名称组合多个字段,遵循最左前缀原则。
合理运用这些标签,可以在模型层直接表达数据库的索引策略,让 GORM 的 AutoMigrate 自动生成高效的索引结构,为应用性能打下坚实基础。