分页场景下、查询数据后排除部分内容导致不足一页的 问题怎么解决? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
rizon
V2EX    程序员

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

  •  
  •   rizon
    othorizon 2019-09-23 11:58:24 +08:00 5619 次点击
    这是一个创建于 2225 天前的主题,其中的信息可能已经有所发展或是发生改变。

    场景:

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

    问题:

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

    抛砖引玉:

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

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

    第 1 条附言    2019-09-23 13:17:28 +08:00
    这个问题还有个思路,就是和数据库建立长连接,然后流式的一条条的读取数据,没读取一条做一次业务过滤,直到凑够一页数据后,断开数据库连接。

    但是这个怎么实现?
    用原生的数据库操作的时候,返回的那个 resultSet 是每执行一次 next()才从库里读取一条数据吗?还是在执行第一次 next 的时候就全部读取了?
    20 条回复    2019-09-24 02:50:43 +08:00
    licoycn
        1
    licoycn  
       2019-09-23 11:59:43 +08:00
    难道不是按照过滤规则在数据库查找数据么
    licoycn
        2
    licoycn  
       2019-09-23 12:01:43 +08:00
    比如要查找年龄大于 18 的,你不可能把先无条件查询 10 条再进行到业务层判断,然后再去查询凑够 10 条,为何不直接在数据库查询年龄大于 18 的用户 10 条返回给业务层这样呢
    SmiteChow
        3
    SmiteChow  
       2019-09-23 12:09:25 +08:00
    给个提示,已排除 xx 条数据
    rizon
        4
    rizon  
    OP
       2019-09-23 12:26:45 +08:00
    @licoycn #1 场景里页提到了,因为很多原因,是不能从数据库层直接做过滤的。
    rizon
        5
    rizon  
    OP
       2019-09-23 12:28:46 +08:00
    这个问题还有个思路,就是和数据库建立长连接,然后流式的一条条的读取数据,没读取一条做一次业务过滤,直到凑够一页数据后,断开数据库连接。

    但是这个怎么实现?
    用原生的数据库操作的时候,返回的那个 resultSet 是每执行一次 next()才从库里读取一条数据吗?还是在执行第一次 next 的时候就全部读取了?
    geeglo
        6
    geeglo  
       2019-09-23 13:03:42 +08:00
    笨方法,分页还是按 10 条分,取 100 条。
    wisej
        7
    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
        8
    gfreezy  
       2019-09-23 13:09:41 +08:00
    我们用到的有 3 中方法:

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

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

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

    btw,如果页数标记可以缓存,你也可以把过滤结果缓存了,这个问题也不存在了.
    rizon
        13
    rizon  
    OP
       2019-09-23 13:20:52 +08:00
    @gfreezy #8
    @wisej #7
    对于先查出来如果不够再补上这种思路,存在的最致命问题就是,如果页面要查询第二页数据,那么你必须从第一条数据开始算,才能算出真正的第二页数据。。。因为你直接用页码计算的 offset 肯定是不对的。
    所以大概也没什么好的办法了吧。只能冗余或者让产品妥协了吧。
    gfreezy
        14
    gfreezy  
       2019-09-23 13:26:33 +08:00
    @rizon 从第一条开始算是因为你们客户端使用 offset 来确定位置。改成 cursor,并且把第一次翻页的位置 encode 在 cursor 里面返回给客户端,客户端请求下一页的时候再把 cursor 原封不动的传到服务器。这样就能实现在两次翻页之间传递数据。第二次翻页就可以知道上一次翻到哪里了,直接从上一次的位置继续往下翻
    LinJunzhu
        15
    LinJunzhu  
       2019-09-23 14:09:54 +08:00
    mark,最近也遇到这个问题
    StarkWhite
        16
    StarkWhite  
       2019-09-23 15:56:37 +08:00
    如果业务上允许的话,可以用存储过程
    summmset
        17
    summmset  
       2019-09-23 16:08:51 +08:00
    #3 的方法,配合方案 2,前端瀑布流,每次加载更多按固定翻页加载,加载完成,提示已加载多少条
    Mirt
        18
    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
        19
    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
        20
    conn4575  
       2019-09-24 02:50:43 +08:00 via Android
    插眼,这个问题也一直困扰着我
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1160 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 23:45 PVG 07:45 LAX 16:45 JFK 19:45
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86