传统偏移量
即 LIMIT OFFSET,OFFSET 子句跳过的行仍需被扫描,数据量较大会存在性能问题。
适用于B端后台数据较小的场景。
基于游标
适用于具有唯一字段且单调的表结构,一般以自增主键id作为游标列。查询时带上上一次返回的cursor,查询第一页数据不用传,后续分页必传。如:select * from table where id < cursor limit 20
,这将从cursor行开始扫描,大数据量下相较于传统偏移量方式表现更好,特别适合C端应用的滚动分页场景。
一种更好的方式是:查询时多查一条记录,用于判断是否还有更多数据,从而减少一次查询操作。
封装为一个通用的工具类
python
class Paginator:
def __init__(self, query):
self.query = query
self.data = None
def paginate(self, page: int, page_size: int, max_per_page=None):
"""分页查询"""
if page_size is None:
page_size = 20
if page is None or page <= 0:
raise ValueError("page or page size is None!")
if max_per_page and page_size > max_per_page:
raise ValueError("per page size exceeded the max limit!")
offset = page_size * (page - 1)
self.data = self.query.slice(offset, offset + page_size).all()
return self
@property
def total(self):
return self.query.count()
@property
def items(self):
return self.data
class ScrollPaginator(Paginator):
"""滚动分页(基于游标)"""
def __init__(self, query: Query, model: db.Model):
"""
:param query: 查询对象
:param model: model对象
"""
super().__init__(query)
self.model = model
self.data = []
self.has_more = False
self.last_score = None
def paginate(
self, last_score: Union[str, int], limit=20, max_limit=100, order_col="id", is_reversed=True
):
"""
基于游标分页
:param order_col: 排序字段名
:param last_score: 查询列表中最后一项的id
:param max_limit: 最大分页限制
:param limit: 滚动步长,使用limit+1用于判断是否还有更多数据
:param is_reversed: 按时间倒序
:return:
"""
if limit < 1:
raise ValueError("limit must >= 1")
if max_limit and limit > max_limit:
raise ValueError("per page size exceeded the max limit!")
if last_score: # 非首次查询
if is_reversed:
filters = [getattr(self.model, order_col) < int(last_score)]
else:
filters = [getattr(self.model, order_col) > int(last_score)]
self.query = self.query.filter(*filters)
queryset = self.query.limit(limit + 1).all()
if (length := len(queryset)) >= 1:
if length > limit:
self.data = queryset[:-1]
self.has_more = True
last = queryset[-2]
else:
self.data = queryset
last = queryset[-1]
self.last_score = getattr(last, order_col)
return self
@property
def last(self):
return self.last_score
@property
def more(self):
return self.has_more
redis zset
见另一篇文章:juejin.cn/post/732933...