哪些分页scheme可以处理快速变化的内容列表?

如果您的内容排名可以快速更改,则分页难度会更大,而当这些排名不同的用户时则更难。 (让我们把无限滚动看作是链接不可见的分页types)。有两个难题:最上面的新增内容和重新排列的内容。

让我们忘记新增内容,并接受您必须刷新页面1才能看到它。 让我们也假装我们正在做纯粹的ORDER BY position ; 如果您按其他方式sorting,则可能必须使用窗口函数。 我们的页面每页有4行动物。 他们开始:

 +----+----------+-----------+ | id | position^| animal | +----+----------+-----------+ | 1 | 1 | Alpacas | | 2 | 2 | Bats | | 3 | 3 | Cows | | 4 | 4 | Dogs | | 5 | 5 | Elephants | | 6 | 6 | Foxes | | 7 | 7 | Giraffes | | 8 | 8 | Horses | +----+----------+-----------+ 

在我们拿到第1页之后,在我们拿到第2页之前,有很多东西在四处移动。 数据库现在是:

 +----+----------+-----------+ | id | position^| animal | +----+----------+-----------+ | 4 | 1 | Dogs | | 2 | 2 | Bats | | 1 | 3 | Alpacas | | 5 | 4 | Elephants | | 6 | 5 | Foxes | | 7 | 6 | Giraffes | | 3 | 7 | Cows | | 8 | 8 | Horses | +----+----------+-----------+ 

有三种常用的方法:

偏移/限制方法

这是典型的幼稚方法; 在Rails中, will_paginate和Kaminari是如何工作的。 如果我想抓取第2页,我会的

 SELECT * FROM animals ORDER BY animals.position OFFSET ((:page_num - 1) * :page_size) LIMIT :page_size; 

其中获得5-8行。 我永远不会看到大象,我会看到两头奶牛。

最后看到的ID方法

Reddit采取不同的方法。 客户端不是根据页面大小计算第一行,而是跟踪您所看到的最后一个项目的ID,如书签。 当你点击“下一步”时,他们开始从那个书签向前看:

 SELECT * FROM animals WHERE position > ( SELECT position FROM animals WHERE id = :last_seen_id ) ORDER BY position LIMIT :page_size; 

在某些情况下,这比页面/偏移量更好。 但在我们的案例中,最后看到的postDogs放大到了#1。 所以客户发送?last_seen_id=4 ,我的页面2是蝙蝠,羊驼,大象和狐狸。 我没有错过任何动物,但我看到蝙蝠和羊驼两次。

服务器端状态

HackerNews(和我们的网站,现在)通过服务器端的延续解决了这个问题。 他们为你保存整个结果集(或者至less提前几页?),以及“更多”链接引用这个延续。 当我获取第2页时,我要求“我的原始查询的第2页”。 它使用相同的偏移/限制计算,但由于它与原始查询相反,我根本不在乎现在已经移动了。 我看到大象,狐狸,长颈鹿和马。 没有dups,没有遗漏的物品。

缺点是我们必须在服务器上存储很多状态。 在HN,这是存储在RAM中,实际上这些延续通常会过期,然后才能按“更多”button,迫使您一路回到第1页find有效的链接。 在大多数应用程序中,可以将其存储在memcached中,甚至存储在数据库本身中(使用自己的表格,或者在Oracle或PostgreSQL中使用可保存的游标)。 根据您的应用程序,可能会有性能问题。 在PostgreSQL中,至less,你必须find一种方法来再次击中正确的数据库连接,这需要大量的粘滞状态或一些聪明的后端路由。

这是唯一的三种可能的方法吗? 如果没有,是否有计算机科学的概念,让我的谷歌果汁阅读这个? 有没有方法来近似连续方法而不存储整个结果集? 长期来看,存在复杂的事件stream/时间点系统,其中“我获取第1页时的结果集”永远是可推导的。 短… …?

解决scheme1:“ 哈克解决scheme

一个解决scheme可以包括你的客户跟踪已经看到的内容,例如ID列表。 每次您需要另一个页面时,您将此ID列表添加到您的服务器调用的参数。 您的服务器然后可以订购内容,删除已经看到的内容,并应用偏移来获得正确的页面。

我不会推荐它,但我坚持哈克 。 我只是把它写下来,因为它很快,可以满足一些需求。 这是我能想到的坏事:

1)在客户端需要做一些工作才能正确(在我的上面的句子中“已经看到”是什么意思,如果我去上一页怎么办?)

2)由此产生的订单不反映您的真实订购政策。 内容可以显示在第2页,虽然政策应该放在第1页。这可能会导致用户误解。 我们以先前的订购策略为例来说明堆栈溢出,这意味着最先提出的答案。 我们可以在第2页有6个upvotes的问题,而在第1页有4个upvotes的问题。这发生在当用户还在第1页时发生了2个或更多个upvotes。 – >对于用户来说可能是令人惊讶的。

解决scheme2客户端解决scheme”

这基本上是客户端对您所谓的“服务器端状态”的解决scheme。 只有跟踪服务器端的完整命令不够方便才有用。 如果项目列表不是无限的。

  • 打电话给你的服务器,以获得完整(有限)的订单清单+项目/页数
  • 保存在客户端
  • 直接通过您的内容的ID检索项目。

Oracle很好地处理这个问题。 只要光标处于打开状态,您可以根据需要多次读取,并且结果将始终反映光标打开的时间点。 它使用撤消日志中的数据虚拟回滚光标打开后提交的更改。

只要所需的回滚数据仍然可用,它就会工作。 最终,日志会被回收,并且回滚数据不再可用,因此存在一些限制,具体取决于日志空间,系统活动等。

不幸的是(国际海事组织),我不知道任何其他数据库是这样的作品。 我已经使用的其他数据库使用锁来确保读取一致性,如果您想要读取的一致性超过非常短的时间,这是有问题的。

我们现在要用服务器端的状态方法,在第一个查询上caching整个结果,所以我们总是返回一个一致的列表。 这将工作,只要我们的查询已经返回所有行; 最终我们将需要使用最近邻居的方法,这将无法正常工作。

但是我认为还有第四种可能性,只要:

  1. 你不需要保证没有重复,只有很高的可能性
  2. 只要你避免重复,你可以在卷轴上丢失一些内容

该解决scheme是“最后看到的ID”解决scheme的一个变体:让客户端保留一个,但5或10或20个书签 – 足够less,可以有效地存储它们。 查询结果如下所示:

 SELECT * FROM posts WHERE id > :bookmark_1 AND id > :bookmark_2 ... ORDER BY id 

随着书签数量的增长,出现这种情况的可能性迅速减小,因为(a)从所有n个书签上的某个点开始,但是(b)无论如何都看到了重复的内容,因为它们都被重新sorting。

如果今后出现漏洞或更好的答案,我会高兴地不接受这个答案。

晚会很晚,但是这里是我们试验过的东西。 我们正在使用连续加载,而不是用户之间来回的页面。

客户端build立一个所有显示的ID的列表,所以在第一组之后,它可能是:4,7,19,2,1,72,3

当我们加载更多的内容时,我们使用相同的sorting进行相同的查询,但将其添加到其中:WHERE id NOT IN(4,7,19,2,1,72,3)

NOT IN列表可以快速增长。 对我们来说,这不是一个问题,因为我们的内部工具通常不会有大量的结果。

我想添加另一个想法。 也许服务器端添加可以应用于此。 当用户search时,将所有他们得到的ID添加到带有search链接的表格中。 当客户想要更多时,只需提供search标识(或使用服务器端状态),查询就可以joinsearch数据。