MongoDB Limit 与 Skip 方法

MongoDB limit()skip() 方法完全解析

(MongoDB 8.0+,2025 年最新实践)

核心结论
limit(n) = 返回前 n 条文档
skip(n) = 跳过前 n 条文档
常用于分页,但 skip 在大数据量时性能差,生产建议用“游标分页”或“范围查询”


一、基本语法

方法作用示例
.limit(n)限制返回 最多 n 条.limit(10)
.skip(n)跳过前 n 条.skip(20)
// 查询第 3 页,每页 10 条(第 21~30 条)
db.users.find()
  .sort({ createdAt: -1 })  // 必须排序!
  .skip(20)                 // 跳过前 20 条
  .limit(10)                // 返回 10 条

二、典型应用:分页

// 第 page 页,每页 size 条
const page = 3;
const size = 10;

db.posts.find()
  .sort({ createdAt: -1 })
  .skip((page - 1) * size)   // 关键:(page-1)*size
  .limit(size)
  .pretty()
页码skiplimit返回
10101~10
2101011~20
3201021~30

三、性能分析:skip 是性能杀手!

数据量skip(0)skip(1000)skip(10000)skip(100000)
执行时间稍慢明显变慢极慢(秒级)

原因
MongoDB 必须扫描并丢弃前 n 条,即使有索引也无法跳过。

// 坏例子:跳过 10 万条
db.logs.find().skip(100000).limit(10)  // 扫描 100,010 条!

// 好例子:用范围查询(推荐)
db.logs.find({ _id: { $gt: lastId } }).limit(10)

四、生产级分页方案(替代 skip

方案 1:基于 _id 或排序字段的范围查询(推荐)

// 第一页:获取最后一条 _id
const firstPage = db.posts.find()
  .sort({ createdAt: -1 })
  .limit(10)
  .toArray()

const lastId = firstPage[firstPage.length - 1].createdAt

// 第二页:从 lastId 继续
db.posts.find({ createdAt: { $lt: lastId } })
  .sort({ createdAt: -1 })
  .limit(10)

优点

  • 使用索引,O(log n)
  • 支持“下一页”,不支持“上一页”
  • 适合无限滚动

方案 2:基于游标的分页(Cursor-based)

// 服务端保存 cursor
let cursor = db.posts.find().sort({ _id: 1 }).batchSize(10)

// 客户端请求下一页
const docs = await cursor.next()  // Node.js Driver

优点

  • 零成本翻页
  • 支持双向
  • 需维护 cursor 状态

方案 3:聚合管道 + $facet(复杂统计分页)

db.posts.aggregate([
  {
    $facet: {
      data: [
        { $match: { status: "published" } },
        { $sort: { createdAt: -1 } },
        { $skip: 20 },
        { $limit: 10 }
      ],
      meta: [
        { $count: "total" }
      ]
    }
  }
])

五、limitskip 的注意事项

注意点说明
必须配合 sort()否则分页顺序不一致
skip + 大数 = 性能灾难避免 skip(>10000)
limit(0) = 返回所有等价于无限制
skip 可为负数无效,MongoDB 忽略
游标超时默认 10 分钟,长时间分页需 noCursorTimeout()
// 安全:防止游标超时
db.collection.find()
  .sort({ _id: 1 })
  .skip(0)
  .limit(10)
  .noCursorTimeout()

六、GUI 工具中的 limitskip

工具操作
MongoDB Compass右侧面板 → Skip / Limit
MongoDB AtlasQuery Bar → { $skip: 10, $limit: 10 }
VS Code查询框支持 .skip().limit()

七、完整分页脚本(生产可用)

// pagination.js
function paginate(collection, page = 1, size = 10, sort = { createdAt: -1 }) {
  const skip = (page - 1) * size

  // 方式 1:传统 skip + limit(小数据量)
  if (skip < 1000) {
    return collection.find()
      .sort(sort)
      .skip(skip)
      .limit(size)
      .toArray()
  }

  // 方式 2:范围查询(大数据量)
  const lastDoc = collection.find()
    .sort(sort)
    .skip(skip)
    .limit(1)
    .toArray()[0]

  if (!lastDoc) return []

  const sortField = Object.keys(sort)[0]
  const sortValue = lastDoc[sortField]
  const operator = sort[sortField] === -1 ? "$lt" : "$gt"

  return collection.find({
    [sortField]: { [operator]: sortValue }
  })
  .sort(sort)
  .limit(size)
  .toArray()
}

// 使用
use blog
const page3 = paginate(db.posts, 3, 10)
page3.forEach(printjson)

八、性能对比测试

// 测试 100万条数据
db.test.insertMany(Array.from({length: 1000000}, (_, i) => ({i, ts: new Date()})))

// 方案 A:skip (慢)
db.test.find().sort({i:1}).skip(500000).limit(10)  // ~800ms

// 方案 B:范围查询 (快)
db.test.find({i: {$gt: 500000}}).sort({i:1}).limit(10)  // ~2ms

九、一句话总结

**“limit(n) 控制数量,skip(n) 控制偏移,但大数据量跳过 skip,改用 **范围查询 + 排序字段


快速模板

// 传统分页(小数据)
.skip((page-1)*10).limit(10)

// 推荐:范围分页
{ createdAt: { $lt: lastSeenDate } }
.sort({ createdAt: -1 })
.limit(10)

// 无限滚动
{ _id: { $gt: lastId } }
.sort({ _id: 1 })
.limit(20)

官方文档

  • https://www.mongodb.com/docs/manual/reference/method/cursor.limit/
  • https://www.mongodb.com/docs/manual/reference/method/cursor.skip/

如需 前端分页组件集成缓存分页结果从 SQL LIMIT OFFSET 迁移实时分页订阅,欢迎继续提问!

文章已创建 2349

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部