news 2026/10/10 4:44:12

列表翻到第 50 页就卡:深分页为什么慢,延迟关联 + 游标分页实测对比

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
列表翻到第 50 页就卡:深分页为什么慢,延迟关联 + 游标分页实测对比

职位列表分页,前 20 页嗖嗖的,翻到 50 页以后明显卡顿,接口从 90ms 涨到 900ms。同样的 SQL,LIMIT 0, 20和LIMIT 980, 20差距能到十倍。以前觉得"不就是 limit 吗",直到线上被打脸,才认真把深分页的账算明白。这篇用实测数据说话。

先看慢在哪:LIMIT 大偏移的真实开销

表还是那张 300 万行的job_post,列表查询长这样:

SELECTid,company_id,salary_min,post_timeFROMjob_postWHEREcity_code='440300'ANDstatus=1ORDERBYpost_timeDESCLIMIT980,20;

执行计划看着还行——有索引、没全表扫。但LIMIT 980, 20的实际动作是:InnoDB 先把前 980 行全部扫描出来丢掉,再返回最后 20 行。偏移越大,白白扫描和丢弃的行越多,行数是指数级上涨的:

LIMIT 0, 20 → 扫描 20 行 LIMIT 980, 20 → 扫描 1000 行 LIMIT 99980, 20 → 扫描 100000 行

更狠的是排序。ORDER BY post_time DESC如果没吃到索引,MySQL 要把符合条件的所有行都放进 filesort 排序,排完再切分页——数据量大起来,filesort 直接吃掉几秒。深分页慢的本质:不是查 20 行慢,是"排序 + 扫描丢弃前 N 行"慢。

方案一:延迟关联,先取 id 再回表

思路:先只查主键,跳过回表,等把该返回的 20 个 id 定下来,再 join 回原表取完整字段。分页子查询里的扫描依然存在,但扫描的是覆盖索引(只读 id 和排序列),不碰行数据,省掉大量回表 IO:

SELECTt.id,t.company_id,t.salary_min,t.post_timeFROMjob_post tINNERJOIN(SELECTidFROMjob_postWHEREcity_code='440300'ANDstatus=1ORDERBYpost_timeDESCLIMIT980,20)tmpONt.id=tmp.idORDERBYt.post_timeDESC;

实测:同样LIMIT 980, 20,延迟关联把耗时从 900ms 压到 210ms 左右。代价是写法丑,MyBatis-Plus 里要写自定义 SQL,PageHelper 帮不了你。适合"偏移不是特别大,但回表量明显"的场景,是性价比最高的第一步。

方案二:游标分页,把"偏移"换成"起点"

列表是"按时间倒序往下翻"的话,可以不用页码,改用"上次最后一条的位置"继续翻。MySQL 里最朴素的实现就是带 WHERE 的 LIMIT:

-- 第一页SELECTid,company_id,salary_min,post_timeFROMjob_postWHEREcity_code='440300'ANDstatus=1ORDERBYpost_timeDESCLIMIT20;-- 下一页:传上次最后一条的 post_time 和 idSELECTid,company_id,salary_min,post_timeFROMjob_postWHEREcity_code='440300'ANDstatus=1AND(post_time<'2026-10-08 12:00:00'OR(post_time='2026-10-08 12:00:00'ANDid<102456))ORDERBYpost_timeDESCLIMIT20;

关键在post_time相同的情况——时间可能重复,所以必须加 id 做第二排序键,否则同一秒发布的职位会翻页丢失或重复。游标分页不管翻多少页,扫描量恒等于"本页该读的行",100 页和 1 页耗时基本一样,这才是根治。

踩坑记录:游标分页把"倒序"翻成了"正序"

现象:游标分页上线后,用户反馈列表越翻越乱,翻几页后出现重复数据,而且新发布的职位不出现。

排查过程:先查接口入参,前端传的 lastPostTime 和 lastId 都正常。再看 SQL,发现问题在排序方向——列表要post_time DESC(新的在前),游标条件写的却是post_time > lastPostTime(继续找更新的),倒序列表配正序游标,永远找不全。

定位思路:游标条件和排序方向必须配套。DESC排序要往下翻,游标条件是"比上一条更小(或相等且 id 更小)";写成"更大"就整个反了。代码里两个地方不一致——接口层排序写 DESC,SQL 拼接游标条件时复制错了模板,方向没跟着改。

最终解决:游标条件生成收敛成一个方法,方向参数化,杜绝手拼:

// direction = -1 表示倒序(新的在前),游标取更小值publicstaticStringbuildCursorCondition(intdirection,StringtimeCol,StringidCol,ObjectlastTime,ObjectlastId){if(lastTime==null){return"";// 第一页无游标}if(direction<0){return"("+timeCol+" < ? OR ("+timeCol+" = ? AND "+idCol+" < ?))";}return"("+timeCol+" > ? OR ("+timeCol+" = ? AND "+idCol+" > ?))";}

方案三:别翻那么深——产品侧限深

说到最后,还有个大实话:业务上没几个用户真会翻到第 100 页。列表 + 搜索场景,产品侧把翻页上限卡在 50 页,超出提示"条件太窄,换个筛选",配合搜索条件本身就能过滤大部分深分页流量。技术方案做得再好,也不如产品上不让它发生。

可直接复用的清单

  • 深分页慢的本质:排序 + 扫描丢弃前 N 行,不是查 20 行慢。
  • 中浅偏移先上延迟关联(子查询先取 id 再回表),改动小见效快。
  • 无限滚动列表直接游标分页:排序键 + 唯一键双条件,扫描量恒定。
  • 游标方向必须和排序方向配套:DESC 配"小于",ASC 配"大于",条件生成统一收口。
  • 时间戳可能重复,游标必带 id 第二排序键,否则翻页丢数据。
  • 产品侧限深分页(上限 50 页),技术 + 产品双管齐下。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 4:43:41

中断机制详解:从硬件触发到Linux内核处理的全链路解析

1. 中断不是“打断”&#xff0c;而是操作系统最精密的呼吸节奏你有没有想过&#xff0c;为什么键盘按下一个键&#xff0c;屏幕几乎立刻就出现字符&#xff1f;为什么鼠标轻轻一划&#xff0c;光标就能丝滑跟上&#xff1f;为什么后台正在压缩大文件&#xff0c;前台还能流畅播…

作者头像 李华
网站建设 2026/10/10 4:43:31

旧安卓手机微信一次只能发一个文件?试试这些批量传输方案

1. 问题背景&#xff1a;一次卡在“选文件”环节的日常崩溃先说说我自己的情况。手里有一台前几年买的真我手机&#xff0c;系统一直没怎么升级&#xff0c;安卓版本还停留在比较早的时代。平时用微信跟人传文件&#xff0c;照片、视频这些倒还好&#xff0c;直接从相册里选&am…

作者头像 李华
网站建设 2026/10/10 4:43:29

主从博弈下的共享储能与综合能源微网双层优化运行

第一次接触基于主从博弈的共享储能与综合能源微网优化运行问题&#xff0c;是在模拟项目X里。当时某课题组要从零搭建一个微网群的仿真环境&#xff0c;我拿到手的第一份资料里&#xff0c;共享储能站被设计成“第三方运营商”&#xff0c;综合能源微网则是买电、买气、买热服务…

作者头像 李华
网站建设 2026/10/10 4:42:20

WorkBuddy Skill实战指南:从工作任务到可复用AI能力单元

1. 项目本质解构&#xff1a;这不是一场普通征文&#xff0c;而是一次AI办公能力的“压力测试”WorkBuddy 这个名字在最近三个月里&#xff0c;已经从腾讯内部的一个实验性工具&#xff0c;悄然演变成国内AI办公领域一个绕不开的坐标。它不是另一个聊天窗口&#xff0c;也不是简…

作者头像 李华
网站建设 2026/10/10 4:41:46

Python之os模块案例详解

前言 os 模块是 Python 与操作系统打交道的入口&#xff1a;路径、目录、环境变量、进程信息、文件描述符&#xff0c;都从它这里走。它的特点是函数名短、参数少、但语义差异大——os.remove 和 os.rmdir 只差几个字母&#xff0c;一个删文件一个删空目录&#xff0c;用错就是…

作者头像 李华