Skip to content

MongoDB - 排序记录

文档排序是数据库的基本操作之一。本章将深入探讨如何在 MongoDB 中使用现代最佳实践对查询结果进行排序。

要对 find() 查询返回的文档进行排序,可以链式调用 sort() 方法。此方法接受一个文档,该文档指定了要排序的字段及其排序顺序。使用 1 表示升序,-1 表示降序。

db.collectionName.find(query).sort({ field1: 1, field2: -1, ... })

首先,让我们向 products 集合插入一些示例数据。请注意,我们使用了现代 BSON 类型,例如用于整数值的 NumberInt。

// 确保集合为空,以便进行干净的测试
db.products.drop();
// 使用 insertMany 插入示例文档
db.products.insertMany([
{ name: "Laptop Pro", category: "Electronics", price: 1200, stock: 15 },
{ name: "Wireless Mouse", category: "Accessories", price: 25, stock: 200 },
{ name: "Mechanical Keyboard", category: "Accessories", price: 75, stock: 50 },
{ name: "4K Monitor", category: "Electronics", price: 400, stock: 30 },
{ name: "Webcam HD", category: "Accessories", price: 75, stock: 0 }
]);

现在,让我们按价格降序(最贵优先)对这些产品进行排序:

> db.products.find().sort({ price: -1 })
// 结果:
[
{ _id: ..., name: 'Laptop Pro', category: 'Electronics', price: 1200, stock: 15 },
{ _id: ..., name: '4K Monitor', category: 'Electronics', price: 400, stock: 30 },
{ _id: ..., name: 'Mechanical Keyboard', category: 'Accessories', price: 75, stock: 50 },
{ _id: ..., name: 'Webcam HD', category: 'Accessories', price: 75, stock: 0 },
{ _id: ..., name: 'Wireless Mouse', category: 'Accessories', price: 25, stock: 200 }
]

对于多字段排序,MongoDB 会按照字段列出的顺序进行排序。让我们先按类别升序排序,然后在每个类别内按价格降序排序:

> db.products.find().sort({ category: 1, price: -1 })
// 结果:
[
{ _id: ..., name: 'Mechanical Keyboard', category: 'Accessories', price: 75, stock: 50 },
{ _id: ..., name: 'Webcam HD', category: 'Accessories', price: 75, stock: 0 },
{ _id: ..., name: 'Wireless Mouse', category: 'Accessories', price: 25, stock: 200 },
{ _id: ..., name: 'Laptop Pro', category: 'Electronics', price: 1200, stock: 15 },
{ _id: ..., name: '4K Monitor', category: 'Electronics', price: 400, stock: 30 }
]

排序可能是一项资源密集型操作,尤其是在大型数据集上。如果排序操作没有索引支持,MongoDB 必须在内存中执行“阻塞式排序”(blocking sort)。如果此操作需要的 RAM 超过 32 兆字节,则查询将失败。

为了确保高效排序,您应该创建与排序键匹配的索引。对于上述多字段排序,理想的索引将是:

> db.products.createIndex({ category: 1, price: -1 })

有了这个索引,MongoDB 可以直接从索引中读取预排序的文档,这大大加快了速度,并避免了内存排序的限制。

对于更复杂的场景,排序通常作为聚合管道 (aggregation pipeline) 中的一个阶段 (stage) 完成,使用 $sort 操作符。这允许您在排序数据之前对其进行转换。

// 示例:查找所有配件,按价格排序,并仅投影名称和价格字段
db.products.aggregate([
{ $match: { category: "Accessories" } },
{ $sort: { price: -1 } },
{ $project: { _id: 0, name: 1, price: 1 } }
])
// 结果:
[
{ name: 'Mechanical Keyboard', price: 75 },
{ name: 'Webcam HD', price: 75 },
{ name: 'Wireless Mouse', price: 25 }
]
  • 超出内存限制: 如果您看到类似 Sort operation used more than the maximum 32MB of RAM 的错误,这明确表明您的排序查询缺少适当的索引。
  • 区分大小写: 排序文档中的字段名是区分大小写的,必须与文档中的字段完全匹配。
  • 使用 explain() 调试: 要验证您的排序是否使用了索引,请使用 explain() 方法:db.products.find().sort({ price: -1 }).explain('executionStats')。在输出中查找使用 IXSCAN(索引扫描)的 FETCH 阶段,并确保计划中没有 SORT 阶段,这表明这是一个高效的、使用索引的排序。