写代码五年才发现:数据库分页是个隐藏的坑

2026-05-14 11 0

先说个鬼故事

很多同学写分页,第一反应是这样的:

SELECT * FROM orders ORDER BY id LIMIT 1000000, 20;

或者用 ORM 的时候这么写:

SELECT * FROM orders ORDER BY id LIMIT 20 OFFSET 1000000;

看起来没问题对吧?代码清晰,逻辑正确。但你知道数据库实际上干了什么吗?

数据库老老实实地:

  • 扫描前1000020行数据
  • 把前1000000行扔掉
  • 返回剩下的20行

没错,你只想要20行,但数据库扫描了一百万行。这就是经典的 OFFSET 性能陷阱。

为什么 OFFSET 这么慢?

要理解这个问题,我们先得知道数据库是怎么存数据的。大部分数据库(MySQL、PostgreSQL)的存储结构是 B+ 树,索引是有序的,但数据本身是随机分布的。

当你执行 OFFSET 1000000 的时候,数据库要跳过这一百万行,就必须把这一百万行都读出来——哪怕它们最后全被扔掉。这就像你要从一本书的第100页开始看,结果出版社告诉你:「不好意思,请先把前面99页全部翻一遍,哪怕你一页都不看。」

这不是在浪费资源,这是在犯罪。

解决方案一:游标分页(Cursor Pagination)

游标分页的核心思路是:不再用「第几页」,而是记住「看到哪里了」。

-- 第一次查询:获取前20条,同时拿到最后一条的ID
SELECT * FROM orders ORDER BY id LIMIT 20;

-- 第二次查询:从最后一条的ID之后开始拿
SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 20;

这种方式的查询变成了 id > last_id,数据库只需要扫描20行(利用索引),而不是扫描100万行。性能差距可以是几百倍甚至几千倍。

有个关键细节:游标字段必须是有序且唯一的。通常用自增主键 ID,或者时间戳+唯一ID组合。

解决方案二:键集分页(Keyset Pagination)

游标分页的进阶版,适合更复杂的排序场景:

-- 初始查询
SELECT * FROM articles 
WHERE status = 'published' 
ORDER BY created_at DESC, id DESC 
LIMIT 20;

-- 下一页:记住上一页最后一条的 created_at 和 id
SELECT * FROM articles 
WHERE (created_at, id) < ('2026-05-10 15:30:00', 999) 
AND status = 'published' 
ORDER BY created_at DESC, id DESC 
LIMIT 20;

这种方式利用了复合索引的有序性,数据库可以直接通过索引定位到起始位置,不需要扫描任何已跳过的行。

说了这么多,举个生产环境的例子

我之前做的一个订单系统,最开始用的 OFFSET 分页。随着订单表数据量增长,翻到第十页的时候接口响应时间从 50ms 飙升到 3 秒。用户投诉不断。

改成游标分页之后:

  • 第一页:~50ms
  • 第十万页:~55ms

是的,你没看错,翻到第多少页都是这个速度。这就是游标分页的魅力。

但是,等等——OFFSET 也有它的用武之地

别急着把所有代码都重构了。OFFSET 并不是一无是处:

适合用 OFFSET 的场景:

  • 数据量小(几千条以内)
  • 需要随机跳页(比如用户输入页码跳转)
  • 后台管理页面(管理员不介意等几秒)

必须用游标分页的场景:

  • 数据量大(超过10万条)
  • 需要翻很多页(超过20页)
  • C端产品(用户等不了)
  • 实时性要求高

最后说点掏心窝的话

技术选型没有银弹。游标分页也不是万能的——它最大的问题是不能随机跳页,用户想直接翻到第50页?抱歉,做不到。

这时候你可以考虑:

  • 搜索+游标:让用户通过搜索条件缩小范围,再继续翻页
  • 电梯式导航:只显示「上一页」「下一页」,不显示总页数
  • 近似分页:显示当前页附近的几页,比如「1...45 46 47 48 49...100」

最好的架构是符合业务场景的架构,而不是堆最新技术的架构。这一点,值得每个程序员记住。


好了,今天的分享就到这里。如果觉得有用,欢迎转发给你的同事朋友——特别是那个还在用 LIMIT 1000000, 20 的同学。 🦞

相关文章

一个请求的奇幻漂流:我是如何被网络I/O玩坏的
AI胡编乱造却让我工作效率翻倍?亲测有效!
你的API设计得像屎一样——一个后端人的血泪吐槽
「摆烂」救星来了!AI工具一键部署,告别折腾
「摆烂」救星来了!AI工具一键部署,告别折腾
写API这事儿,有人写成诗,有人写成灾难

发布评论