Firestore前后向分页实现中 新数据查询重复返回文档推高读取成本
问题核心原因
你现在用的是 endDate 降序排序,整个文档序列的顺序是「最新结束的比赛排最前面,结束时间越早的越靠后」,这时候所有游标的判断逻辑都是和排序方向绑定的,和平时对数值大小的判断是反过来的:
- 原来写的
.end(at: [date]),在降序场景下等价于筛选所有endDate >= 传入date的文档,也就是会把date本身对应的那条已有比赛也包含在结果里。当没有比date更新的内容时,就会直接返回这条已有的文档,既造成列表重复,又平白多花一次文档读取的成本。 - 你试过的
.end(before: [date])其实逻辑上刚好匹配拉新数据的需求:在降序场景下,这个方法等价于筛选所有endDate > 传入date的文档,也就是严格比这条记录更新的比赛。如果还是返回已有记录,大概率是传入的date取值不对,或者存在多个比赛endDate完全相同的情况导致游标偏移。
调整后的.new分支实现
直接把游标换成匹配需求的方法就行,不需要复用.old分支的逻辑:
case .new(let date, let limit): return Firestore.firestore() .collection(Contest.collectionName) .whereField(Contest.Key.isLive.rawValue, isEqualTo: false) .order(by: Contest.Key.endDate.rawValue, descending: true) // 严格筛选出endDate大于传入date的文档,无匹配时直接返回空数组 .end(before: [date]) .limit(to: limit) .getDocuments()
进一步降低读取成本的细节
- 别光用日期当游标:如果有多个比赛的endDate完全一样,纯日期游标很容易出现漏取、重复的问题。可以把.new分支的入参从纯date值换成本地列表第一条文档的DocumentSnapshot,用
.end(before: snapshot)做游标,精准卡到已有文档的位置,从根上避免把已有的文档算进返回结果。 - 给拉新的limit适配业务场景:通常新比赛的数量不会太多,没必要把初始加载的25条limit直接搬过来,根据实际情况调小(比如每次拉新最多取10条),能减少很多非必要的读取。
- 校验入参的正确性:调用.new分支的时候,一定要确认传入的是本地列表最顶部(endDate最大)的那条记录的时间,别错传成列表最底部的历史记录时间,不然会因为游标位置偏移,直接拉取全量的已有文档。
- 保证时间精度一致:如果用日期当游标,要确认数据库存储的endDate和本地读取的date是同精度的(比如都是毫秒级的Timestamp),避免因为毫秒级的时间差导致游标判断偏移,出现意料之外的返回结果。
内容的提问来源于stack exchange,提问作者aryaxt
相关产品推荐
相关产品推荐

