API 分页探讨:offset 来分页真的有效率吗?
下面是一个小小的性能对比,先看看 offset 是如何工作:
=#?explain?analyze?select?id?from?product?offset?10000?limit?100;
QUERY?PLAN????????????????????????????????????????????????????????????
Limit??(cost=1114.26..1125.40?rows=100?width=4)?(actual?time=39.431..39.561?rows=100?loops=1)
->??Seq?Scan?on?product??(cost=0.00..1274406.22?rows=11437243?width=4)?(actual?time=0.015..39.123?rows=10100?loops=1)
Planning?Time:?0.117?ms
Execution?Time:?39.589?ms
再看看 where (cursor) 语句如何工作:
=#?explain?an 《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》开源 alyze?select?id?from?product?where?id?>?10000?limit?100;
QUERY?PLAN??????????????????????????????????????????????????????????
Limit??(cost=0.00..11.40?rows=100?width=4)?(actual?time=0.016..0.067?rows=100?loops=1)
->??Seq?Scan?on?product??(cost=0.00..1302999.32?rows=11429082?width=4)?(actual?time=0.015..0.052?rows=100?loops=1)
Filter:?(id?>?10000)
Planning?Time:?0.164?ms
Execution?Time:?0.094?ms
这是几个数量级的差异! 当然,实际的差异取决于表的大小以及过滤器和存储的实现。有一篇不错的文章 (1) 提供了更多的技术信息,里面有 ppt,性能比较见第 42 张幻灯片。
(1) https://use-the-index-luke.com/no-offset
当然,用户不会按 id 来检索商品,而是会按一些相关性来查询(然后按 id 作为关联字段)。在现实世界中,需要根据你的业务来决定该怎么做。订单可以按 id 排序(因为它是单调增加的)。购买清单可以按 wishlist 时间排序。在我们的案例中,产品来自 ElasticSearch,自然支持游标的特性。
我们可以看到的一个不足是,使用无状态的 API, 无法支持翻到“上一页”这样的功能。所以在面向用户界面中,如果有 prev/next 或者 “直接进入第 10 页” 这样的按钮,就没有办法绕过前面提到的 offset/limit 这种实现。但是在其他情况下,使用基于游标的分页可以极大地提高性能,特别是在真正的大表和真正的深度分页上。
英文原文:
https://solovyov.net/blog/2020/api-pagination-design/
HackerNews 评论:
https://news.ycombinator.com/item?id=25547716
HN 网友 et1337:
使用游标的另一个原因是避免由于并发编辑而导致元素重复或跳过的问题,比如你使用 offset 正在第 10 页上,而有人在第 1 页上删除了一个项目,则整个列表会移动,你可能会意外跳过第 11 页上的一行数据。同样,如果有人在第 1 页上添加了一条记录而你正在第 10 页上,第 10 页中的一项也会重复显示在第 11 页上。
游标优雅地回避了这些问题。
HN 网友 chrismorgan:
有时候,你需要一个游标,这样你就可以从你刚才的地方继续前进,而不用担心新的记录进来扰乱你的分页。
有时你想要基于位置的查询,因为你明确地希望所有的东西都是位置的。
有时你想把这两种技术结合起来,例如,如果你跳到一个大的、不断变化的列表中间,然 Java 开源项目【ali1024.coding.net/public/P7/Java/git】 后想在刚才的位置之后检索下一批结果。
我喜欢 JMAP 最后的设计(https://tools.ietf.org/html/rfc8620#page-45):你可以指定一个位置整数,或者一个锚 ID 和可选的 anchorOffset 整数。锚是游标的一种实现,它使用结果集中一个实体 ID,而不是一个可以嵌入其他信息(比如 coroutine 地址)的不透明类型,,它有一个明显的优点,就是可以由客户端控制。
HN 网友?vincnetas
我认为作者在使用 OFFSET 时忽略了一些关键点。至少 postgres 文档对此有明确的的说法(https://www.postgresql.org/docs/13/queries-limit.html)
When?using?LIMIT,?it?is?important?to?use?an?ORDER?BY?clause?that?constrains?the?result?rows?into?a?unique?order.?Otherwise?you?will?get?an?unpredictable?subset?of?the?query's?rows.?You?might?be?asking?for?the?tenth?through?twentieth?rows,?but?tenth?through?twentieth?in?what?ordering?
看起来作者提供的分页查询没有考虑到排序,这意味着第 100 页上的项目的 ID 大于 10000,但顺序未定义。
explain?analyze?select?id?from?product?where?id?>?10000?limit?100
HN 网友 boulos
鉴于对“游标”一词的重用感到困惑,我更喜欢 Google 为分页所使用的术语:页面令牌和页面大小,详细可以参阅:
https://google.aip.dev/158
图片
结局:总结+分享
看完美团、字节、腾讯这三家的一二三面试问题,是不是感觉问的特别多,可能咱们真的又得开启面试造火箭、工作拧螺丝的模式去准备下一次的面试了。
开篇有提及我可是足足背下了 Java 互联网工程师面试 1000 题,多少还是有点用的呢,换汤不换药,不管面试官怎么问你,抓住本质即可!能读到此处的都是真爱
Java 互联网工程师面试 1000 题
而且从上面三家来看,算法与数据结构是必备不可少的呀,因此我建议大家可以去刷刷这本左程云大佬著作的 《程序员代码面试指南 IT 名企算法与数据结构题目最优解》,里面近 200 道真实出现过的经典代码面试题。
程序员代码面试指南--IT 名企算法与数据结构题目最优解
其余像设计模式,建议可以看看下面这 4 份 PDF(已经整理)
更多的 Java 面试学习笔记如下,关于面试这一块,我额外细分出 Java 基础-中级-高级开发的面试+解析,以及调优笔记等等等。。。
以上所提及的全部 Java 面试学习的 PDF 及笔记,如若皆是你所需要的,那么都可发送给你!
评论