分页是后端开发中最常见的需求之一,但很多人把它当成"加个 LIMIT 和 OFFSET 就完事"的琐碎工作。真正到了数据量上来、并发升高、或者像聊天APP这种要求实时性的场景,分页方式的选择会直接决定接口延迟、数据库负载和用户体验。目前主流的两套方案——偏移量分页(OFFSET 分页)和游标分页(Cursor 分页),各有其适用边界,用错了会出现深翻页变慢、数据重复、数据丢失这类隐蔽bug。本文结合常见业务,把两者的优缺点讲透,并给出选择建议。
先说偏移量分页。它的SQL形态非常直观,就是 SELECT * FROM table ORDER BY id LIMIT 10 OFFSET 0,然后 OFFSET 10、OFFSET 20 这样递增翻页。这种写法最突出的优点就是简单,几乎所有框架和ORM都原生支持,代码量最少,后端只需要接收 page 和 size 两个参数,返回给前端的也是当前页数据加一个"总条数",前端可以据此渲染出完整的页码条。这种模式对后台管理系统、报表页这类低并发、数据量适中的场景非常合适,因为翻页逻辑直观,开发效率高,测试也容易覆盖。
但偏移量分页有一个根深蒂固的性能问题,就是"深翻页"。数据库在执行 OFFSET 100000 LIMIT 20 时,并不是直接跳到第10万条,而是必须先扫描并丢弃前10万行,再取第100001到100020条。也就是说,翻页越深,扫描的行数越多,耗时会线性甚至超线性增长。你翻到第5000页,数据库可能就要先扫过几十万行才返回20条结果。在MySQL里,即使 ORDER BY 的字段有索引,OFFSET 导致的大偏移依然会造成严重回表。数据量在百万级时,深翻页接口能轻松把查询时间拖到几百毫秒甚至秒级,并发一上来就会拖垮数据库。
偏移量分页的第二个隐患是数据一致性问题。它的翻页依据是"位置",而位置是动态的。假设你的聊天记录列表每页20条,用户正在翻第3页时,正好有两条新数据插入到了最前面,那么用户第3页看到的,就会和第2页出现重叠,甚至漏掉中间某条记录。反过来,如果翻页过程中有人删除了数据,你又可能跳过一条。这种"重复"和"丢失"在OFFSET模式下是结构性的,无法靠加索引解决,只能靠业务容忍或加时间戳去重。对社交信息流、聊天记录这种高频插入、删除的场景,这是很致命的。
再来说游标分页。游标分页不依赖"位置",而是依赖"锚点值",它的SQL形态通常是把上一次返回的最后一条记录的主键或排序键作为条件传回来,例如 SELECT * FROM record_msg WHERE id < last_id ORDER BY id DESC LIMIT 20。因为每次查询都是基于索引的精确比较,数据库可以直接定位到锚点,向后读取20条,无论你翻到第几页,扫描的行数都恒定等于本页条数,不存在深翻页变慢的问题。数据量再大,只要排序键有索引,翻页性能都稳定,这是游标分页最大的优势。
游标分页在数据一致性上同样优于偏移量。因为它锚定的是"某条具体记录",翻页过程中即使有新数据插入到最前面,新的记录在排序上排在锚点之前或之后是确定的,不会造成已经翻过的页与未翻的页重叠,也不会漏数据。这一点对聊天记录、IM会话、信息流、评论列表这类高频写入场景尤其重要。前面我在帮你排查 record_msg 的会话列表和 remark 拼接逻辑时,正是这种"谁最新谁在前、翻页不能重也不能漏"的需求,游标分页才是正确解法。
但游标分页也有明显的缺点。第一是它无法直接跳页,你不能从第1页直接跳到第5页,因为跳页需要知道第5页的锚点值,而锚点只能一页一页往后拿。所以它天然不支持页码条,只适合"上一页/下一页"的加载更多模式。对需要任意跳页的后台报表来说,这就是硬伤。第二是它依赖一个"稳定且唯一"的排序键,通常要用主键id或带唯一索引的创建时间。如果业务需要按非唯一字段(比如点赞数、价格、热度)排序,就很容易出现同一锚点被多次取到、分页结果边界混乱的问题,处理起来复杂得多。第三是它的"总条数"统计代价高,因为没有 OFFSET 那样简单的 count 语义,信息流类接口通常会放弃返回总条数。
具体怎么选,我给一个务实的判断标准。如果你做的是后台管理、报表导出、低并发、允许跳页、数据量可控的场景,优先用偏移量分页,简单就是最大的优势,别过度设计。如果你做的是用户直接面对的列表接口,尤其是聊天记录、IM消息、朋友圈、信息流这类高频插入删除、需要"加载更多"、追求稳定性能和一致性的场景,就用游标分页。另外还有一个折中:第一页用偏移量拿初始锚点,后续用游标翻,既能保留初始体验,又能规避深翻页。
还有一个容易被忽略但很重要的点:无论选哪种分页,都要先问清楚"排序键能不能唯一且稳定"。如果排序键有重复值,OFFSET 会乱序,Cursor 会错位,再好的分页方案也会翻车。最好用主键id作为排序兜底,或者在业务排序键上叠加id做联合排序,这是分页健壮性的底层保障。
总结一句话:偏移量分页胜在简单灵活,败在深翻页性能和数据一致性;游标分页胜在性能稳定、翻页准确,败在无法跳页和实现复杂。做聊天APP、信息流这类产品,我强烈建议默认游标分页;做后台和报表,偏移量完全够用。分页不是银弹,选对了才是。