MySQL 社招必考题:如何优化LIMIT分页?

405 阅读5分钟



大家好,我是小米,31岁,依旧是个爱折腾数据库的程序员。今天想跟大家聊聊我在一次社招面试中遇到的一个问题 —— “MySQL LIMIT 分页怎么优化?”

故事开头:面试官的微笑

那天我去面试,面试官问:“小米啊,如果一个表有几百万条数据,我们要做分页查询,用 LIMIT offset, size,你会怎么优化?”

我当时心里咯噔一下,毕竟 LIMIT 我平时用得多,但真说到优化,还是得掰开揉碎讲出来才行。于是我就从 LIMIT 的本质问题 开始说起。

面试官笑了笑:“好,那你先讲讲 LIMIT 的缺点吧。”

LIMIT 的“慢病”

很多小伙伴可能习惯写这样的 SQL:

看上去没啥问题,分页嘛,第 100001 条开始取 20 条。但是,你知道 MySQL 内部是怎么干的吗?

它其实是从头开始扫,先把前 100000 条丢掉,再返回后 20 条。

换句话说,LIMIT 的偏移量越大,丢弃的数据越多,性能就越差。 想象一下:

  • 你点开外卖 APP,翻到第 1 页,嗖的一下很快。
  • 翻到第 5000 页?不好意思,外卖都凉了还没查出来。

所以,LIMIT 在大数据量分页时,性能就是一个灾难。

那该怎么办呢?我总结了几个方法,也正是我在面试时的回答。

记录上次查询的最大 ID(最常用)

这招简单粗暴:我们不要让数据库丢弃大量数据,而是用一个“锚点”。

比如第一页查询时:

假设返回的最后一条记录 ID 是 120。那么第二页我们就可以这样查:

这样,数据库只需要从 ID > 120 开始往后找,不用再数前面几十万条。

是不是很爽?这种方法通常叫 基于游标的分页(Keyset Pagination)

优点:

  • 查询效率高,尤其是大数据量时几乎不受影响。
  • ID 有索引,直接走索引范围扫描,非常快。

缺点:

  • 必须依赖一个 唯一且递增的字段(一般是主键 ID)
  • 不支持“跳页”,比如你直接想看第 500 页,就比较麻烦。

但大多数场景,用户都是“下一页、下一页”往下翻,这个优化非常实用。

覆盖索引优化

有些场景你可能必须要用 OFFSET,比如用户点开页面直接跳转第 N 页。

这时可以这样优化:

先用覆盖索引(只查 ID,不查所有字段),再通过 ID 回表:

这样做的好处是:

  • 减少了 MySQL 在 OFFSET 阶段需要处理的数据量。
  • 利用索引,避免了全表扫描。

虽然还是会丢弃很多数据,但比起全字段扫描要快得多。

延迟关联

这是上一种方式的进一步改进。先查 ID:

再把 ID 拿去和主表做关联:

好处是,MySQL 在处理大 OFFSET 时,依旧只需要扫 ID 索引,不会动用大字段,性能明显提升。

借助业务逻辑(缓存 & 限制页数)

有时候,技术优化不如“限制需求”来得直接。比如:

  • 限制最多翻到第 100 页,超过就不让查。
  • 对热门数据(比如最新的前几页)做缓存,避免重复分页查询。

这种方案虽然“土”,但在业务系统里非常常见。

面试中的深入追问

说到这里,面试官点点头,然后问了个关键问题:

“那如果这个表不是按照 ID 排序的,而是按照 create_time 呢?还能用你说的最大 ID 优化吗?”

我心里一紧,但立刻想到了解法。

如果是 create_time 排序,可以记录上一次分页的最后一个时间点,然后加上 ID 来避免时间重复,比如:

这样就能避免时间相同导致的漏查或重复问题。

面试官笑了笑:“不错,继续。”

真实踩坑经历

说实话,我在一个项目里就栽过 LIMIT 的坑。我们有个日志查询页面,用户可以翻到很后面去查历史记录。

一开始我们用的就是最简单的:

结果到了 10 万页的时候,SQL 慢到爆炸,服务器 CPU 飙升。后来我们改成 记录上次 ID 的分页,性能直接起飞,从 20 秒降到 50 毫秒以内。

所以我特别想跟大家说:

  • LIMIT 是好用的,但别滥用。
  • 大数据量下,一定要学会用 Keyset Pagination。

总结

回顾一下:

  1. LIMIT 的问题:偏移量大时会扫描并丢弃大量数据,效率极低。
  2. 优化方式
  • 基于游标的分页(记录最大 ID)。
  • 覆盖索引 + 延迟关联。
  • 利用业务逻辑限制页数或加缓存。

扩展场景:排序字段不是 ID 时,可以组合时间戳 + 主键做锚点。

最后,面试官笑着说:“小米,你这回答挺全面的。”

我心里一松,感觉这关算是过了。

END

写到这里,我想说:技术的本质是为业务服务。分页查询这种小问题,看似平常,但在高并发、大数据量系统里,真的能决定一个页面是“秒开”还是“崩溃”。

所以,下次你再写 LIMIT,别忘了想想:是不是可以换一种方式更高效?

我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!