为什么我们需要按键分页的工具支持?

大家好! 我是一名后端开发人员,用 Java + Spring 编写微服务。 我在 Tinkoff 的一个内部产品开发团队工作。

为什么我们需要按键分页的工具支持?

在我们的团队中,经常会出现优化 DBMS 中的查询的问题。 您总是希望速度更快一点,但您并不总是能够通过精心构建的索引来实现 — 您必须寻找一些解决方法。 在一次在网络上徘徊寻找使用数据库时合理优化的过程中,我发现 马库斯·温纳德 (Marcus Wynand) 的博客给您带来无尽的帮助,《SQL 性能解释》一书的作者。 这是一种罕见的博客类型,您可以在其中连续阅读所有文章。

我想为您翻译马库斯的一篇短文。 在某种程度上,它可以被称为一份宣言,旨在引起人们对根据 SQL 标准的偏移操作性能的古老但仍然相关的问题的关注。

有些地方我会补充作者的解释和评论。 我将把所有这些地方称为“大约”。 为了更清楚

小介绍

我想很多人都知道通过偏移量进行页面选择是多么有问题且缓慢。 您是否知道它可以很容易地被更高效的设计所取代?

因此,offset 关键字告诉数据库跳过请求中的前 n 条记录。 然而,数据库仍然需要按照给定的顺序从磁盘读取前n条记录(注意:如果指定则应用排序),只有这样才有可能返回从n+1开始的记录。 最有趣的是,问题不在于DBMS中的具体实现,而在于按照标准的原始定义:

…行首先根据然后通过删除中指定的行数来限制从一开始就…
-SQL:2016, Part 2, 4.15.3 派生表(注:目前使用最多的标准)

这里的关键点是 offset 采用单个参数 - 要跳过的记录数,仅此而已。 按照这个定义,DBMS 只能检索所有记录,然后丢弃不需要的记录。 显然,这种偏移量的定义迫使我们做额外的工作。 而且无论是 SQL 还是 NoSQL 都不重要。

只是再痛一点

偏移的问题还不止于此,原因如下。 如果在从磁盘读取两页数据之间,另一个操作插入了一条新记录,这种情况会发生什么?

为什么我们需要按键分页的工具支持?

当使用 offset 跳过前一页的记录时,在不同页的读取之间添加新记录的情况下,您很可能会得到重复项(注意:当我们使用 order by 结构逐页读取时,这是可能的,然后在我们的输出中间它可能会得到一个新条目)。

该图清楚地描绘了这种情况。 基读取前 10 条记录,然后插入一条新记录,这会将所有读取的记录偏移 1。然后基从接下来的 10 条记录中获取一个新页,并且不是从第 11 条记录开始,而是从第 10 条记录开始。 XNUMX、复制此记录。 还有与此表达式的使用相关的其他异常,但这是最常见的。

正如我们已经发现的,这些不是特定 DBMS 或其实现的问题。 问题在于根据 SQL 标准定义分页。 我们告诉 DBMS 要获取哪个页面或要跳过多少条记录。 数据库根本无法优化这样的请求,因为这方面的信息太少。

还值得澄清的是,这不是特定关键字的问题,而是查询语义的问题。 还有其他几种语法在其问题性质上是相同的:

  • offset 关键字如前所述。
  • 两个关键字 limit [offset] 的构造(尽管 limit 本身并没有那么糟糕)。
  • 根据行编号(例如 row_number()、rownum 等)按下限进行过滤。

所有这些表达式只是告诉您要跳过多少行,没有其他信息或上下文。

在本文后面,将使用 offset 关键字作为所有这些选项的摘要。

没有OFFSET的生活

现在让我们想象一下,如果没有这些问题,我们的世界会是什么样子。 事实证明,没有偏移量的生活并不那么困难:使用 select,您可以使用 where 中的条件仅选择那些我们尚未看到的行(注意:即那些不在上一页上的行)。

在这种情况下,我们从这样一个事实开始:选择是在有序集上执行的(好旧的 order by)。 由于我们有一个有序集,因此我们可以使用一个相当简单的过滤器来仅获取上一页最后一条记录后面的数据:

    SELECT ...
    FROM ...
    WHERE ...
    AND id < ?last_seen_id
    ORDER BY id DESC
    FETCH FIRST 10 ROWS ONLY

这就是该方法的全部原理。 当然,按多列排序时事情会变得更有趣,但想法仍然是一样的。 值得注意的是,这种设计适用于许多 NoSQL-决定。

这种方法称为查找方法或键集分页。 它解决了浮动结果问题(注意:前面描述的页面读取之间写入的情况),当然,我们都喜欢它,它比经典偏移工作得更快、更稳定。 稳定性在于请求处理时间不会与请求的表数量成正比增加(注:如果你想详细了解不同分页方法的工作原理,可以 看看作者的介绍。 您还可以在那里找到不同方法的比较基准)。

其中一张幻灯片 谈论那个当然,按键分页并不是万能的——它有其局限性。 最重要的是她不具备阅读随机页面的能力(注意:不一致)。 然而,在无限滚动的时代(注:在前端),这并不是这样的问题。 无论如何,指定点击页码在 UI 设计中都是一个糟糕的决定(注:文章作者的观点)。

工具呢?

由于缺乏对此方法的工具支持,按键分页通常不合适。 大多数开发工具(包括各种框架)不允许您准确选择分页的执行方式。

由于所描述的方法需要所使用的技术的端到端支持(从 DBMS 到在浏览器中执行无休止滚动的 AJAX 请求),这种情况变得更加严重。 您现在必须同时为所有页面指定一组键,而不是仅指定页码。

然而,支持按键分页的框架数量正在逐渐增长。 这是我们目前所拥有的:

(注:由于翻译时某些库自 2017-2018 年以来未更新,因此删除了一些链接。如果您有兴趣,可以查看原始来源。)

此刻就需要您的帮助。 如果您开发或支持使用分页的框架,那么我要求,我强烈建议,我恳求您为按键上的分页提供本机支持。 如果您有任何疑问或需要帮助,我将很乐意为您提供帮助(форум, Twitter, 联系表)(注:根据我与马库斯的经验,我可以说他非常热衷于传播这个话题)。

如果您使用现成的解决方案,您认为值得支持按键分页,请创建请求,甚至提供现成的解决方案(如果可能)。 您还可以链接到这篇文章。

结论

按键分页这样简单又有用的方法之所以没有普及,并不是因为它在技术上难以实现或者需要付出很大的努力。 主要原因是许多人习惯于查看和使用偏移 - 这种方法是由标准本身决定的。

因此,很少有人考虑改变分页方法,正因为如此,框架和库的工具支持发展得很差。 因此,如果无偏移分页的想法和目标离您很近,请帮助传播它!

来源: https://use-the-index-luke.com/no-offset
作者:马库斯·维南德

来源: habr.com

添加评论