5月28日 01:10

GORM 中 First、Find、Where 等常用查询方法有哪些区别?

GORM 是 Go 语言中最流行的 ORM 库,查询方法是日常开发中使用频率最高的 API。掌握 First、Find、Where 等方法的区别和使用场景,是 GORM 面试的核心考点。

检索单条记录:First、Last、Take 的区别

这三种方法都会自动添加 LIMIT 1,且记录不存在时返回 ErrRecordNotFound,但生成的 SQL 不同:

方法排序方式生成 SQL 示例
First主键升序SELECT * FROM users ORDER BY id LIMIT 1
Last主键降序SELECT * FROM users ORDER BY id DESC LIMIT 1
Take不排序SELECT * FROM users LIMIT 1
go
var user User // 按主键升序取第一条 db.First(&user) // 按主键降序取第一条 db.Last(&user) // 不排序取一条 db.Take(&user) // 按主键查询(First 内联条件) db.First(&user, 10) // SELECT * FROM users WHERE id = 10

面试追问:如何避免 ErrRecordNotFound?

使用 Find 替代:db.Limit(1).Find(&user),Find 找不到记录时不报错,只返回空结果。

检索多条记录:Find

go
var users []User // 查询全部 db.Find(&users) // SELECT * FROM users // 内联条件查询 db.Find(&users, []int{1, 2, 3}) // WHERE id IN (1,2,3) // struct 条件(零值字段会被忽略) db.Find(&users, User{Name: "John"}) // WHERE name = "John"

注意:用 struct 做条件时,零值字段(如 Age: 0、Active: false)不会出现在 WHERE 子句中。需要查询零值字段,改用 map:

go
db.Where(map[string]interface{}{"Name": "John", "Age": 0}).Find(&users)

条件查询:Where、Or、Not

Where — 最常用的条件构造器

go
// 字符串条件(参数化防注入) db.Where("name = ?", "John").First(&user) db.Where("name = ? AND age >= ?", "John", 18).Find(&users) // map 条件 db.Where(map[string]interface{}{"name": "John", "age": 30}).Find(&users) // struct 条件(零值字段忽略) db.Where(&User{Name: "John"}).Find(&users)

Or 和 Not

go
// Or 条件 db.Where("name = ?", "John").Or("name = ?", "Jane").Find(&users) // Not 条件 db.Not("name = ?", "John").Find(&users)

常用条件操作符

go
// IN 查询 db.Where("id IN ?", []int{1, 2, 3}).Find(&users) // LIKE 模糊查询 db.Where("name LIKE ?", "%John%").Find(&users) // BETWEEN 范围查询 db.Where("age BETWEEN ? AND ?", 18, 30).Find(&users)

排序、分页与字段选择

go
// 排序 db.Order("age DESC").Find(&users) db.Order("age DESC, name ASC").Find(&users) // 分页(Limit + Offset) db.Offset(10).Limit(10).Find(&users) // 第2页,每页10条 // 选择特定字段(减少数据传输量) db.Select("name", "email").Find(&users) db.Select("name, email").Find(&users)

聚合与单列提取

go
// Count 计数 var count int64 db.Model(&User{}).Where("age > ?", 18).Count(&count) // Pluck 提取单列值 var names []string db.Model(&User{}).Pluck("name", &names)

面试追问:Count 在链式调用中的位置?

Count 会覆盖 SELECT 列,必须放在链式调用最后。且 Count 之后不能再链式调用 Find 等方法。

高级查询方法

FirstOrInit 和 FirstOrCreate

go
// 找到返回记录,找不到初始化一个空实例(不写入数据库) var user User db.Where("name = ?", "John").FirstOrInit(&user) // 找到返回记录,找不到创建一条新记录 db.Where("name = ?", "John").FirstOrCreate(&user)

Group 和 Having

go
type Result struct { Role string Count int64 } var results []Result db.Model(&User{}).Select("role, count(*) as count"). Group("role"). Having("count > ?", 5). Find(&results)

Distinct 去重

go
db.Distinct("name").Find(&users)

SubQuery 子查询

go
// WHERE age > (SELECT AVG(age) FROM users) db.Where("age > ?", db.Model(&User{}).Select("AVG(age)")).Find(&users)

Joins 关联查询

go
// 内连接 db.Joins("LEFT JOIN orders ON orders.user_id = users.id").Find(&users) // 预加载关联(避免 N+1 查询) db.Preload("Orders").Find(&users)

Scopes 复用查询逻辑

go
func Active(db *gorm.DB) *gorm.DB { return db.Where("active = ?", true) } func OlderThan(age int) func(db *gorm.DB) *gorm.DB { return func(db *gorm.DB) *gorm.DB { return db.Where("age > ?", age) } } // 链式复用 db.Scopes(Active, OlderThan(18)).Find(&users)

原生 SQL

go
// Raw 查询返回数据 db.Raw("SELECT * FROM users WHERE age > ?", 18).Scan(&users) // Exec 执行不返回数据的语句 db.Exec("UPDATE users SET age = age + 1 WHERE id = ?", 1)

软删除对查询的影响

如果模型启用了软删除(gorm.DeletedAt),所有查询方法会自动添加 WHERE deleted_at IS NULL。如需查询包含已删除的记录:

go
db.Unscoped().Where("age > ?", 18).Find(&users) // 包含软删除记录 db.Unscoped().Find(&users) // 查询全部(含已删除)

链式调用的注意事项

GORM 的查询方法是链式调用,但要注意:

  • Session 方法会创建新的会话,之后的操作不影响之前的链
  • Clauses 方法可以复写 GORM 内部的 Clause 构建器
  • 多个 Where 调用会叠加 AND 条件
  • Or 只与上一个 Where 组合,不与所有条件组合
go
// 多 Where 叠加 AND db.Where("age > ?", 18).Where("name = ?", "John").Find(&users) // WHERE age > 18 AND name = "John" // Or 只与上一个 Where 组合 db.Where("name = ?", "John").Or("name = ?", "Jane").Where("age > ?", 18).Find(&users) // WHERE (name = "John" OR name = "Jane") AND age > 18

常见面试考点总结

考点关键结论
First vs TakeFirst 按 PK 排序,Take 不排序
First vs FindFirst 找不到报 ErrRecordNotFound,Find 返回空
struct 查询零值陷阱struct 零值字段被忽略,用 map 替代
Count 的位置必须放在链式调用最后
软删除影响自动加 deleted_at IS NULL,Unscoped 跳过
避免 N+1用 Preload 预加载关联
参数化查询用 ? 占位符防 SQL 注入
FirstOrCreate找到返回,找不到自动创建
标签:Gorm