联合搜索分页需避免count失效、排序不稳定和offset性能雪崩:count须用新会话或子查询;order by必须含索引匹配的多字段(如created_at desc, id desc);大数据量应改用游标分页。

联合搜索条件的分页不是加个 Where 就完事——它会让 Count 失效、让排序不稳定、让 OFFSET 性能雪崩,三者叠加就等于线上翻车。
Why Count(&total) 总是返回 0 或极小值?
因为你在同一个 *gorm.DB 实例上先拼了搜索条件 + 分页,再调 Count,GORM 会把前面的 Limit 和 Offset 一并带上。结果就是查出来 10 条,Count 也只数这 10 条。
- 正确做法:用
db.Session(&gorm.Session{NewDB: true})隔离会话,再Model(&User{}).Where(...).Count(&total) - 更稳妥的写法是手写子查询:
db.Raw("SELECT COUNT(*) FROM (SELECT 1 FROM users WHERE ?) t", conditions).Scan(&total),尤其当Where含JOIN或IN子句时 - 别在事务里反复查
Count和Find——两次查询之间数据变了,总数和当前页对不上是常态
ORDER BY 必须带索引字段组合,不能只靠 created_at
联合搜索(比如 WHERE status = ? AND category = ? AND name LIKE ?)下,如果只写 .Order("created_at DESC"),数据库无法稳定排序:相同时间戳的记录顺序由存储位置决定,一刷新就跳条或重复。
- 必须补二级排序字段,优先选主键:
.Order("created_at DESC, id DESC") - 确保
created_at和id在联合索引中,且顺序匹配 ORDER BY(如INDEX idx_status_cat_created_id (status, category, created_at, id)) - 如果搜索条件含
name LIKE "%xxx%",这个字段没法走索引,那排序字段更要强依赖id,避免全表扫描后排序不可控
Limit+Offset 在联合搜索下性能断崖式下跌
当 WHERE 条件过滤后只剩几百条,但原始表有百万行,OFFSET 10000 仍要扫描并丢弃前一万行——索引再好也救不了。
- 数据量超 10 万行且并发写入频繁时,直接切游标分页:前端传上一页最后一条的
created_at和id,后端写WHERE status = ? AND category = ? AND (created_at, id) - 游标分页必须配合覆盖索引,否则
(created_at, id)排序字段不在索引里,照样回表+排序 - 别在游标分页里用
Preload——先查出 ID 列表,再用IN批量加载关联数据,否则 N+1 变成 N×M
参数校验不是可选项,是防 DOS 的第一道门
前端传 page=abc 或 size=999999,GORM 不拦,MySQL 会硬扛,然后慢查询打满连接池。
- 用
c.ShouldBindQuery(&p)绑定结构体,比c.Query()更早拦截非法类型 -
Page≤ 0 时强制设为 1;Size超过配置上限(如 100)就截断,不报错也不 panic - 所有搜索字段(
name、category等)都要做长度限制和 SQL 注入防护,LIKE参数必须用fmt.Sprintf("%s%%", keyword)拼接,禁止用户直输通配符
最常被忽略的是:联合搜索 + 分页时,Count 查询的 WHERE 条件必须和主查询完全一致,连空格、参数顺序都不能差——哪怕只是多一个 TrimSpace,两个查询就可能命中不同索引,导致总数和列表对不上。











