
在项目初期的列表页中,分页查询通常非常简单:分页、查数据、返回结果,一切都很顺畅。page 方法看起来像是一个已经帮你处理好所有事情的“黑盒”能力。
但只要业务需求向前推进一步——加入排序、增加筛选条件或组合多种查询条件,情况就开始变得复杂起来。这种变化往往不是突然爆发的,而是随着时间推移慢慢显现。
1. 初期:分页查询几乎没有负担
项目初期的分页查询,代码通常如下所示:
page(new Page<>(pageNo, pageSize), wrapper);
这个阶段的特点是:
分页的职责非常单一:限制返回数量 + 控制页码跳转。此时,page 方法几乎不会引发任何性能或稳定性问题。
2. 加入排序:打破分页的“稳定性”
第一个常见的变化是支持前端动态排序,例如:
- 按创建时间倒序排列
- 按某个数值字段(如价格、评分)排序
- 支持升序与降序的切换
代码中开始出现这样的语句:
wrapper.orderByDesc("create_time");
这时,一个很容易被忽略的问题浮出水面:分页的准确性高度依赖排序的稳定性。
如果排序字段具有以下特征:
- 不是唯一键(存在重复值)
- 数据更新频繁(值会动态变化)
- 存在大量相同值(如状态字段)
那么在翻页过程中,很容易出现:
- 同一条数据出现在相邻的不同页面
- 翻页时数据“缺失”或“重复”
问题的根源不在于分页本身,而在于排序规则缺乏确定性。
3. 增加筛选条件:分页查询开始“变形”
接下来,业务通常会增加各种筛选条件:
- 状态过滤(如仅显示“已审核”)
- 类型筛选
- 时间范围查询
- 关键词模糊搜索
于是,查询条件包装器(Wrapper)中开始出现大量的条件判断逻辑:
if (status != null) {
wrapper.eq("status", status);
}
if (keyword != null) {
wrapper.like("name", keyword);
}
此时,分页的角色已经发生了转变:它不再仅仅是“查询一页数据”,而是在一个动态变化的数据集合中截取其中一段。这会带来两个现实问题:
- 查询条件稍有变化,原有页码就可能失效
- 前端的翻页逻辑变得难以预测
你会开始观察到一些看似“偶发”的现象:
- 翻到某一页时突然没有数据
- 查询条件未变,但数据总数前后不一致
- 同样的参数,返回的结果却不同
4. 排序与筛选叠加:SQL 复杂度快速上升
当排序和筛选条件同时存在时,生成的分页 SQL 通常会包含:
- 多条件的 WHERE 子句
- 多字段的 ORDER BY 子句
- LIMIT / OFFSET 分页限制
在数据量较小时,这些问题并不明显。但一旦数据量增长,以下问题开始集中出现:
- 查询响应时间变慢
- COUNT 查询明显耗时
- 索引开始“失效”或无法有效利用
分页查询,逐渐成为列表页中最重的一条 SQL。
5. COUNT 查询:被低估的性能瓶颈
许多人关注分页查询慢,却忽略了一个关键事实:page 方法实际上执行了两条 SQL 语句。
- 一条用于查询当前页的数据
- 另一条用于查询满足条件的总数(COUNT)
当筛选条件复杂时,COUNT 查询往往比数据查询本身还要慢。尤其是在以下场景:
- 涉及多表 JOIN
- 条件动态拼接
- 包含多个模糊搜索
这时你会发现:页面加载很慢,但实际返回的数据量并不多。分页的性能问题,很多时候症结不在“查数据”,而在“算总数”。
6. 业务计算字段排序:问题进一步加剧
在真实业务中,排序字段并不总是数据库中的原始字段。例如:
- 按业务状态优先级排序(如“进行中”优先于“待处理”)
- 按自定义权重评分排序
- 按组合规则(如“评分 × 热度”)排序
这类基于业务计算的排序一旦引入,分页查询的负担会明显加重:
- SQL 语句可读性下降
- 数据库索引基本失效
- 分页结果的稳定性进一步降低
此时的 page,已经很难再被当作一个简单的工具来使用。
7. 后期:分页查询成为系统“脆弱点”
在项目后期,典型的分页查询通常具备以下特征:
- 条件繁多且动态
- 排序规则复杂
- 被多个功能模块复用
- 修改一处可能影响多个页面
于是,代码库中常见的一种状态是:
- 功能能正常运行
- 但无人敢轻易修改
- 出现问题后往往只能回滚
分页查询,反而成了系统中最“脆弱”的一段业务逻辑。
小结
page 方法本身并没有变复杂,复杂的是围绕它不断叠加的排序规则和筛选条件。
当你发现分页查询开始出现:
这往往不是因为分页实现有误,而是因为它承担了超出其原始职责的业务复杂度。理解这一点,许多分页相关的问题在早期就有迹可循。

对于分页查询的优化,需要从索引设计、查询重写、缓存策略等多方面入手。更多关于数据库性能优化和后端架构的深入讨论,欢迎访问云栈社区的技术论坛。
