推荐用 page + size,后端转为 offset + limit;可校验边界防越界,避免前端传过大 offset 导致全表扫描;大数据量时应改用游标分页(如 WHERE created_at
分页参数该用
page+size还是offset+limit?推荐在 API 层统一用
page和size,后端转换为offset和limit再传给数据库。这样对前端更友好,也便于做默认值和边界校验。常见错误是直接把
offset暴露给前端:用户可能传offset=9999999导致全表扫描或超时;而page=1000&size=20更容易做上限拦截(比如限制page )。
page从 1 开始(不是 0),避免前端混淆size应设硬性上限(如最大 100),防止恶意拉取大量数据- 若业务需精确跳转(如“加载更多”无限滚动),可额外支持
cursor模式,但不要替代page/sizego 后端如何安全解析并校验分页参数
别用
strconv.Atoi直接转,要结合net/http的ParseForm和自定义校验逻辑。重点防空值、负数、超限、非数字。func parsePagination(r *http.Request) (page, size int, err error) { r.ParseForm() page, err = strconv.Atoi(r.FormValue("page")) if err != nil || page < 1 { return 0, 0, fmt.Errorf("invalid page: must be >= 1") } size, err = strconv.Atoi(r.FormValue("size")) if err != nil || size < 1 || size > 100 { return 0, 0, fmt.Errorf("invalid size: must be between 1 and 100") } return page, size, nil }注意:
r.FormValue对重复 key 只取第一个,如果需要支持多值(如page[]=1&page[]=2),得用r.Form["page"]并手动处理。立即学习“go语言免费学习笔记(深入)”;
数据库查询时 offset 性能问题怎么绕开
mysql / PostgreSQL 中
OFFSET越大越慢,尤其在千万级表上。当page > 1000或offset > 10000时,必须考虑优化。
- 用主键/时间戳做游标分页(
WHERE created_at )- 加覆盖索引,确保
ORDER BY和WHERE字段都在索引中- 对管理后台类场景,可缓存总条数和高频页的
offset映射(如预计算 page 500 → offset 9980)不要依赖
select count(*)实时算总数——高并发下它会成瓶颈。改用近似值(如 MySQL 的EXPLaiN行数)或异步更新的统计表。返回分页元信息时字段命名要一致且带单位
前端最怕字段名来回变:
total、totalCount、total_items混用。建议固定为:
total:总记录数(int)page:当前页码(从 1 开始)size:每页条数pages:总页数((total + size - 1) / size)has_next/has_prev:布尔值,比算page 更直观示例响应结构(jsON):
{ "data": [...], "pagination": { "total": 1247, "page": 3, "size": 20, "pages": 63, "has_next": true, "has_prev": true } }游标分页则不用
pages和page,改用next_cursor和prev_cursor字符串,且不返回total—— 这点很容易被忽略,但必须明确告知前端。
