569 字
3 分钟
条件查询与分页

GORM 条件查询与分页#

Where 的几种写法#

字符串条件适合少量固定条件,值始终使用占位符:

db.Where("name = ? AND age >= ?", name, minAge).Find(&users)

结构体查询只使用非零字段:

db.Where(&User{Name: "Alice", Age: 18}).Find(&users)

如果 Age 的 0 也是有效筛选条件,改用 map 或显式字段:

db.Where(map[string]any{"age": 0}).Find(&users)
db.Where(&User{}, "Age").Find(&users)

切片条件:

db.Where("id IN ?", []uint{1, 2, 3}).Find(&users)
db.Where("name LIKE ?", "%alice%").Find(&users)

Select、Omit 和 Pluck#

db.Select("id", "name", "email").Find(&users)
db.Omit("password").Find(&users)
var names []string
db.Model(&User{}).Where("active = ?", true).Pluck("name", &names)

查询字段越少,网络、扫描和内存开销通常越低;但要确保业务真的不需要被省略的字段。

排序与分组#

db.Where("status = ?", "active").
Order("created_at DESC, id DESC").
Find(&users)
var stats []struct {
Status string
Count int64
}
db.Model(&User{}).
Select("status, COUNT(*) AS count").
Group("status").
Scan(&stats)

排序列通常来自服务端白名单,而不是直接拼接用户输入:

allowedSort := map[string]string{
"newest": "created_at DESC, id DESC",
"name": "name ASC, id ASC",
}
order, ok := allowedSort[sortKey]
if !ok {
order = allowedSort["newest"]
}
db.Order(order).Find(&users)

通用分页#

type Page[T any] struct {
Items []T `json:"items"`
Page int `json:"page"`
PageSize int `json:"page_size"`
Total int64 `json:"total"`
TotalPages int `json:"total_pages"`
}
func ListUsers(db *gorm.DB, page, pageSize int) (Page[User], error) {
if page < 1 {
page = 1
}
if pageSize < 1 {
pageSize = 20
}
if pageSize > 100 {
pageSize = 100
}
query := db.Model(&User{}).Where("deleted_at IS NULL")
var total int64
if err := query.Count(&total).Error; err != nil {
return Page[User]{}, err
}
items := make([]User, 0, pageSize)
offset := (page - 1) * pageSize
if err := query.
Select("id", "name", "email", "created_at").
Order("created_at DESC, id DESC").
Offset(offset).
Limit(pageSize).
Find(&items).Error; err != nil {
return Page[User]{}, err
}
totalPages := int((total + int64(pageSize) - 1) / int64(pageSize))
return Page[User]{
Items: items, Page: page, PageSize: pageSize,
Total: total, TotalPages: totalPages,
}, nil
}

分页必须固定排序,否则数据插入或删除时可能出现重复、遗漏。深分页场景可以考虑基于 (created_at, id) 的游标分页,避免很大的 OFFSET。

Scopes#

Scopes 是可组合的查询函数:

func Active(db *gorm.DB) *gorm.DB {
return db.Where("status = ?", "active")
}
func CreatedAfter(t time.Time) func(*gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where("created_at >= ?", t)
}
}
db.Scopes(Active, CreatedAfter(startAt)).Find(&users)

Scope 只负责构造查询,不应该偷偷执行查询、提交事务或修改全局状态。

批量读取#

err := db.Where("status = ?", "pending").
FindInBatches(&users, 500, func(tx *gorm.DB, batch int) error {
for _, user := range users {
// 处理当前批次
_ = user
}
return tx.Error
}).Error

批量读取适合导出、离线处理和数据修复;不要在回调里为每条记录再发一个查询,否则容易形成 N+1。

官方资料#

条件查询与分页
https://shirone.mysqil.com/posts/gorm/条件查询与分页/
作者
HikariTish
发布于
2026-07-30
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取