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

我们的团队经常在DBMS中提出查询优化的问题。 您总是希望更快一点,但是您无法总是通过设计良好的索引来解决-您必须寻找一些解决方法。 在网上徘徊以寻求合理的优化时,我发现了SQL Performance Explained的作者Marcus Vinand的博客,它是无限有用的博客 。 这是一种非常罕见的博客,您可以在其中连续阅读所有文章。
我想为您翻译Marcus的一篇简短文章。 在某种程度上,它可以被称为宣言,旨在引起人们对根据SQL标准进行偏移操作性能的古老但仍然相关的问题的关注。
在某些地方,我将为作者提供一些解释和说明。 为了清楚起见,我将所有此类位置都指定为“大约”。
小介绍
我认为许多人都知道通过偏移量进行分页选择会产生多大的问题和抑制作用。 但是您知道吗,可以用更高效的设计完全替代它吗?
因此,offset关键字告诉数据库跳过请求中的前n个条目。 但是,数据库仍必须从磁盘读取前n条记录,并以指定的顺序读取(注意:如果指定了记录,则应用排序),只有在此之后,才可以返回从n +1开始的记录。 最有趣的是,问题不在DBMS的具体实现中,而是在根据标准的初始定义中:
...首先根据<order by子句>对行进行排序,然后通过从开头删除<result offset子句>中指定的行数来进行限制...
-SQL:2016年,第2部分,4.15.3派生表(注意:现在是最常用的标准)
关键是偏移量只有一个参数-要跳过的记录数,仅此而已。 按照此定义,DBMS只能获取所有记录,然后丢弃不必要的记录。 显然,这种偏移量的定义迫使您要做额外的工作。 而且,无论是SQL还是NoSQL都没有关系。
多一些痛苦
胶印问题不止于此,这就是原因。 如果另一个操作在从磁盘读取两页数据之间插入了一条新记录,在这种情况下会发生什么?

如果使用offset来跳过前一页的记录,则在读取不同页面的操作之间添加新记录的情况下,很可能会得到重复(注意:当我们使用order by构造逐页读取时,这是可能的,然后在输出中间获得新记录)。
该图清楚地描述了这种情况。 该库读取前10条记录,然后插入新记录,该记录将所有读取的记录移位1。然后该库从接下来的10条记录中获取新页,并且不是从第11条开始,而是从第10条开始,复制该记录。 使用此表达式还有其他异常,但这是最常见的。
正如我们已经发现的,这些不是特定DBMS或其实现的问题。 问题是根据SQL标准对分页的定义。 我们告诉DBMS获取哪个页面或跳过多少记录。 基地根本无法优化这样的请求,因为对此的信息太少了。
还值得说明的是,这不是特定的关键字问题,而是查询语义。 在问题方面,有几种语法是相同的:
- 偏移关键字,如前所述。
- 两个关键字的构造限制[offset](尽管limit本身还不错)。
- 基于行编号的下限过滤(例如row_number(),rownum等)。
所有这些表达式仅表示要跳过多少行,没有其他信息或上下文。
本文后面的内容中,将offset关键字用作所有这些选项的概括。
没有偏移的生活
现在想象一下,如果没有所有这些问题,我们的世界将会是什么样。 事实证明,没有偏移的生活并不那么复杂:您可以使用where的条件来仅选择那些我们尚未看到的行(请注意:即那些不在最后一页上的行)。
在这种情况下,我们基于选择是在有序集合(好的旧命令依据)上执行的事实。 由于我们有一个有序的集合,因此我们可以使用一个相当简单的过滤器来仅获取前一页最后一条记录后面的数据:
SELECT ... FROM ... WHERE ... AND id < ?last_seen_id ORDER BY id DESC FETCH FIRST 10 ROWS ONLY
这就是这种方法的全部原理。 当然,当按许多列进行排序时,一切都会变得更加有趣,但是想法是相同的。 重要的是要注意,这种构造适用于许多N o S Q L解决方案。
这种方法称为搜索方法或键集分页。 它解决了浮动结果的问题(请注意:页面读取之间的书写情况,如前所述),当然,我们大家都喜欢,它比传统的偏移更快,更稳定。 稳定性在于查询处理时间不会与所请求表的数量成比例地增加(请注意:如果您想了解有关分页方法的更多信息,可以浏览作者的演示文稿 。在那里,您还可以使用不同的方法找到比较基准)。
其中一张幻灯片告诉您 ,分页当然不是万能的,它有其自身的局限性。 最重要的-她没有能力阅读随机页面(注意:不一致)。 但是,在无限滚动的时代(请注意:在前端),这不是问题。 在开发UI时,在任何情况下,为单击指定页码都是一个错误的决定(注意:文章作者的观点)。
那工具呢?
由于缺少此方法的仪器支持,因此通常不适合使用分页。 大多数开发工具(包括各种框架)都无法选择执行分页的方式。
所描述的方法需要所用技术的端到端支持,从DBMS到在浏览器中无休止滚动地执行AJAX请求,这一事实使情况更加恶化。 现在,您不仅要指定页码,还必须一次为所有页面指定一组键。
但是,支持关键分页的框架数量正在逐渐增加。 现在是这样的:
(注意:由于一些链接在翻译时未从2017-2018年进行更新,因此删除了某些链接。如果有兴趣,可以查看源代码。)
正是在这一刻需要您的帮助。 如果您正在开发或支持某种以某种方式使用分页的框架,那么我要求我恳请您为关键的分页提供本地支持。 如果您有任何疑问或需要帮助,我将很乐意为您提供帮助( 论坛 , Twitter , 联系表格 )(注:根据我在Marcus的经验,我可以说他非常热衷于传播这一主题)。
如果您使用现成的解决方案,认为对按键分页是值得支持的,则创建请求,甚至提供交钥匙解决方案(如果可能)。 您也可以在链接中指定此文章。
结论
这种简单而有用的方法(例如密钥分页)不普及的原因不是技术上难以实施或需要付出很大的努力。 主要原因是许多人习惯于查看和使用偏移量-这种方法由标准本身规定。
结果,很少有人考虑更改分页方法,因此,框架和库的工具支持发展得很差。 因此,如果您接近无忧分页的想法和目标,请帮助传播它!
资料来源: https : //use-the-index-luke.com/no-offset
发言者:Markus Winand