最近有面试过也遇到了问关于深分页问题,在这里简单从MySQL、ES等方面分享一下自己对该问题认识和总结。
可以从ES定义上来划分浅分页和深分页的边界,即页数超过10000页为深分页,少于10000页为浅分页。
关于MySQL深分页问题先看一下以下这条SQL语句:
SELECT * FROM t_order WHERE user_id = 1001 LIMIT 1000000, 10
--------------------------------------------------------------
查询时间:89.15s
如果t_order表数据超过1000w直接执行以上SQL会非常慢,这就是典型深分页问题。
简单说一下关于在MySQL上limit执行过程:是会先扫描offset+n行,然后再丢弃掉前offset行,返回后n行数据。
从这里就可以看出来以上SQL之所有会慢主要问题是因为做了大量(offset+n行)的回表操作,则关于MySQL深分页问题主要是大量回表问。
select * from table_name where id > 最大id limit 10000, 10
具体实现是:每一次查询把本批数据的最大ID传给前端,查询下一页的时候再带到后台。
这种方式有一个很大的局限性问题就是它只适用于主键ID自增的情况,分布式ID(雪花算法等)则不行,还需要考虑连续型字段datetime,sequence 等。
该方案可以极大优化MySQL深分页问题,能优化到ms级别。
刚刚有提到MySQL深分页问题主要是回表问题,那么解决回表问题肯定得用到索引覆盖技术,在《高性能MySQL》书上叫这种解决深分页问题的方法叫做延迟关联。
具体解决方案的可以看一下我的这篇博文:延迟关联
SELECT * FROM t_order t
RIGHT JOIN (
SELECT id
FROM t_order
WHERE user_id = 1001
LIMIT 1000000,50
) tmp ON tmp.id = t.id
--------------------------------------------------------------
该方案解决了id不需要自增的问题,但是优化性能较低,在数据量大的情况下只能优化到秒级别的。
ES浅分页(From,Size)过程:ES是基于数据分片的,假设有3个分片,from=100,size=10。则会根据排序规则从3个分片中各取回100条数据数据,然后汇总成300条数据后选择最后面的10条数据。
且From最大默认值是10000,如果超过这个值就会报错,虽然这个最大值可以修改,但是不推荐,因为会极大的影响查询效率。
为了满足深度分页的场景,ES 提供了 scroll 的方式进行分页读取。
原理上是对某次查询生成一个游标 scroll_id , 后续的查询只需要根据这个游标去取数据,直到结果集中返回的 hits 字段为空,就表示遍历结束。scroll_id 的生成可以理解为建立了一个临时的历史快照,在此之后的增删改查等操作不会影响到这个快照的结果。
scroll方式官方的建议并不是用于实时的请求,因为每一个 scroll_id 不仅会占用大量的资源(特别是排序的请求),而且是生成的历史快照,对于数据的变更不会反映到快照上。这种方式往往用于非实时处理大量数据的情况,比如要进行数据迁移或者索引变更之类的
ES5之后提供search_after,它是假分页方式,基本原理是根据上一页的最后一条,确定下一页的位置。
可以支持实时检索,而且性能也比scroll好,但是它只能一直下一页。
通过以上对比可以看出来ES的深分页方案也并没解决常见的需求问题。
一般数据量级别没有达到时根本就不存在什么深分页问题,那么我们可以看一下淘宝和京东他们是怎么解决深分页问题:
固定分页:
无论有多少页数据都对检索后数据只取前100页。
滚动分页:
只支持上一页或下一页操作。