V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
rizon
V2EX  ›  程序员

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

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

    场景:

    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   ·   实用小工具   ·   1431 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 17:28 · PVG 01:28 · LAX 09:28 · JFK 12:28
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.