一、背景
分页应该是极为常见的数据展现方式了,一般在数据集较大而无法在单个页面中呈现时会采用分页的方法。各种前端UI组件在实现上也都会支持分页的功能,而数据交互呈现所相应的后端系统、数据库都对数据查询的分页提供了良好的支持。以几个流行的数据库为例:
查询表 t_data 第 2 页的数据(假定每页 5 条)
MySQL 的做法:select * from t_data limit 5,5
PostGreSQL 的做法:select * from t_data limit 5 offset 5
MongoDB 的做法:
db.t_data.find().limit(5).skip(5);
尽管每种数据库的语法不尽相同,通过一些开发框架封装的接口,我们可以不需要熟悉这些差异。如 SpringData 提供的分页接口:
public interface PagingAndSortingRepository<T, ID extends Serializable>
extends CrudRepository<T, ID> {
Page findAll(Pageable pageable);
}
这样看来,开发一个分页的查询功能是非常简单的。然而万事皆不可能尽全尽美,尽管上述的数据库、开发框架提供了基础的分页能力,在面对日益增长的海量数据时却难以应对,一个明显的问题就是查询性能低下!那么,面对千万级、亿级甚至更多的数据集时,分页功能该怎么实现?
下面,我以 MongoDB 作为背景来探讨几种不同的做法。
二、传统方案
就是最常规的方案,假设 我们需要对文章 articles 这个表(集合) 进行分页展示,一般前端会需要传递两个参数:
页码(当前是第几页)
页大小(每页展示的数据个数)
按照这个做法的查询方式,如下图所示:
海量数据的分页怎么破?我的方案这样“改良”,实在太完美!
因为是希望最后创建的文章显示在前面,这里使用了_id 做降序排序。其中红色部分语句的执行计划如下:
{
“queryPlanner” : {
“plannerVersion” : 1,
“namespace” : “appdb.articles”,
“indexFilterSet” : false,
“parsedQ