·更新于 2026年10月25日· 11 次浏览 Nuxt 3MongoDBNode.js分页性能优化工程实践

Nuxt 3 列表越翻越慢:我把 skip 分页改成 _id 游标分页

一次文章列表深分页接口超时排查:讲清 MongoDB skip 的扫描成本,并用复合索引与 _id 游标做稳定翻页。

Nuxt 3 列表越翻越慢:我把 skip 分页改成 _id 游标分页 封面
ZHOUYI

作者

ZHOUYI

后台文章列表翻到第 50 页后,Nuxt 3 的服务端渲染开始明显变慢,接口日志里出现好几次超时。排查后发现,问题不在 Nuxt,而在 Node API 的 MongoDB 分页:我一直在用 skip + limit。前几页很快,越往后越像拖着沙袋跑。

为什么 skip 越深越慢

skip 的问题很朴素:MongoDB 要先按排序找到目标区间,再跳过前面的文档。即使走了索引,深页也要扫描并丢弃大量索引项或文档。页数越深,扫描成本越高,接口耗时自然线性上涨。对内容站来说,用户可能不会真翻到 200 页,但爬虫、预览和运营后台会。

把页码换成 _id 游标

我把传统页码改成游标分页:不再告诉数据库“跳过多少条”,而是告诉它“从某个位置之后继续取”。列表按 _id 倒序展示时,上一页最后一条的 _id 就是游标。查询条件加上 _id 小于游标,再配合 { status: 1, _id: -1 } 复合索引,就能稳定取下一页。

javascript
// 需要按 _id 倒序,并建 { status: 1, _id: -1 } 索引
const cursor = typeof query.cursor === 'string' ? query.cursor : ''
const filter = { status: 'published' }
if (cursor) filter._id = { $lt: new ObjectId(cursor) }
const posts = await db.collection('posts')
  .find(filter)
  .sort({ _id: -1 })
  .limit(20)
  .toArray()
const nextCursor = posts.at(-1)?._id || ''

改完后最直观的变化是:接口耗时不再随页码增长。无论第一屏还是深页,数据库都只按索引取一小段数据,超时告警也消失了。对 SEO 来说,列表页链接仍可抓,但我不再鼓励无限深翻,而是把内容按时间归档。

踩坑与边界

  • 游标分页不能直接跳到第 100 页,它更适合“下一页”或信息流场景。
  • 排序字段必须稳定。若按发布时间排序,最好把发布时间和 _id 一起做复合游标,避免同秒数据重复或漏掉。
  • 索引要覆盖过滤和排序。只改查询不建索引,深分页还是会慢;具体索引写法以官方文档为准。
  • Nuxt 侧把 cursor 放在 URL query 里即可,但页面缓存要区分匿名列表和用户态内容,别把登录态混进去。

“这次修复给我的提醒是:分页不是简单页码,而是查询边界。先把慢查询的扫描成本降下来,再谈首屏、SEO 和广告位,才有意义。”

Guestbook

留言

若这篇文章也曾停在你心上,留一行字吧,我会看的。

先确认一下邮箱

多这一步,是为了少些机器骚扰,也让我能更踏实地读每一条真人写下的字。验证码 10 分钟内有效;同一邮箱 60 秒内只能发一封。

本文留言

共 0 条已加载 0 条

这里还静悄悄的——愿意做第一个留下脚印的人吗?