大数跨境
0
0

深分页问题:为什么你的分页查询会卡住?解决方案来了!

深分页问题:为什么你的分页查询会卡住?解决方案来了! 供应链架构师
2025-11-27
0
导读:深分页问题:为什么你的分页查询会卡住?解决方案来了!
架构师(JiaGouX)
我们都是架构师!
架构未来,你来不来?






前言



在业务开发中,分页是一个再常见不过的需求。无论是电商平台的商品列表,还是社交媒体的动态流,分页都是提升用户体验的重要手段。然而,当数据量逐渐增大时,分页问题就会悄然演变成一个“隐形杀手”——深分页问题。

今天,我们就来深入探讨这个让无数开发者头疼的问题,并详细分析几种解决方案,尤其是Elasticsearch(ES)如何优雅地解决深分页问题。





一、什么是深分页问题?



深分页问题通常出现在需要查询大量数据的场景中。假设你有一个包含1000万条记录的表,用户想要查看第9999页的数据(每页10条)。传统的分页查询可能会使用类似以下的SQL语句:

SELECT * FROM table ORDER BY id LIMIT 9999010;

这条语句的逻辑是跳过前99990条记录,然后返回接下来的10条。看起来很简单,对吧?但问题在于,数据库在执行这条语句时,实际上需要扫描前99990条记录,然后再返回10条。随着偏移量的增加,查询的性能会急剧下降,甚至可能导致数据库崩溃。

这就是深分页问题的核心:偏移量越大,查询效率越低。





二、深分页问题的解决方案



既然深分页问题如此棘手,那我们该如何应对呢?以下是几种常见的解决方案:

1. 游标分页(Cursor-based Pagination)

游标分页是一种基于唯一标识(如ID或时间戳)的分页方式。它的核心思想是记录上一次查询的最后一条记录的标识,下一次查询时直接从该标识之后开始查询。例如:

SELECT * FROM table WHERE id > last_id ORDER BY id LIMIT 10;

这种方式避免了偏移量的计算,性能非常稳定。但它有一个限制:用户无法直接跳转到某一页,只能一页一页地往下翻。

2. 子查询优化

在某些数据库中,可以通过子查询的方式优化深分页。例如:

SELECT * FROM table WHERE id >= (SELECT id FROM table ORDER BY id LIMIT 999901LIMIT 10;

这种方式通过子查询先定位到偏移量的起始位置,然后再查询数据,性能会比直接使用LIMIT offset,size好一些。但它仍然无法完全解决深分页的性能问题。并且这种方法只适用于 ID 是正序的。在复杂分页场景,往往需要通过过滤条件,筛选到符合条件的 ID,此时的 ID 是离散且不连续的。

另一种基于子查询的优化写法(不依赖于id有序,通用性更强

select t1.* FROM account t1, (select id from account where update_time >= '2020-09-19' limit 10000010) t2 where t1.id = t2.id;

3. 延迟查询

延迟关联的优化思路,跟子查询的优化思路其实是一样的 :都是希望减少回表。不同点是,延迟关联使用了inner join代替子查询。

阿里巴巴《Java 开发手册》中也有对应的描述:

利用延迟关联或者子查询优化超多分页场景。

SELECT t.*
FROMtableAS t
INNERJOIN (
    SELECTid
    FROMtable
    ORDERBYid
    LIMIT9999010-- 获取目标分页的主键范围
AS sub ON t.id = sub.id;
子查询的作用:

子查询 (SELECT id FROM table ORDER BY id LIMIT 99990, 10) 的目的是快速定位到目标分页的主键范围(id)。这里直接获取了目标分页的 10 条记录的 id,而不是像原始 SQL 那样只获取一个起始点。

这样可以避免主查询中因 id >= ... 导致的范围扫描,直接定位到目标记录。

主查询的作用:

主查询通过 INNER JOIN 将子查询返回的 id 与原表进行关联,只返回这些 id 对应的完整记录。

这种方式可以利用索引快速定位到目标记录,减少主查询的扫描范围。

4. 缓存分页数据

对于一些不经常变动的数据,可以将分页结果缓存起来。例如,使用Redis缓存前几页的数据,减少数据库的压力。但这种方式只适用于数据更新频率较低的场景。

5. 业务限制

以京东 web 端为例,根据关键词搜索历史订单,时间维度默认为近三个月,以年为单位允许用户手动切换,但不允许查询全量数据。

除此之外还有各大搜索网站在分页主件上做了限制:

百度

Google

Github

6. Elasticsearch的Search After

接下来,我们重点介绍Elasticsearch如何解决深分页问题。





三、为什么ES可以解决深分页问题?



Elasticsearch(ES)是一个分布式的搜索引擎,天生适合处理海量数据的查询。它提供了多种分页方式,其中最适合解决深分页问题的是Search After。

基本原理

es维护一个实时游标,它以上一次查询的最后一条记录为游标,方便对下一页的查询,它是一个无状态的查询,因此每次查询的都是最新的数据。

由于它采用记录作为游标,因此 SearchAfter要求doc中至少有一条全局唯一变量(每个文档具有一个唯一值的字段应该用作排序规范)

ES的Search After机制与游标分页类似,但它更加强大。它的核心思想是:基于上一页的最后一条记录的排序值,作为下一页查询的起始点。 这种方式完全避免了偏移量的计算,因此性能非常稳定。

举个例子,假设我们有一个索引存储了用户的订单数据,我们需要分页查询这些订单。使用Search After的方式如下:

第一次查询:

{
  "size": 10,
  "sort": [
    {"order_date""asc"},
    {"_id""asc"}
  ]
}

返回的结果中,每条记录都会包含排序字段的值(如order_date_id)。

第二次查询时,使用上一页最后一条记录的排序值作为起始点:

{
  "size": 10,
  "sort": [
    {"order_date""asc"},
    {"_id""asc"}
  ],
  "search_after": [last_order_date, last_id]
}

通过这种方式,ES可以高效地返回下一页的数据,而无需扫描前面的所有记录。

Search After的优势
  • 性能稳定:无论查询第几页,性能都不会下降。
  • 适合海量数据:ES的分布式架构可以轻松处理亿级甚至更大规模的数据。
  • 灵活性高:支持多字段排序,适用于复杂的业务场景。
Search After的局限性
  • 无法跳页:和游标分页一样,用户只能一页一页地往下翻。
  • 依赖排序字段:必须指定一个唯一的排序字段(如_id),否则可能会导致分页结果不准确。





总结



深分页问题是业务开发中一个常见的性能瓶颈,尤其是在数据量庞大的场景下。传统的LIMIT offsetsize方式虽然简单,但在深分页时性能极差。

通过游标分页、子查询优化、缓存分页等方式,我们可以在一定程度上缓解这个问题。而Elasticsearch的Search After机制,则为我们提供了一种更加优雅和高效的解决方案。

如喜欢本文,请点击右上角,把文章分享到朋友圈
如有想了解学习的技术点,请留言给若飞安排分享

因公众号更改推送规则,请点“在看”并加“星标”第一时间获取精彩技术分享

·END·

相关阅读:


作者:秋天的一阵风

来源:juejin.cn/post/7472717843571163170

版权申明:内容来源网络,仅供学习研究,版权归原创者所有。如有侵权烦请告知,我们会立即删除并表示歉意。谢谢!

架构师

我们都是架构师!



关注架构师(JiaGouX),添加“星标”

获取每天技术干货,一起成为牛逼架构师

技术群请加若飞:1321113940 进架构师群

投稿、合作、版权等邮箱:admin@137x.com

【声明】内容源于网络
0
0
供应链架构师
各类跨境出海行业相关资讯
内容 1846
粉丝 0
供应链架构师 各类跨境出海行业相关资讯
总阅读1.3k
粉丝0
内容1.8k