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 索引标签的完整分类与关系:

GORM 索引约束全景图 索引标签体系 primaryKey index uniqueIndex 单字段主键 复合主键 单列索引 联合索引 唯一约束索引 索引标签常用参数 • name:索引名称,如 idx_user_status • type:索引类型(unique/hash/btree/gist 等) • sort:排序方向(ASC/DESC) • length:前缀索引长度

4.7 避坑指南

常见陷阱:
  • 索引冗余:联合索引 (a, b) 已包含 (a) 的索引能力,无需再单独为 a 建索引。
  • 索引过多:每个索引都会增加写入开销和存储空间,OLTP 系统单表索引建议不超过 5 个。
  • NULL 与索引:MySQL 中 NULL 值在唯一索引中可重复,业务层需额外校验。
  • 字符串索引长度:对长文本字段建索引时,务必使用 length 参数限制前缀长度,避免索引过大。

4.8 小结

本章系统讲解了 GORM 中主键与索引约束的核心标签:

  • primaryKey:定义单字段或复合主键,保障数据唯一性与快速定位。
  • index:创建普通索引,支持自定义名称、类型、排序和前缀长度。
  • uniqueIndex:语义化创建唯一索引,防止重复数据。
  • 联合索引:通过相同索引名称组合多个字段,遵循最左前缀原则。

合理运用这些标签,可以在模型层直接表达数据库的索引策略,让 GORM 的 AutoMigrate 自动生成高效的索引结构,为应用性能打下坚实基础。