分页场景下、查询数据后排除部分内容导致不足一页的 问题怎么解决?

2019-09-23 11:58:24 +08:00
 rizon

场景:

  1. 数据分页展示
  2. 数据在从库里查询出来后,会进行一些业务逻辑排除掉不需要的数据,该操作不能从数据库层直接处理,只能代码处理

问题:

以分页 10 页为例,从库里获取 10 条数据,进行业务过滤后,只剩 5 条,因为不足 10 条还要去库里再获取一些数据,再进行业务排除,然后判断是否够了 10 条,以此循环处理。 这种处理方式显然过于笨拙和性能太差。 所以有什么解决办法吗?

抛砖引玉:

方案 1: 对于被排除的数据不真的排除掉,而是返回给前端,让前端通过设置 disable 等标记,这算是产品对技术的妥协了。

方案 2: 页面采用瀑布流的方式,由前端控制数据加载。但具体的实现上没有太多好的想法。

5056 次点击
所在节点    程序员
20 条回复
licoycn
2019-09-23 11:59:43 +08:00
难道不是按照过滤规则在数据库查找数据么
licoycn
2019-09-23 12:01:43 +08:00
比如要查找年龄大于 18 的,你不可能把先无条件查询 10 条再进行到业务层判断,然后再去查询凑够 10 条,为何不直接在数据库查询年龄大于 18 的用户 10 条返回给业务层这样呢
SmiteChow
2019-09-23 12:09:25 +08:00
给个提示,已排除 xx 条数据
rizon
2019-09-23 12:26:45 +08:00
@licoycn #1 场景里页提到了,因为很多原因,是不能从数据库层直接做过滤的。
rizon
2019-09-23 12:28:46 +08:00
这个问题还有个思路,就是和数据库建立长连接,然后流式的一条条的读取数据,没读取一条做一次业务过滤,直到凑够一页数据后,断开数据库连接。

但是这个怎么实现?
用原生的数据库操作的时候,返回的那个 resultSet 是每执行一次 next()才从库里读取一条数据吗?还是在执行第一次 next 的时候就全部读取了?
geeglo
2019-09-23 13:03:42 +08:00
笨方法,分页还是按 10 条分,取 100 条。
wisej
2019-09-23 13:06:25 +08:00
@rizon
pageNum 代表符合过滤条件的 num,totalNum 代表已经拿到过的数据总 num (用于后续分页 Offset )
每次按照一定的比例从数据库拿数据(譬如每页 10 条数据,按 1.5 倍,那就拿 15 条数据),得到 resultSet,调用 next(),totalNum++,符合条件,则 pageNum++。
情况 1. 这 15 条数据包含 10 条有效数据,当 pageNum=10 时,返回数据集,pageNum 重置,下一次拿 resultSet 的时候 Offset(totalNum)
情况 2. 这 15 条数据不足构成 10 条有效数据,pageNum 不重置 0,再拿一次 resultSet,逻辑同上
gfreezy
2019-09-23 13:09:41 +08:00
我们用到的有 3 中方法:

1. 直接把过滤后的全量数据冗余一份,翻页直接在过滤后的表里面查询
2. 数据不用 MySQL 存,改成存 ElasticSearch。ES 一般都能满足各种业务条件过滤和筛选
3. 在翻页函数外面再套一个翻页函数。每次翻页的时候先取一页数据,然后过滤完看下数据够不够。如果数据不够,再往下多查一页数据,一直到满足的条数为止。外层的翻页函数用起来就跟普通的翻页函数一样。

1、2 的难点在于保证冗余数据与原始数据的一致性。3 的难点在于客户端往下翻页的时候应该从哪个地方往下查询。
someonedeng
2019-09-23 13:11:20 +08:00
先过滤,再分页
gfreezy
2019-09-23 13:11:31 +08:00
1、2 简单做就是定期脚本,或者异步任务,要一致性高得靠 binlog 同步数据。

3 我们的方案是改造 cursor,抛弃传统的 offset 和 limit,改成 cursor 和 size 的组合。cursor 直接用 json 存储需要在两次翻页直接传递的数据。
optional
2019-09-23 13:11:48 +08:00
用 id 分页 where id > :prev limit 10
Vegetable
2019-09-23 13:15:40 +08:00
别闹了,你怎么分页呢?
分页的基础就是能计算全部符合条件数据的数量,如果这个必须应用层来做,你只能全表扫描之后由应用层分页.

btw,如果页数标记可以缓存,你也可以把过滤结果缓存了,这个问题也不存在了.
rizon
2019-09-23 13:20:52 +08:00
@gfreezy #8
@wisej #7
对于先查出来如果不够再补上这种思路,存在的最致命问题就是,如果页面要查询第二页数据,那么你必须从第一条数据开始算,才能算出真正的第二页数据。。。因为你直接用页码计算的 offset 肯定是不对的。
所以大概也没什么好的办法了吧。只能冗余或者让产品妥协了吧。
gfreezy
2019-09-23 13:26:33 +08:00
@rizon 从第一条开始算是因为你们客户端使用 offset 来确定位置。改成 cursor,并且把第一次翻页的位置 encode 在 cursor 里面返回给客户端,客户端请求下一页的时候再把 cursor 原封不动的传到服务器。这样就能实现在两次翻页之间传递数据。第二次翻页就可以知道上一次翻到哪里了,直接从上一次的位置继续往下翻
LinJunzhu
2019-09-23 14:09:54 +08:00
mark,最近也遇到这个问题
StarkWhite
2019-09-23 15:56:37 +08:00
如果业务上允许的话,可以用存储过程
summmset
2019-09-23 16:08:51 +08:00
#3 的方法,配合方案 2,前端瀑布流,每次加载更多按固定翻页加载,加载完成,提示已加载多少条
Mirt
2019-09-23 16:57:45 +08:00
不知道 lz 这个查询有没有排序,如果有的话
lz 可以从数据库里一次捞 x 条数据( x 大于 10,可以预估下一次性取多少条数据能最后过滤出 10 条)
如果捞了 x 条数据过滤后还没有满足十条再去查询一次 x 条,评估好这个 x 的大小应该可以做到 1,2 次查询就能查出来结果。
查询下一页的时候,可以让前端将最后一条数据的 id 传给你,这样你获取数据的时候就可以直接查询 >id 的部分,这样也能保证翻页的时候也是 1,2 次查询就能出结果。
还有数据库长链接的问题,这个你们应该是有用一些数据库连接池的技术吧,不然每次查询都建立一次链接那得多慢。
howell5
2019-09-23 17:09:30 +08:00
在真正返回的数据 List 外增加一个字段 pageOffset,这个 offset 才是真正的 offset,如果出现一开始的 10 条还差 5 条,那就从数据库再取后面 10 条前面 valid 的 5 条,假设第二次取得 10 条里前 6 条就有 5 条 valid , 那么这时候给前端的 pageOffset 是 16,下次前端翻页带着这个 pageOffset 就行了
conn4575
2019-09-24 02:50:43 +08:00
插眼,这个问题也一直困扰着我

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/603295

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX