## 数据结构
是一个大 List 拆成多个小 List 的问题
```Java
[ 一共 30 个 item,一页 5 个,所以共 6 页,当前在第 3 页 ]
┌───────────────────────────────────────────────────────────────────────┐
│ □ □ □ □ □ | □ □ □ □ □ | ■ ■ ■ ■ ■ | □ □ □ □ □ | □ □ □ □ □ | □ □ □ □ │
└───────────────────────────────────────────────────────────────────────┘
```
三个核心参数:
1. 一共有多少数据?totalItems **这是得后端查的**
2. 间隔多少分一次?pageSize **前端用户定的**
3. 现在在哪一段?currentPage **前端用户定的**
## 后端需要做的事情
1. 校验当前页不能小于 1,不能大于最后一页
```java
int curPage = Math.min(params.getCurPage(), (total + params.getPageSize() - 1) / params.getPageSize());
curPage = Math.max(curPage, 1);
```
1. 把 curPage 和 pageSize 换算成 sql 中的 start 和 offset,这样才能真正减少查询量
2. 一共有多少数据?只有知道总 count,前端才能显示一共有多少页。返回给前端分页信息,前端拿到数据后,可根据 `pagination` 的信息进行分页展示或分页控件渲染。
```json
{
"data": [ ... 数据列表 ... ],
"pagination": {
"totalItems": 30,
"pageSize": 5,
"currentPage": 3,
"totalPages": 6
},
"status": "ok"
}
```
## 常见问题与思考
1. **数据更新导致分页结果不一致**
- 在高并发或数据频繁更新场景下,前后两次分页查询之间,数据可能发生变化,造成总数或数据位置变化。常见做法是"最终一致性",即不强求前后端数据一模一样,但保持整体一致即可。
- 如需保证严格一致,可以考虑基于游标(Cursor-Based)或时间戳的分页方式,但实现更为复杂。
2. **大数据量下的性能问题**
- 当数据量非常大时,`LIMIT offset, size` 在 MySQL 等数据库中的执行效率可能变差,因为数据库需要扫描和跳过大量数据。
- 可以考虑 **Keyset Pagination(基于主键或索引做"从某个位置往后取")**、**分段聚合** 等方式优化性能。
- 当仅需要查询有限范围的页面时,结合业务规则或用户交互方式进行限制也是一种可行方案。
3. **分页排序**
- 大多数应用会使用 `ORDER BY` 进行排序,如果没有合适的索引或排序字段,分页越往后 SQL 的开销会越大,可能会带来较大的数据库压力。需要在需求和数据库设计层面做好索引优化。
4. **缓存与数据库一致性**
- 如果使用缓存(如 Redis)来存储 `totalItems` 或数据列表,需要考虑缓存过期策略、更新机制以及是否与数据库实时同步。
- 一般地,如果对实时性要求不高,可以定期刷新缓存减少数据库压力;若对实时性要求高,则需要更精确的增量更新或失效处理。