分库分表上线后出现跨分片分页重复、漏数据和排序不稳定,你会如何定位全局排序与分页实现中的问题?
免费版我会先确认问题是否确实发生在“跨分片归并分页”这一层,而不是单个分片查询本身。重点检查请求参数、分片路由、各分片 SQL、排序字段、归并逻辑以及分页截取的位置。
如果每个分片都执行 ORDER BY create time LIMIT offset, size,然后把结果直接合并,再取前 size 条,这种实现通常是不正确的。因为 offset 是局部分片偏移,不代表全局偏移,某个分片前面被过滤掉的数据可能影响全局结果,容易出现漏数据和重复数据。正确做法一般是各分片按照全局排序规则查询足够多的数据,再在应用层做归并排序,最后进行全局分页。对于第 page 页、每页 size 条,常见做法是每个分片查询 offset + size 条,然后通过归并排序取全局第 offset + 1 到 offset + size 条,但深分页时成本会比较高。
接着我会检查全局排序是否稳定。只使用 create time 这类可能重复的字段排序时,不同分片中相同时间的数据顺序是不确定的。分页请求之间如果数据库执行计划、数据写入或网络返回顺序发生变化,就可能导致同一条数据重复出现在不同页,或者某些数据被跳过。通常需要增加唯一且确定性的次排序字段,例如 ORDER BY create time DESC, id DESC,并保证 id 在全局范围内唯一,或者使用 (create time, id) 作为游标。
还要确认各分片的排序规则是否完全一致,包括字段类型、时间精度、字符集和大小写规则,不能出现一个分片按数据库时间类型排序,另一个分片按格式化字符串排序的情况。应用层归并时也必须使用与 SQL 一致的比较规则,不能依赖各分片响应顺序或线程返回顺序。
如果当前使用的是 offset 分页,我会重点排查分页期间的数据变化。第一页查询和第二页查询之间如果有新增、删除或更新数据,数据可能发生位移,即使排序稳定,也可能出现重复或漏数据。对实时变化较多的列表,我会优先改成基于游标的分页,例如携带上一页最后一条记录的 (create time, id),下一页使用:
跨分片时,每个分片根据同一个游标查询下一批数据,再做全局归并,直到取满一页。这样比深分页的 offset 更稳定,性能也更好。
定位过程中我会构造可复现的数据:让多个分片存在相同排序时间、边界数据刚好落在分页位置,并在分页过程中插入或删除记录。然后分别记录每个分片的 SQL、参数、返回主键列表、应用层归并结果和最终分页结果,检查数据是在分片查询阶段丢失,还是在归并、去重或截取阶段出问题。同时确认是否存在错误的去重逻辑,例如只按业务字段去重,或者不同分片生成了相同的主键。
如果数据量较大,还要检查实现是否是“全量拉取后排序”,以及是否使用了优先队列做多路归并。理想情况下每个分片只返回必要的数据,应用层按排序字段维护堆,归并复杂度大致为 O(N log K),其中 K 是分片数,N 是参与归并的数据量。
最终我会从三个方面修复:第一,统一所有分片的排序条件,并增加全局唯一的稳定排序键;第二,确保分页发生在全局归并之后,而不是在各分片分别分页后直接拼接;第三,根据业务一致性要求选择 offset 或游标分页,必要时引入查询快照、版本号或时间边界,保证多页查询期间看到相对一致的数据集。修复后还需要通过边界数据、相同排序值、深分页以及并发写入场景进行回归测试,验证每一页之间没有重复和漏数据。
