回到顶部

阅读目录

两种经典列表分页方式的优缺点

页码/偏移分页

描述:客户端提供页码和每页大小,服务器根据总数计算出偏移量进行查询。

  • SQL:SELECT ... FROM table ORDER BY created_time DESC LIMIT {pageSize} OFFSET {(page-1)*pageSize};

  • API:GET /items?page=2&page_size=20

  • 响应:通常包含 items(数据列表), total(总记录数), total_pages(总页数)。

优点

  1. 直观易用:概念简单,符合用户对“书页”的认知。用户可以轻松跳转到任意页面(如第5页)。

  2. 状态无关:查询不依赖于特定记录,任何时间请求同一页码,返回的都是该“位置段”的数据。

  3. 易于实现统计:自然地提供总数、总页数等信息,便于前端显示“共100条,1/5页”。

  4. 数据一致性视图:在数据静态或变化缓慢的场景下,能提供一个稳定的数据视图。

缺点与显示问题

  1. 性能问题(核心缺点):

    1. OFFSET 在大数据量下效率低。数据库需要扫描并跳过 OFFSET 之前的所有行,即使你只取 LIMIT 条。OFFSET 10000 意味着数据库要先读取并丢弃一万条记录。

    2. 计算 COUNT(*) 在数据量大时也非常耗时。

  2. 数据重复/丢失问题(核心显示问题):

    1. 这是最致命的问题,尤其是在数据频繁增删的动态列表中(如社交动态、新闻推送)。

    2. 场景:用户正在看第一页(ID为100-81),此时在第一页之前插入了一条新记录(ID=101)。当用户翻到第二页时,数据库会用 OFFSET 20 去查询,此时原第一页的最后一条(ID=81)会再次出现在第二页的头部,导致数据重复

    3. 反之,如果在翻页过程中第一页的数据被删除,那么第二页的数据会整体前移,导致用户丢失原本应该看到的一条记录(跳过了某条数据)。

    4. 这给用户的体验是列表“跳动”或不连贯。

 

游标/键集分页

描述:客户端提供上一页最后一条记录的某个唯一、有序的字段值(游标,通常是ID或时间戳),服务器查询“该游标之后”的N条记录。

  • SQL:SELECT ... FROM table WHERE id < {last_id} ORDER BY id DESC LIMIT {pageSize};

  • API:GET /items?cursor=123&page_size=20 (或 since_id=123, last_id=123

  • 响应:通常只包含 items(数据列表),以及用于下一页的游标(如最后一条的ID)。

优点

  1. 高性能:查询利用了WHERE条件在索引上的定位,直接找到起始点开始读取,无需扫描和跳过大量行。性能与偏移量无关,仅和LIMIT大小有关。

  2. 数据一致性(解决核心显示问题):

    1. 非常适合动态数据流。因为查询基于某个时间点的数据快照(游标值),在连续的翻页过程中,即使有新增或删除,也不会影响当前“下一页”的查询结果,避免了重复和丢失。

    2. 提供了稳定的浏览体验,用户看到的内容是连贯的。

缺点与显示问题

  1. 无法直接跳页:用户只能“上一页”、“下一页”地导航,无法直接跳转到任意中间页面(如第5页)。这对管理后台等需要跳转的场景不友好。

  2. 不提供总数:由于不进行COUNT操作,且数据动态变化,总页数或总条数通常无法提供或不准确。

  3. 游标字段要求高:

    1. 必须基于一个唯一且有序的字段(如自增ID、时间戳)。

    2. 如果排序涉及多个非唯一字段(如按score排序,score相同时按id排序),游标处理会变得复杂(需要组合条件,如 WHERE (score, id) < (?, ?))。

  4. 数据处理复杂度:需要客户端维护游标状态。API响应和参数设计需要仔细,以传递正确的“上一页”和“下一页”游标。

  5. 边界情况处理:如果游标指向的记录被删除,需要设计兜底逻辑(例如,如果根据ID找不到记录,则回退到时间范围查询)。

两种分页方式对比

特性 页码/偏移分页 游标/键集分页
性能 大数据量下差(OFFSET慢) 优秀,适合大数据集
跳页 支持 不支持,只能顺序翻页
数据总数 容易提供 难以提供,通常不提供
数据一致性 差,动态数据下会重复/丢失 优秀,适合动态数据流
实现复杂度 简单 较复杂
适用场景 后台管理系统、数据变化慢的列表、需要跳页和统计的场景 社交动态流、实时消息、新闻推送、任何无限滚动列表

灵活选择

选择页码分页的场景

  • 你的数据表相对静态,更新不频繁。

  • 用户需要跳转到特定页面(如后台数据查询)。

  • 必须显示总条数和总页数。

  • 数据量不大,性能不是首要考量。

选择游标分页的场景

  • 你的数据是实时、动态的(如Twitter时间线、商品评论)。

  • 你使用无限滚动作为前端交互。

  • 数据量非常大,且对性能和浏览连贯性要求极高。

  • 可以接受不显示总数和无法任意跳页。

混合策略

在一些复杂场景下,可以结合使用。例如,管理后台默认用页码分页,但在“操作日志”这种可能大量插入数据的子模块中使用游标分页。或者,第一页用页码查询,后续翻页使用游标。

进阶优化方案

描述:不是独立的全新方式,而是在特定场景下对基础方案的组合与增强。

方案名称 核心优化思路 关键优点 适用场景
游标分页的深度优化 结合业务规则,用主键或时间范围替代OFFSET。 性能最佳,数据连贯,无需全局排序。 按时间线、ID顺序浏览的列表(如信息流)。
业务降级方案 对用户需求进行妥协或简化,如限制查询深度、提供替代路径。 实现简单,能规避最难处理的“深分页”性能悬崖。 C端产品,用户容忍度较高的场景。
分布式环境优化 在分库分表或数据仓库中,采用流式归并和查询缓存。 缓解分布式全局排序的“数据爆炸”问题,降低重复计算。 分库分表的大数据量后台,固定报表导出

📈 分页方案详解与选择

  1. 游标分页的深度优化 这是最高效的方案,尤其适合连续浏览的场景。关键在于将WHERE条件与排序字段对齐并建立索引。例如,按时间倒序查新闻:

  2. sql

  3. -- 首页SELECT * FROM news WHERE publish_time < NOW() ORDER BY publish_time DESC LIMIT 20;-- 下一页:用上一页最后一条的时间作为游标SELECT * FROM news WHERE publish_time < '上一页最后时间' ORDER BY publish_time DESC LIMIT 20;

  4. 注意:它牺牲了随机跳页的能力。如果需要跳页,可结合“业务降级方案”,改用其他查询路径(如搜索)。

  5. 业务降级方案 当技术优化遇到瓶颈时,从产品逻辑上着手往往更有效。

    1. 限制最大页码:例如,搜索和电商平台通常只允许查看前100页,引导用户使用更精确的筛选条件。

    2. 提供替代路径:在列表旁提供“按时间筛选”、“按标签筛选”或搜索框,让用户直接定位目标区域,避免连续翻页。

  6. 分布式环境优化 在分库分表或数据仓库(如AnalyticDB)中,深分页性能问题会被放大,因为数据需要在多个节点排序合并。

    1. 流式归并:像Apache ShardingSphere这样的中间件,采用流式处理从各分片按顺序获取数据再归并,避免将所有数据加载到内存。

    2. 查询缓存:阿里云AnalyticDB的Paging Cache功能是一个典型方案。首次查询时将排序结果存入缓存,后续相同模式的分页直接读取缓存,这对固定报表的多次分页导出场景性能提升巨大。

💡 如何选择与综合运用

你可以根据数据量、场景和技术栈来决策:

数据规模与场景 推荐方案 原因与说明
中小数据量,需随机跳页 偏移分页 + 良好索引 简单直观。确保ORDER BY字段有索引,可配合数据库查询缓存。
大数据量,连续浏览 游标分页 性能和体验最佳。多数现代App信息流的首选。
分库分表,深分页查询 游标分页 + 中间件优化 优先用游标分页。若必须用偏移分页,需依赖ShardingSphere等中间件的流式归并优化。
固定报表导出 Paging Cache类缓存方案 首次查询缓存全量排序结果,后续分页极快。
C端产品,体验优先 游标分页 + 业务降级 用游标保证浏览流畅,同时提供筛选、搜索等路径替代深度跳页。

简单来说,对于追求极致性能的动态列表,深度优化的游标分页是首选。对于分库分表等复杂场景,可以借助中间件的流式处理能力。而对于后台批量操作,可以关注数据库厂商提供的 Paging Cache 等专有优化。


^_^
请喝咖啡 ×

文章部分资料可能来源于网络,如有侵权请告知删除。谢谢!

前一篇: 测试相关 AI 学习和应用资料整理
captcha