find查多条记录需传切片地址且类型为[]user,配合where过滤、游标分页、select字段控制和preload预加载,避免空结果、n+1、oom或慢查询。

用 Find 查多条记录最直接
查多条记录,Find 是 GORM 最常用也最直观的方法。它会自动把结果填充进切片,前提是切片元素类型与模型匹配。
常见错误是传入空结构体指针或未初始化切片,比如写成 var user User; db.Find(&user) —— 这只会查一条,且容易 panic;或者写成 var users []User; db.Find(&users) 却忘了加取地址符 &,导致数据无法写入。
- 必须传切片的地址:
db.Find(&users),不是db.Find(users) - 切片需提前声明为
[]User类型,不能是[]interface{}(否则 GORM 无法映射字段) - 如果数据库无匹配记录,
users会是空切片(len(users) == 0),不会报错
Where + Find 组合过滤条件
实际开发中几乎不会裸调 Find,基本都配合 Where 加条件。GORM 的 Where 支持字符串、结构体、map 多种写法,但行为有差异。
例如 db.Where("age > ?", 18).Find(&users) 是安全的占位符写法;而 db.Where(User{Age: 18}).Find(&users) 会生成 WHERE age = 18,且忽略零值字段(如 Name: "" 不会进 WHERE);用 map 则所有键值都会参与查询,db.Where(map[string]interface{}{"age": 18, "name": ""}) 会生成 WHERE age = 18 AND name = ''。
- 字符串方式最灵活,适合动态条件,但要注意 SQL 注入风险,务必用
?占位符 - 结构体方式自动跳过零值,适合表单搜索场景
- map 方式不跳过空字符串或零值,需手动过滤无效条件
分页查多条:别只靠 Limit/Offset
查列表基本都要分页,但直接写 db.Limit(10).Offset(20).Find(&users) 在大数据量下性能差,且 Offset 越大越慢。更稳妥的做法是结合主键或时间字段游标分页。
比如按 ID 递增分页:db.Where("id > ?", lastID).Order("id ASC").Limit(10).Find(&users),避免深分页抖动。同时注意,Limit 和 Offset 必须在 Find 前调用,顺序反了无效。
-
Limit传 0 表示不限制条数(但不推荐,易拖垮数据库) -
Offset传负数会被忽略,不会报错,容易误以为生效了 - 分页时记得用
Count单独查总数:db.Model(&User{}).Where(...).Count(&total),别依赖len(users)
查多条时字段选择与预加载
默认 Find 会 SELECT 所有字段,但多数接口不需要全部字段,用 Select 可减少传输和内存开销;关联数据则要用 Preload 避免 N+1 查询。
例如:db.Select("id, name, email").Find(&users) 只查三列;db.Preload("Profile").Preload("Orders").Find(&users) 会一次性 JOIN 或 IN 查询关联表。注意 Preload 的字段名必须是 GORM 定义的关联字段名(如 struct tag 中的 gorm:"foreignKey:UserID" 对应的字段),不是数据库外键名。
-
Select后不能再用Preload(GORM 会忽略),需先Preload再Select - 如果用了
Select但漏掉主键字段,Preload可能失效,因为 GORM 依赖主键做关联匹配 - 聚合查询(如 COUNT、GROUP BY)不能和
Find混用,得换Raw或Scan
Find 的参数类型、Where 的构造方式、分页策略和字段控制这四点,任一选错都可能引发空结果、N+1、OOM 或慢查询——尤其是线上环境里,这些坑往往在流量上来后才暴露。











