GORM多级栏目分页核心是明确分页对象:查子栏目需加parent_id过滤后Limit/Offset;查递归文章应避免IN大列表,优先限制层级或用原生CTE;Count必须复用相同Where条件确保总数准确。

GORM 做多级栏目分页,核心不是“能不能”,而是「要不要用 Limit/Offset 直接套」——多数情况下,直接套会翻车。
多级栏目(比如:新闻 > 国内 > 政策解读)本质是树形结构,分页时你真正要查的往往不是「第 N 页的所有栏目」,而是「某一级下子栏目列表」或「某栏目下所有文章」。这两个场景的分页逻辑、性能瓶颈、SQL 写法完全不同。
查某一级子栏目(如 parentId = 123)怎么分页?
这是最常见也最容易写错的场景。错误做法是把整个栏目表当平铺列表分页,忽略层级关系。
正确做法是先过滤出目标父级下的直接子项,再分页:
DB.Where("parent_id = ?", parentID).Limit(p.PageSize).Offset((p.PageNum-1)*p.PageSize).Find(&categories)- 必须加
parent_id条件,否则分页结果和树结构完全脱节 - 注意
parent_id字段名要和模型定义一致,比如有的项目叫pid或parent_id,别硬写死 - 如果前端传的是路径字符串(如
/news/domestic/policy),得先查出对应栏目的ID,再查其子项——这步不能省,否则无法做精准层级控制
查某栏目下所有文章(含递归子栏目)怎么分页?
这才是真坑点。GORM 本身不支持递归 CTE(Common Table Expression),MySQL 8.0+、PostgreSQL 才原生支持,而 Limit/Offset 在递归查询里直接失效。
常见但危险的做法:
- 先查出所有子栏目 ID(递归查一遍),再用
IN查文章:WHERE category_id IN (1,2,5,8,...)—— ID 太多会触发 MySQL 的max_allowed_packet或生成超长 SQL - 用两次查询:第一次查 ID 列表(带分页),第二次查文章 —— 这会导致分页错位(ID 分页了,但文章数不等于 ID 数)
更稳妥的解法是业务层收口:
- 只允许查「当前栏目 + 一级子栏目」的文章,不递归到底;用
IN拼 2~3 层 ID,可控 - 如果真要全量递归,改用原生 SQL 配合数据库的 CTE(例如 PostgreSQL 的
WITH RECURSIVE),GORM 只负责执行:db.Raw(sql, args...).Scan(&articles) - 避免在应用层做递归+内存分页,大数据量下 OOM 风险极高
Count() 和分页总数怎么对得上?
很多人写了 Limit/Offset,却忘了总数也要走同样条件,否则前端显示「共 200 条,当前第 3 页」,实际第 3 页查不到数据。
- 总数查询必须复用完全相同的
Where条件(包括parent_id、软删除标记deleted_at IS NULL等) - 别用
db.Model(&Category{}).Where(...).Count(&total)后再手动算页数,容易漏条件 - 推荐封装一个函数,把条件提取成可复用的
*gorm.DB实例,再分别调Count和Find - 注意
Count()返回的是int64,别直接转int,超大表可能溢出
PageNum 参数通吃。


















