数据库表结构设计是后端开发的基石,良好的设计能提升系统性能、降低维护成本。本文将从基础概念讲起,结合商品三级分类的实战案例,详解表结构设计的核心思路与最佳实践。
id
字段,如id INT32 PRIMARY KEY AUTO_INCREMENT
。parent_category_id
关联自身的id
。name
字段必须填写。username
。level
字段默认值为 1。level CHECK (level BETWEEN 1 AND 3)
确保分类层级不超过 3 级。parent_category_id
)。product_category
),避免关键字。parent_category_id
),见名知意。TINYINT
/INT
/BIGINT
),避免过度使用大类型。CHAR
,可变长度用VARCHAR
,超长文本用TEXT
。DATETIME
或TIMESTAMP
,避免字符串存储。is_deleted
或deleted_at
字段标记删除,保留历史数据。version
字段用于乐观锁,解决并发更新冲突。在实际开发中,几乎所有表都需要通用字段(如主键、创建时间),将这些字段抽象为BaseModel
可大幅提升复用性。
// model/base.go
package model
import (
"gorm.io/gorm"
"time"
)
// BaseModel 基础模型,所有业务模型都嵌入该结构体
type BaseModel struct {
ID int32 `gorm:"primarykey;type:int" json:"id"` // 主键
CreatedAt time.Time `gorm:"column:add_time" json:"-"` // 创建时间
UpdatedAt time.Time `gorm:"column:update_time" json:"-"` // 更新时间
DeletedAt gorm.DeletedAt `gorm:"column:delete_time" json:"-"` // 软删除标记
IsDeleted bool `json:"-"` // 逻辑删除标识
}
单独文件存储:将BaseModel
放在model/base.go
中,所有业务模型通过嵌入复用:
// 商品分类模型嵌入BaseModel
type Category struct {
BaseModel // 继承BaseModel的所有字段
Name string `gorm:"type:varchar(20);not null" json:"name"`
// 其他业务字段...
}
为什么用 int32 而非 bigint?
三级分类是电商系统的常见需求(如 "电子产品→手机→智能手机"),通过单表设计实现树形结构是最优方案。
parent_category_id
字段关联自身的id
,实现 "自己关联自己"。// model/category.go
package model
type Category struct {
BaseModel // 嵌入基础模型
Name string `gorm:"type:varchar(20);not null" json:"name"` // 分类名称
ParentID int32 `gorm:"column:parent_category_id" json:"parent"` // 父分类ID
Parent *Category `gorm:"-" json:"-"` // 父分类对象(用于关联查询)
SubCategory []*Category `gorm:"foreignKey:ParentID;references:ID" json:"sub_category"` // 子分类列表
Level int32 `gorm:"type:int;not null;default:1" json:"level"` // 层级(1-3)
IsTab bool `gorm:"default:false;not null" json:"is_tab"` // 是否显示在导航栏
}
ParentID
为 0 或 NULL。ParentID=0
ParentID=顶级分类ID
ParentID=二级分类ID
Preload("Parent")
关联父分类数据,避免二次查询。category.Parent.Name
获取父分类名称,无需手动拼接 SQL。json:"-"
含义:JSON 序列化时忽略该字段,避免返回冗余的父分类数据。foreignKey
和references
指定关联关系。Preload("SubCategory")
一次性加载所有子分类,轻松构建树形结构。go
var categories []*Category
db.Where("level = 1").Preload("SubCategory.SubCategory").Find(&categories)
Name
:非空(not null
),分类必须有名称。ParentID
:允许为 0(顶级分类),但不允许为 NULL(通过默认值 0 约束)。Level
:非空且默认值为 1,确保每个分类都有明确层级。IsTab
:非空且默认值为 false,明确是否在导航栏显示。// 递归打印分类树
func PrintCategoryTree(categories []*Category, depth int) {
for _, c := range categories {
// 打印缩进,直观展示层级
for i := 0; i < depth; i++ {
fmt.Print(" ")
}
fmt.Printf("└─ %s(Level=%d)\n", c.Name, c.Level)
// 递归打印子分类
if len(c.SubCategory) > 0 {
PrintCategoryTree(c.SubCategory, depth+1)
}
}
}
// 使用示例
func main() {
var topCategories []*Category
// 查询所有顶级分类,并预加载二级、三级分类
db.Where("level = 1").Preload("SubCategory.SubCategory").Find(&topCategories)
// 打印分类树
PrintCategoryTree(topCategories, 0)
}
输出结果:
└─ 电子产品(Level=1)
└─ 手机(Level=2)
└─ 智能手机(Level=3)
└─ 功能机(Level=3)
└─ 电脑(Level=2)
└─ 笔记本(Level=3)
└─ 服饰(Level=1)
└─ 男装(Level=2)
└─ T恤(Level=3)
通过合理的表结构设计,不仅能提升系统性能,还能降低后续维护成本。三级分类的设计思路可推广到所有树形结构场景(如评论回复、权限菜单等),掌握这种设计模式能大幅提升后端开发效率。
如果这篇文章对大家有帮助可以点赞关注,你的支持就是我的动力!