news 2026/11/4 12:09:31

深分页查询越来越慢,如何优化 LIMIT OFFSET

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深分页查询越来越慢,如何优化 LIMIT OFFSET

深分页查询越来越慢,如何优化 LIMIT OFFSET

订单列表第一页打开很快,翻到第 5000 页却要好几秒。SQL 看起来只取了 20 条,LIMIT 100000, 20里的 20 也很小,为什么还会慢?因为数据库并不是直接跳到第 100000 条,它要先数过去。


1.LIMIT 100000, 20到底做了什么

很多人把LIMIT 100000, 20理解成“从第 100000 条开始,取 20 条”。这个理解在结果上没错,但执行过程不是瞬移。

数据库会先按照ORDER BY的顺序找到符合条件的行,然后跳过前 100000 行,最后把接下来的 20 行返回。被跳过的行并不是免费消失的,它们仍然要经过索引扫描、条件判断,很多时候还要回表读取整行数据。

SELECT*FROMordersORDERBYidLIMIT100000,20;

如果id是主键,数据库可以沿着主键顺序扫描。但即使如此,它也要走过前 100020 个索引项,才能拿到最后那 20 行。页码越深,走过的路越长。

如果排序字段是普通索引,查询又要SELECT *,情况会更重:数据库先在二级索引里找到一批主键,再逐个回表取整行。深分页时,这种随机读取会被放大很多倍。

图 1:OFFSET 要先扫过并丢弃前面十万行;游标从上一页的位置继续,只读需要的 20 行。

2. 为什么第一页快,第五百页慢

第一页的OFFSET是 0,数据库找到开头就能返回,成本很小。第二页OFFSET 20,只多扫 20 行。到第 5000 页,OFFSET变成了 100000,扫描量也跟着涨到了十万级。

这就是深分页的核心:成本大致和OFFSET成正比,而不是和LIMIT的大小成正比。

第 1 页 OFFSET 0 扫描约 20 行 第 2 页 OFFSET 20 扫描约 40 行 第 5000 页 OFFSET 100000 扫描约 100020 行

如果表再大一点,或者每行数据比较宽,回表次数增加,差距会更明显。再加上ORDER BY需要排序、COUNT(*)需要统计总数,一个列表接口很容易从几十毫秒变成几秒。

3. 游标分页:从上一页的位置继续

如果页面是“按时间倒序、不断往下翻”的形态,最适合的不是OFFSET,而是游标分页。

不要告诉数据库“跳过前 100000 行”,而是告诉它“从上一页最后一条记录之后继续”:

SELECTid,user_id,amount,created_atFROMordersWHEREid>1000000ORDERBYidLIMIT20;

这里假设上一页最后一条记录的id是1000000。数据库可以直接从id > 1000000的位置开始,沿着主键索引取 20 条,不需要数前面有多少行。

如果排序不是主键,而是时间,时间又可能重复,就要把“时间 + 唯一键”一起作为游标:

SELECTid,user_id,amount,created_atFROMordersWHERE(created_at,id)<('2026-10-01 12:00:00',1000)ORDERBYcreated_atDESC,idDESCLIMIT20;

这里的(created_at, id)是一个组合位置。时间相同的记录再用id区分,下一页就不会漏掉或重复。

游标分页的代价是不能随机跳到第 5000 页。它适合“下一页”“加载更多”这种连续浏览,不适合后台管理系统里“直接跳到最后一页”的需求。

4. 延迟关联:先拿 ID,再回表

如果产品确实需要OFFSET分页,又不想让每次深翻都回表十万次,可以用延迟关联。

思路是:先在索引里只查本页需要的 20 个主键,再用这 20 个主键去取完整数据。

SELECTo.*FROMorders oJOIN(SELECTidFROMordersORDERBYidLIMIT100000,20)tONt.id=o.idORDERBYo.id;

内层查询只取id,如果id上有索引,它可以只扫描索引,不需要为前十万行逐行回表。外层再拿这 20 个id去关联完整数据,回表次数就从十万级降到了 20 次。

但要说清楚:延迟关联减少的是回表成本,不是扫描成本。内层仍然要跳过前十万个索引项。对于特别深的页码,它能把几秒降到几百毫秒,但不一定能像游标分页那样稳定。

如果内层查询用的是覆盖索引,效果会更明显。比如按created_at排序,可以建立(created_at, id)索引,让内层只在这个索引里完成排序和取 ID。

5. 还有几种降低深分页成本的办法

如果业务允许,可以直接限制最大页码。搜索引擎通常只展示前几十页,不会让用户翻到第一百万条。后台管理系统也可以改成“按条件筛选”,而不是无限翻页。

对于数据量特别大的列表,可以预计算分页边界。比如按时间分区,先定位到某个月,再在小区间内分页。这样OFFSET始终在一个很小的范围内,成本就可控了。

如果列表需要复杂的搜索、排序和高亮,继续在 MySQL 上做深分页往往不划算。把数据同步到 Elasticsearch 这类搜索引擎,用search_after做游标分页,通常更合适。

另外,列表页的总数统计也要注意。SELECT COUNT(*)在千万级表上可能比列表查询还慢。可以缓存总数、使用近似统计,或者只显示“还有更多”,不必每次请求都精确统计。

6.OFFSET分页还有一个一致性问题

深分页不只是慢,还可能“翻着翻着就乱了”。

假设你正在看第 2 页,这时有人插入了一条排在前面的新订单。原本第 2 页的第一条会被挤到第 3 页,你翻到下一页时可能又看到它一次。反过来,如果前面的记录被删除,也可能有一条记录被你直接跳过。

游标分页基于“上一页最后一条的位置”,对插入和删除没那么敏感。它更适合不断有新数据进入的列表。OFFSET分页则更像“按当前排名取第 N 名”,数据一变,排名就会跟着变。


总结

LIMIT 100000, 20慢,不是那 20 条数据难取,而是数据库要先扫过并丢弃前面的十万条。游标分页用“上一页最后一条的位置”代替OFFSET,适合连续浏览;延迟关联先用覆盖索引取 ID,再回表取 20 条,能减少深分页的回表成本。选择哪种方式,要看页面是否允许随机跳页、排序字段是否稳定,以及数据会不会在翻页过程中变化。

参考资料

  • MySQL Reference Manual: LIMIT Optimization
  • MySQL Reference Manual: Range Optimization
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/4 12:03:10

LabVIEW国产替代推荐:自动化测试集成与项目管理适配说明

LabVIEW国产替代软件了解&#xff1a;自动化测试集成与项目管理适配说明 在工业自动化测试与仿真领域&#xff0c;测试软件的选型直接决定了研发效率与系统可靠性。当团队需要从传统方案切换到国产平台时&#xff0c;核心关注点往往集中在两个层面&#xff1a;其一&#xff0c;…

作者头像 李华
网站建设 2026/11/4 12:01:58

Java下载安装全攻略:一步到位配置运行环境

用户先需要把Java的那个安装包给下载下来, 然后之后才可以去做运行环境那个安装还有配置这些事情。1、 你打开Java的官方网站&#xff0c;把鼠标移到对应的位置等着, 一旦那个下拉菜单跳出现了之后, 你就可以用鼠标去点击其中的Java这个选项, 这样就能完成了。2、 用户通过把鼠…

作者头像 李华
网站建设 2026/11/4 11:58:06

第十四章 标准模块库(STL)

第十四章 标准模板库&#xff08;STL&#xff09;1. STL 概述1.1 六大部件STL 是 Standard Template Library 的缩写。名字里带模板两个字&#xff0c;第十三章那张图纸就是它的地基&#xff0c;容器和算法全靠模板撑起来。第一章提过&#xff0c;C98 这一版把 STL 迎进门&…

作者头像 李华