first按主键升序取第一条,适合查最早/最小id记录;take不排序取数据库返回的第一条,适合任意匹配场景;last按主键降序取最后一条;查单条必须用first/take/last而非find,且需显式处理gorm.errrecordnotfound错误。

用 First 还是 Take?搞清语义再选
查单条记录最常踩的坑,就是默认用 First 却没意识到它按主键升序找第一条——如果表里没主键、或主键不是你想查的字段(比如用 uuid 当主键但想按 email 查),First 返回的可能完全不是你预期的那条。
Take 更中性:不排序,直接取满足条件的第一条(数据库引擎返回顺序),适合“随便拿一条匹配的”场景;First 适合“取最早创建/最小ID的那条”;Last 则是最大ID/最新创建。
- 按条件查邮箱唯一用户,用
Where("email = ?", email).First(&user)是安全的,因为通常email建了唯一索引,结果只有一条 - 查状态为
"pending"的任意一条任务,用Where("status = ?", "pending").Take(&task)更合理 - 如果明确要最新提交的,得加
Order("created_at DESC").First(&item),光靠First不保证时序
Find 和 First 混用导致 record not found 错误
很多人写 db.Where(...).Find(&user) 想查单条,但 Find 是为切片设计的——即使只查到一条,它也尝试往切片里塞,而你传的是单个结构体变量,GORM 会报 record not found 或 panic。
正确做法只用 First/Take/Last 查单条;要用 Find 就必须传指针到切片:var users []User; db.Where(...).Find(&users)。
- 错误写法:
db.Where("id = ?", 123).Find(&user)→ 类型不匹配,GORM 不知道你要单条还是多条 - 正确写法:
db.Where("id = ?", 123).First(&user),且记得检查err是否等于gorm.ErrRecordNotFound - 如果不确定是否存在,别用
First后直接用user.ID,先判 err
查不到时的 err 处理不能只靠 == nil
GORM 对“查不到”的处理很特别:它不把 record not found 当成严重错误,而是返回一个可判断的 error。如果你只写 if err != nil,会漏掉“查到零条”的正常情况,也可能掩盖真正的数据库错误(比如连接失败)。
必须显式区分:
- 查不到:
errors.Is(err, gorm.ErrRecordNotFound)(Go 1.13+)或err == gorm.ErrRecordNotFound - 真出错了:比如表不存在、权限不足,
err是其他类型,需要单独处理(如打日志、告警) - 用
FirstOrInit或FirstOrCreate前,务必确认 WHERE 条件足够精确,否则可能意外创建多条重复记录
带关联的单条查询:小心 N+1 和空指针
用 Preload 查单条并加载关联,看起来简单,但容易忽略两件事:预加载字段是否真的存在、以及结构体字段是否已初始化。
例如 User 有 Profile 关联,但数据库里某用户的 profile_id 是 NULL,Preload("Profile").First(&user) 后 user.Profile 是 nil,不是空结构体——直接访问 user.Profile.Nickname 会 panic。
- 安全做法:查完先判空,
if user.Profile != nil { ... } -
Joins("Profile")走 INNER JOIN,会过滤掉没有 Profile 的用户,和Preload行为不同,别混用 - 如果只查关联字段,用
Select("users.*, profiles.nickname").Joins("left join profiles...")更省资源,避免 GORM 自动构造嵌套结构
gorm.ErrRecordNotFound 就直接用结果,或者以为 First 总是返回“你想要的那一条”。











