分页时出现重复数据

昨天为一个小程序项目增加一个功能:首页滚动自动加载更多数据,每次加载20条。

实现的思路很清晰:

  1. 后台将数据分页,每页20条数据;
  2. 前台每次滚到底部时,触发 API 请求,向后台获取下一页的数据;
  3. 前台将新一页的数据合并到已有的数据里。

按照思路做完之后,为了测试,我将分页数据改成 1,新建了几个用户,简单测试一下功能,没问题,然后提PR,我就下班(喝酒去)了。没过多久,收到邮件提示说,这个功能合并上线之后,出 bug 了。

在加载的时候,有些数据重复出现了。

image

找原因

今天到公司,开始找原因。首先要确定的是前台问题还是后台问题。

我在前台加了几个断点,发现触发加载的时候,后台传来的数据确实出现了重复数据。说明问题出在后台。

后台的代码其实很简单,按照 compressive_strength 排序,然后用 Kaminari 这个 gem 来分页。

@players = User.where('compressive_strength IS NOT NULL').order("compressive_strength desc").includes(:profile).page(params[:page]).per(20)

猜想应该是分页的问题,用 Kaminari + duplicate 关键词 google 了一下,果然找到了相同的问题

问题出现在不确定的排序上。

在获取第一页数据时,代码的运行逻辑是 取得满足要求的 User 数据 -> 按照 compressive_strength 字段排序 -> 取出第 1~20 条数据

在获取第二页数据时,代码的运行逻辑是 取得满足要求的 User 数据 -> 按照 compressive_strength 字段排序 -> 取出第 21~40 条数据

问题在于,如果 compressive_strength 的值相同,因为两次请求是两次独立的排序,得到的排序结果可能是不相同的,第 1~20 条数据第21~40条数据就可能出现重复的数据。

解决办法

解决办法也很简单,保证每次排序的结果都是一致的就可以了。采用的方法是,在排序条件里增加一个字段,比如 id,所以代码变成

@players = User.where('compressive_strength IS NOT NULL').order("compressive_strength desc, id").includes(:profile).page(params[:page]).per(20)

总结

  1. 有分页的时候,排序的条件最好是有唯一值的字段,才能保证排序结果唯一;
  2. 提交代码前,要认真测试,修完 bug 之后,我就建了 100 个用户,做了各种测试。
· rails