复合主键

大多数表用单字段主键,但某些场景需要多个字段组合作为主键。GORM 支持复合主键定义。

定义复合主键

多个字段标记 primaryKey

type UserLanguage struct {
    UserID     uint   `gorm:"primaryKey"`
    LanguageID uint   `gorm:"primaryKey"`
    Proficiency string
}

生成的 SQL:

CREATE TABLE user_languages (
    user_id INT,
    language_id INT,
    proficiency VARCHAR(50),
    PRIMARY KEY (user_id, language_id)
)

复合主键查询

var ul UserLanguage
db.First(&ul, UserLanguage{UserID: 1, LanguageID: 2})

或者:

db.Where("user_id = ? AND language_id = ?", 1, 2).First(&ul)

复合主键更新

ul := UserLanguage{UserID: 1, LanguageID: 2}
db.Model(&ul).Update("proficiency", "fluent")

复合主键删除

ul := UserLanguage{UserID: 1, LanguageID: 2}
db.Delete(&ul)

复合外键

关联表使用复合主键:

type Order struct {
    OrderNo    string `gorm:"primaryKey"`
    BranchID   uint   `gorm:"primaryKey"`
    Items      []OrderItem
}

type OrderItem struct {
    OrderNo    string `gorm:"primaryKey;index:fk_order"`
    BranchID   uint   `gorm:"primaryKey;index:fk_order"`
    ItemID     uint   `gorm:"primaryKey"`
    ProductID  uint
    Quantity   int
}

定义复合外键:

type OrderItem struct {
    OrderNo   string `gorm:"primaryKey"`
    BranchID  uint   `gorm:"primaryKey"`
    ItemID    uint   `gorm:"primaryKey"`
    Order     Order  `gorm:"foreignKey:OrderNo,BranchID;references:OrderNo,BranchID"`
}

实际案例

用户语言关联

type UserLanguage struct {
    UserID      uint   `gorm:"primaryKey;index:fk_user"`
    LanguageID  uint   `gorm:"primaryKey;index:fk_language"`
    Proficiency string `gorm:"type:enum('beginner','intermediate','advanced','native')"`
    CreatedAt   time.Time
}

func (UserLanguage) TableName() string {
    return "user_languages"
}

订单明细

type OrderItem struct {
    OrderID   uint `gorm:"primaryKey"`
    ItemSeq   uint `gorm:"primaryKey"`
    ProductID uint
    Quantity  int
    Price     float64
}

func (OrderItem) TableName() string {
    return "order_items"
}

多租户数据

type TenantData struct {
    TenantID uint `gorm:"primaryKey"`
    ID       uint `gorm:"primaryKey;autoIncrement"`
    Data     string
}

func (TenantData) TableName() string {
    return "tenant_data"
}

复合主键的注意事项

自增主键

复合主键中可以有自增字段:

type OrderItem struct {
    OrderID uint `gorm:"primaryKey"`
    Seq     uint `gorm:"primaryKey;autoIncrement"`
}

但只有部分数据库支持复合主键中的自增。

软删除

复合主键表使用软删除:

type UserLanguage struct {
    UserID     uint           `gorm:"primaryKey"`
    LanguageID uint           `gorm:"primaryKey"`
    DeletedAt  gorm.DeletedAt `gorm:"primaryKey"`
    Proficiency string
}

DeletedAt 需要加入主键,否则软删除后无法再次创建相同记录。

关联查询

复合主键的关联查询需要额外处理:

type Order struct {
    OrderNo  string `gorm:"primaryKey"`
    BranchID uint   `gorm:"primaryKey"`
    Items    []OrderItem
}

var order Order
db.Where("order_no = ? AND branch_id = ?", "ORD001", 1).Preload("Items").First(&order)

替代方案

复合主键在某些场景下可以用唯一索引替代:

type UserLanguage struct {
    ID         uint `gorm:"primaryKey;autoIncrement"`
    UserID     uint `gorm:"uniqueIndex:idx_user_lang"`
    LanguageID uint `gorm:"uniqueIndex:idx_user_lang"`
    Proficiency string
}

用自增 ID 作为主键,复合唯一索引保证数据唯一性。

好处:

  • 更简单的关联
  • 更好的 ORM 支持
  • 更容易的外键约束

小结

复合主键适合多对多关联表、订单明细等场景。但会增加复杂度,权衡使用。