点击上方“程序员蜗牛g”,选择“设为星标”跟蜗牛哥一起,每天进步一点点
程序员蜗牛g大厂程序员一枚 跟蜗牛一起 每天进步一点点31篇原创内容公众号
在业务开发中,分页是一个再常见不过的需求。
无论是电商平台的商品列表,还是社交媒体的动态流,分页都是提升用户体验的重要手段。然而,当数据量逐渐增大时,分页问题就会悄然演变成一个“隐形杀手”——深分页问题。
今天,我们就来深入探讨这个让无数开发者头疼的问题,并详细分析几种解决方案,尤其是Elasticsearch(ES)如何优雅地解决深分页问题。
一、什么是深分页问题?
深分页问题通常出现在需要查询大量数据的场景中。假设你有一个包含1000万条记录的表,用户想要查看第9999页的数据(每页10条)。传统的分页查询可能会使用类似以下的SQL语句:
这条语句的逻辑是跳过前99990条记录,然后返回接下来的10条。看起来很简单,对吧?但问题在于,数据库在执行这条语句时,实际上需要扫描前99990条记录,然后再返回10条。随着偏移量的增加,查询的性能会急剧下降,甚至可能导致数据库崩溃。
这就是深分页问题的核心:偏移量越大,查询效率越低。
二、深分页问题的解决方案
既然深分页问题如此棘手,那我们该如何应对呢?以下是几种常见的解决方案:
1. 游标分页(Cursor-based Pagination)
游标分页是一种基于唯一标识(如ID或时间戳)的分页方式。它的核心思想是记录上一次查询的最后一条记录的标识,下一次查询时直接从该标识之后开始查询。例如:
这种方式避免了偏移量的计算,性能非常稳定。但它有一个限制:用户无法直接跳转到某一页,只能一页一页地往下翻。
2. 子查询优化
在某些数据库中,可以通过子查询的方式优化深分页。例如:
这种方式通过子查询先定位到偏移量的起始位置,然后再查询数据,性能会比直接使用LIMIT offset
,size好一些。但它仍然无法完全解决深分页的性能问题。并且这种方法只适用于 ID 是正序的。在复杂分页场景,往往需要通过过滤条件,筛选到符合条件的 ID,此时的 ID 是离散且不连续的。
另一种基于子查询的优化写法(不依赖于id有序,通用性更强)
3. 延迟查询
延迟关联的优化思路,跟子查询的优化思路其实是一样的 :都是希望减少回表。不同点是,延迟关联使用了inner join
代替子查询。
阿里巴巴《Java 开发手册》中也有对应的描述:
利用延迟关联或者子查询优化超多分页场景。
子查询的作用:
子查询 (SELECT id FROM table ORDER BY id LIMIT 99990, 10
) 的目的是快速定位到目标分页的主键范围(id)。这里直接获取了目标分页的 10 条记录的 id,而不是像原始 SQL 那样只获取一个起始点。
这样可以避免主查询中因 id >= ...
导致的范围扫描,直接定位到目标记录。
主查询的作用:
主查询通过 INNER JOIN
将子查询返回的 id 与原表进行关联,只返回这些 id 对应的完整记录。
这种方式可以利用索引快速定位到目标记录,减少主查询的扫描范围。
4. 缓存分页数据
对于一些不经常变动的数据,可以将分页结果缓存起来。例如,使用Redis缓存前几页的数据,减少数据库的压力。但这种方式只适用于数据更新频率较低的场景。
5. 业务限制
以京东 web 端为例,根据关键词搜索历史订单,时间维度默认为近三个月,以年为单位允许用户手动切换,但不允许查询全量数据。
除此之外还有各大搜索网站在分页主件上做了限制:
百度
6. Elasticsearch的Search After
接下来,我们重点介绍Elasticsearch如何解决深分页问题。
三、为什么ES可以解决深分页问题?
Elasticsearch(ES)是一个分布式的搜索引擎,天生适合处理海量数据的查询。它提供了多种分页方式,其中最适合解决深分页问题的是Search After。
基本原理
es维护一个实时游标,它以上一次查询的最后一条记录为游标,方便对下一页的查询,它是一个无状态的查询,因此每次查询的都是最新的数据。
由于它采用记录作为游标,因此 SearchAfter要求doc中至少有一条全局唯一变量(每个文档具有一个唯一值的字段应该用作排序规范)
ES的Search After机制与游标分页类似,但它更加强大。它的核心思想是:基于上一页的最后一条记录的排序值,作为下一页查询的起始点。 这种方式完全避免了偏移量的计算,因此性能非常稳定。
举个例子,假设我们有一个索引存储了用户的订单数据,我们需要分页查询这些订单。使用Search After的方式如下:
第一次查询:
返回的结果中,每条记录都会包含排序字段的值(如order_date
和_id
)。
第二次查询时,使用上一页最后一条记录的排序值作为起始点:
通过这种方式,ES可以高效地返回下一页的数据,而无需扫描前面的所有记录。
Search After的优势
- 性能稳定:无论查询第几页,性能都不会下降。
- 适合海量数据:ES的分布式架构可以轻松处理亿级甚至更大规模的数据。
- 灵活性高:支持多字段排序,适用于复杂的业务场景。
Search After的局限性
- 无法跳页:和游标分页一样,用户只能一页一页地往下翻。
- 依赖排序字段:必须指定一个唯一的排序字段(如_id),否则可能会导致分页结果不准确。
总结
深分页问题是业务开发中一个常见的性能瓶颈,尤其是在数据量庞大的场景下。传统的LIMIT offset
, size
方式虽然简单,但在深分页时性能极差。
通过游标分页、子查询优化、缓存分页等方式,我们可以在一定程度上缓解这个问题。而Elasticsearch的Search After
机制,则为我们提供了一种更加优雅和高效的解决方案。
如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。
关注公众号:woniuxgg,在公众号中回复:笔记 就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!