首页 / MongoDB 入门教程 / 排序、限制与分页

MongoDB 入门教程

排序、限制与分页

本教程共 50 篇 · 第 21 篇 · 更新于 2026-07-30 · 约 7 分钟阅读

MongoDBMongoDB 入门教程排序分页sort深分页

21. 排序、限制与分页

本节目标:学会用 sort、limit、skip 给查询结果排序和翻页,并理解为什么「跳过很多条」的深分页会很慢。

查出来的文档默认按 _id 顺序返回,不一定是你想要的顺序。排序(sort)、限制(limit)、跳过(skip)这三个方法就是用来收拾结果的。它们都接在 find 后面,用点号串起来。

db.users.insertMany([
  { _id: 1, name: "张三", age: 28, city: "北京" },
  { _id: 2, name: "李四", age: 34, city: "上海" },
  { _id: 3, name: "王五", age: 22, city: "北京" },
  { _id: 4, name: "赵六", age: 41, city: "广州" },
  { _id: 5, name: "孙七", age: 30, city: "上海" }
])

排序:sort()

sort() 接收一个文档,字段写 1 表示升序、-1 表示降序:

// 按年龄从大到小排
db.users.find({}, { name: 1, age: 1, _id: 0 }).sort({ age: -1 })

多个字段也可以排,前面的优先:

// 先按城市升序,城市相同再按年龄降序
db.users.find({}, { name: 1, city: 1, age: 1, _id: 0 })
  .sort({ city: 1, age: -1 })
Note

排序方向只认 1-1。想按「最近注册」排,就对 registered_at 这样的日期字段降序即可。

限制条数:limit()

limit(n) 只保留前 n 条结果,常用来取「排行榜前几名」:

// 年龄最大的前两名
db.users.find({}, { name: 1, age: 1, _id: 0 })
  .sort({ age: -1 }).limit(2)

跳过条数:skip()

skip(n) 跳过前 n 条,常和 limit 搭配做分页:

// 第 2 页,每页 2 条(跳过前 2 条,再取 2 条)
db.users.find({}, { name: 1, age: 1, _id: 0 })
  .sort({ age: -1 }).skip(2).limit(2)

用 sort + skip + limit 做分页

经典分页套路就是「固定每页大小,靠 skip 移动起点」:

页码skiplimit含义
第 1 页02取第 1–2 条
第 2 页22取第 3–4 条
第 3 页42取第 5–6 条

公式:skip = (页码 - 1) * 每页大小

深分页的性能坑

skip 不是「直接跳到那一行」,而是 MongoDB 先扫出前面所有被跳过的文档再丢掉。翻到第 1000 页时,它其实读了前面 2000 条:

// 深分页:skip 很大,越往后越慢
db.orders.find({}).sort({ created_at: -1 }).skip(20000).limit(20)
Warning

深分页(大 skip)会随偏移量增大而明显变慢,因为它要扫描并丢弃大量文档。这是我在生产环境反复踩过的坑。

深分页的替代思路

真要做无限滚动或深翻页,更好的办法是用「上一页最后一个值」当游标。比如按时间倒序翻页:

// 第一页
db.orders.find({}).sort({ created_at: -1 }).limit(20)
// 下一页:只取比上一页最后时间更早的,彻底不要 skip
db.orders.find({ created_at: { $lt: ISODate("2024-06-12") } })
  .sort({ created_at: -1 }).limit(20)

这种「基于游标」的分页只靠索引定位,不丢数据,速度恒定。

Tip

小数据量用 skip 翻页没问题;一旦数据量大、要翻很深,换成「时间戳/ID 游标」分页才是正道。

排序、限制、分页就讲到这。下面我们进入更新(Update),先认识一批更新运算符。