”使用mysql limit 分页就行了,分页查询用得着四种写法吗? "
这可能是很多人的想法。的确mysql limit offset是可以胜任分页的,但是另外三种办法在其他场景表现更好。
大家最熟悉的就是如下的分页截图,返回总页数、支持页数跳转。
Limit Offset分页
例如每页10条,查询第三页 ,mysql limit 部分为: limit 20,10;
前段每次需要指定 每页数量,当前页数。由后端拼接查询SQL,构建mysql limit 子句。
limit offset 分页有几个特性。
- 支持页数跳转。用户选定第几页,就跳转到对应的页面。
- 返回记录总条数。用户可以看到共几页,一共多少条数据。
limit offset 实现简单,但是存在缺陷。当出现深度分页时,MySQL 需要扫描大量数据才能找到指定页的数据,造成慢查询,增加增加数据库的内存和cpu负载, 如果这个深度分页的QPS比较高,无疑最终会拖垮数据库。在流量高峰期,如果深度分页的慢查询较多,毫无疑问,会增加其他SQL耗时,影响其他业务场景。
值得说明的是,分页查询必须指定排序方式。如果没有指定排序方式,使用分页很难保证数据不会出现重复。 如果实在没有排序字段,可以使用主键ID。
我曾经犯过类似错误,在使用ElasticSearch替换lucene 做检索时,发现lucene和ElasticSearch返回的结果一直不一致,排查了很久,才意识到必须指定排序方式,否则使用分页查询会导致数据重复。
那么Limit Offset就没有其他方式避免深度分页吗? 答案是可以
Limit 指定主键Id过滤
如果在查询条件上加上主键Id是不是就可以了呢?
改进前:select * from students where xxxx查询条件xxx order by id desc limit 1000,20;
改进后: select * from students where xxxx查询条件xxx AND id <lastMinId order by id desc limit 20;
改进后在原有的查询条件上 指定了lastMinId,上一轮最小的Id。在查询下一页时,把上一页的最小id 传下去,这样保证后续查到的列表都是小于lastMinId。从源头上增加了查询条件,减少了mysql的检索范围,每次都只获取前二十条数据。
这样就高枕无忧了吗?当然不
这种方式前提条件是排序方式可以指定主键Id,如果根据其他排序方式,就不能这样做了。
这种方式还有其他应用场景吗? 最佳的场景就是从下游批量获取大量数据时,可以根据主键id进行排序,每次选择最大的N条,或最小的N条。
每次查询都更新主键id范围,这样就能避免深度分页,查询全部的数据。
各位兄弟姐妹们,如果看完有收获,关注、点赞、收藏转发,怎么方便怎么来,小弟在此拜谢了。你的一次点赞就足够让我开心一上午。比心
HasMore 滚动查询
有的业务场景例如用户App端的购买记录页,用户只能每页滚动查询购买记录,无需知道购买订单总数。 针对这个场景,有什么优化呢?
在之前的limit Offset分页时,需要返回记录总数,前端也要确定查询总页数。 滚动分页查询则无需获取总页数,无需查询总数。减少了一次select count(*)的查询。
只需要在每一次分页查询时,每页数量+1 即可。例如每页10条,可以指定11条,如果真查出来11条,hasMore=true,上游需要继续查,否则hasMore=false,上游无需再分页查询。
ElasticSearch 分页查询
ES 比较适用于检索条件复杂、实时性要求比较低的查询场景。 例如B端的各类复杂查询条件检索场景以及 C端用户关键词订单列表搜索等场景。查询耗时基本在100ms以上、甚至1s以上。
值得一提的是需要mysql数据异构到ES,ES加载进索引也有1s左右延迟,数据从产生到ES索引延迟比较高。
ElasticSearch 支持分页查询,和Mysql Limit offset 类似。同时也强烈建议,使用分页查询时,指定排序方式。
SearchRequest searchRequest = new SearchRequest(index);
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//计算出记录起始下标
int from = (pageNum - 1) * pageSize;
// 起始记录下标,从0开始
sourceBuilder.from(from);
//每页显示的记录数
sourceBuilder.size(pageSize);
和mysql类似,ES也有深度分页的查询压力,默认的最大查询深度max_result_window=1W, 阈值可以修改。在低频的B端查询场景,可以根据需要适当调整阈值。
以上4种分页查询方式没有最好,需要针对不同的场景选择最合适的。
各位兄弟姐妹们,如果看完有收获,关注、点赞、收藏转发,怎么方便怎么来,小弟在此拜谢了。你的一次点赞就足够让我开心一上午。比心
我的开源项目
最后夹带一点私货,五阳最近花了3个月的时间完成一个开源项目。
开源3周以来,已有近 230 多个关注和Fork
Gitee:gitee.com/juejinwuyan…
GitHub github.com/juejin-wuya…
开源平台上有很多在线商城系统,功能很全,很完善,关注者众多,然而实际业务场景非常复杂和多样化,开源的在线商城系统很难完全匹配实际业务,广泛的痛点是
- 功能堆砌,大部分功能用不上,需要大量裁剪;
- 逻辑差异点较多,需要大量修改;
- 功能之间耦合,难以独立替换某个功能。
由于技术中间件功能诉求较为一致,使用者无需过多定制化,技术中间件开源项目以上的痛点不明显,然而电商交易等业务系统虽然通用性较多,但各行业各产品的业务差异化极大,所以导致以上痛点比较明显
所以我在思考,有没有一个开源系统,能提供电商交易的基础能力,能让开发者搭积木的方式,快速搭建一个完全契合自己业务的新系统呢?
- 他们可以通过编排和配置选择自己需要的功能,而无需在一个现成的开源系统上进行裁剪
- 他们可以轻松的新增扩展业务的差异化逻辑,不需要阅读然后修改原有的系统代码!
- 他们可以轻松的替换掉他们认为垃圾的、多余的系统组件,而不需要考虑其他功能是否会收到影响
开发者们,可以择需选择需要的能力组件,组件中差异化的部分有插件扩展点能轻松扩展。或者能支持开发者快速的重新写一个完全适合自己的新组件然后编排注册到系统中?
memberclub 就是基于这样的想法而设计的。 它的定位是电商类交易系统工具箱, 以SDK方式对外提供通用的交易能力,能让开发者像搭积木方式,从0到1,快速构建一个新的电商交易系统!
具体介绍可参见
Gitee开源地址:gitee.com/juejinwuyan…
GitHub开源地址 : github.com/juejin-wuya…
在这个项目中你可以学习到 SpringBoot 集成 以下框架或组件。
- Mybatis、Mybatis-plus 集成多数据源
- Sharding-jdbc 多数据源分库分表
- redis/redisson 缓存
- Apollo 分布式配置中心
- Spring Cloud 微服务全家桶
- RabbitMq 消息队列
- H2 内存数据库
- Swagger + Lombok + MapStruct
同时你也可以学习到以下组件的实现原理
- 流程引擎的实现原理
- 扩展点引擎实现原理
- 分布式重试组件实现原理
- 通用日志组件实现原理 参考:juejin.cn/post/740727…
- 商品库存实现原理: 参考:juejin.cn/post/731377…
- 分布式锁组件: 参考:
- Redis Lua的使用
- Spring 上下文工具类 参考: juejin.cn/post/746927…