跳至主要内容

MongoDB 查询 API 入门

通过本指南掌握 MongoDB 查询 API,全面覆盖 CRUD 操作、高级过滤、数据聚合以及提升性能的索引。
· 7分钟 阅读

使用 AI 探索

作为一款广受欢迎的 NoSQL 数据库,MongoDB 为数据管理提供了灵活且可扩展的方式。本文将带您从零开始掌握其直观的Query API,它允许您通过简洁的BSON(二进制 JSON)与数据交互,并针对效率与数据类型进行了优化。

在本文中,我们将讲解基础的创建、读取、更新、删除(CRUD)操作,深入精确查询中的过滤与投影,探索聚合管道在数据转换中的强大能力,并讨论通过索引来提升性能。读完后,您将具备在 MongoDB 集合中高效检索与操作数据的扎实基础。

基础概念:CRUD 操作

任何数据库交互的核心都是 CRUD:创建、读取、更新与删除。下面我们看看在 MongoDB 中如何完成这些操作。

创建数据(insert)

要向集合添加新文档,请使用insert 方法。MongoDB 中的文档是类 JSON 对象。

insertOne()

该方法用于向集合添加单个文档。

// A sample document to insert
const newProduct = {
  name: "Wireless Mouse",
  brand: "Logitech",
  price: 29.99,
  inStock: true
};

// Insert the new document into the 'products' collection
db.products.insertOne(newProduct);

insertMany()

该方法可一次性添加多个文档,比逐个插入高效得多。

// An array of documents to insert
const newProducts = [
  { name: "Mechanical Keyboard", brand: "Corsair", price: 129.99, inStock: false },
  { name: "Gaming Headset", brand: "Razer", price: 99.99, inStock: true },
  { name: "Webcam", brand: "LogiTech", price: 79.99, inStock: true }
];

// Insert the array of documents into the 'products' collection
db.products.insertMany(newProducts);

读取数据(find)

find() 方法是从集合检索文档的主要工具。调用find()时,并不会立刻返回所有文档本身,而是返回一个游标(cursor)。这是一种高效方式,可在不一次性将所有结果加载到内存的情况下处理潜在的大结果集。

最简单的find() 调用不带参数,返回指向集合中每个文档的游标。

// Find and return a cursor to all documents in the 'products' collection
db.products.find();

下面是在mongosh(MongoDB Shell)中该游标对象的示例,以及您可能与之交互的方式:

// Find all products and assign the returned cursor to a variable
const productCursor = db.products.find();

// The variable 'productCursor' now holds the cursor object, not the documents themselves.
// To get the documents, you need to iterate over the cursor.

// You can convert the cursor to an array to see all the documents
const allProducts = productCursor.toArray();
console.log(allProducts);

// Or, you can manually iterate through the documents
while (productCursor.hasNext()) {
  const product = productCursor.next();
  console.log(product);
}

游标本身是一个对象,包含诸如toArray()、hasNext() 和 next() 等方法,可用于检索并处理结果集中的文档。

更新数据(update)

要修改现有文档,请使用update 方法。这些方法接收两个主要参数:用于选择要更新文档的过滤条件(filter)与用于指定更改内容的更新操作符(update operator)。常见的操作符是$set,用于设置某个字段的值。

updateOne()

该方法更新匹配过滤条件的第一个文档。

// Update the 'Wireless Mouse' document to set its price to 39.99
db.products.updateOne(
  { name: "Wireless Mouse" }, // Filter to find the document
  { $set: { price: 39.99 } } // Update operator
);

updateMany()

该方法会更新匹配指定过滤条件的所有文档。

// Update all 'LogiTech' brand products to be in stock
db.products.updateMany(
  { brand: "LogiTech" }, // Filter to find the documents
  { $set: { inStock: true } } // Update operator
);

删除数据(delete)

要移除文档,请使用delete 方法。这些方法同样接受过滤条件来确定要删除的文档。

deleteOne()

该方法会删除匹配过滤条件的第一个文档。

// Delete the 'Webcam' document
db.products.deleteOne({ name: "Webcam" });

deleteMany()

该方法会删除所有匹配过滤条件的文档。若要删除集合中的所有文档,可以传入空过滤条件{}。

// Delete all documents that are not in stock
db.products.deleteMany({ inStock: false });

精确检索:查询过滤器

查询过滤器是 MongoDB 强大能力的核心。它们是 find() 与 update 方法中的第一个参数,使您可以精确选择要处理的文档。

比较操作符

这些操作符用于将字段值与指定值进行比较。

$eq(等于)

查找字段等于某个值的文档。

// Find all products that are made by 'Razer'
db.products.find({ brand: { $eq: "Razer" } });
// This can be simplified to:
// db.products.find({ brand: "Razer" });

$gt 与 $gte(大于、大于等于)

查找字段值大于($gt)或大于等于($gte)指定值的文档。

// Find all products with a price greater than 100
db.products.find({ price: { $gt: 100 } });

$lt 与 $lte(小于、小于等于)

查找字段值小于($lt)或小于等于($lte)指定值的文档。

// Find all products with a price less than or equal to 50
db.products.find({ price: { $lte: 50 } });

$ne(不等于)

$ne 查找字段不等于指定值的文档。

// Find all products that are NOT made by 'LogiTech'
db.products.find({ brand: { $ne: "LogiTech" } });

$in 与 $nin(在数组中、不在数组中)

查找字段值位于($in)或不在($nin)指定值数组中的文档。

// Find all products from 'Razer' or 'Corsair'
db.products.find({ brand: { $in: ["Razer", "Corsair"] } });

逻辑操作符

逻辑操作符可组合多个条件以构建复杂查询。

$and

$and 使用逻辑与组合查询条件,所有条件均需为真。

// Find all products with a price greater than 50 AND are in stock
db.products.find({ $and: [{ price: { $gt: 50 } }, { inStock: true }] });
// This can be simplified by just listing the conditions:
// db.products.find({ price: { $gt: 50 }, inStock: true });

$or

使用逻辑或组合查询条件,至少一个条件为真即可。

// Find all products with a brand of 'Corsair' OR a price less than 40
db.products.find({ $or: [{ brand: "Corsair" }, { price: { $lt: 40 } }] });

元素与评估操作符 

这些操作符提供了更灵活的过滤方式。

$exists

$exists 匹配包含特定字段的文档。

// Find all documents that have an 'inStock' field
db.products.find({ inStock: { $exists: true } });

$regex

允许在字符串字段中使用正则表达式进行模式匹配。

// Find all product names that start with 'Wireless'
db.products.find({ name: { $regex: /^Wireless/ } });

塑造输出:投影(Projection)

执行find() 操作时,您可以仅返回每个文档的一部分字段。这称为投影(projection),它是传给find() 的第二个参数,有助于减少传输与处理的数据量。

包含字段

将要返回的字段值设为1 即可指定要返回的字段。

// Find all products and return only the 'name' and 'price' fields
db.products.find({}, { name: 1, price: 1 });

排除字段

相反地,将字段值设为0 可指定要隐藏的字段。

// Find all products and hide the 'inStock' field
db.products.find({}, { inStock: 0 });

除非显式排除,否则_id 字段将默认返回。

_id 字段

若要隐藏_id 字段,必须显式将其设为0。

// Find all products, returning only 'name' and 'price', and hiding '_id'
db.products.find({}, { _id: 0, name: 1, price: 1 });

高级数据转换:聚合管道

聚合是 MongoDB 中功能强大的数据处理框架。聚合管道由一系列阶段组成,按顺序处理文档并将结果从一个阶段传递到下一个阶段。

$match

$match 阶段基于查询过滤器筛选文档,与 find() 类似。它通常作为第一阶段,用于减少需要处理的文档数量。

// Find all products with a brand of 'LogiTech'
db.products.aggregate([
  { $match: { brand: "LogiTech" } }
]);

$group

$group 阶段根据指定的 _id 表达式对文档进行分组,并可执行多种累加操作,如对某字段求和或求平均值。

// Group products by brand and calculate the total number of products for each brand
db.products.aggregate([
  { $group: { _id: "$brand", totalProducts: { $sum: 1 } } }
]);

$project

$project 阶段会重塑数据流中的每个文档,类似于 find() 中的投影。您可以新增字段、移除现有字段,并对数据进行处理。

// Project a new field 'nameAndPrice' for each document
db.products.aggregate([
  { $project: { name: 1, price: 1, nameAndPrice: { $concat: ["$name", " (", "$price", ")"] } } }
]);

$sort

$sort 阶段会根据指定字段对文档重新排序。值为 1 表示升序,-1 表示降序。

// Sort all products by price in descending order
db.products.aggregate([
  { $sort: { price: -1 } }
]);

$limit 与 $skip

这些阶段用于分页。$skip 会丢弃管道开头的指定数量文档,$limit 则限制传递到下一阶段的文档数量。

// Skip the first 10 products and then return the next 5
db.products.aggregate([
  { $skip: 10 },
  { $limit: 5 }
]);

简单管道示例

下面是一个完整示例,展示这些阶段如何协同工作。

// Find the average price of all products that are currently in stock,
// grouped by brand.
db.products.aggregate([
  { $match: { inStock: true } }, // Filter for products in stock
  { $group: { _id: "$brand", averagePrice: { $avg: "$price" } } } // Group and calculate average price
]);

提升性能:索引

索引是特殊的数据结构,它们存储了集合数据中一小部分、易于遍历的内容。通过让 MongoDB 无需扫描整个集合即可快速定位并检索文档,索引能显著提升查询性能。

createIndex()

应在经常用于查询或排序的字段上创建索引。

// Create a single-field index on the 'brand' field
db.products.createIndex({ brand: 1 });

数值 1 表示按升序建立索引。若为降序,可使用 -1,对按该方向排序的查询会更有利。

在类似 brand 的字段上创建索引,将使按 brand 过滤的查询(如 db.products.find({ brand: "Razer" }))在集合规模增长时依然能够快速执行。

选择索引字段的考量

通常应在查询中经常使用的字段上创建索引,例如 find() 的过滤条件或聚合的 $match 阶段中使用的字段。

  • 基数(Cardinality): 具有大量唯一值(高基数)的字段,如用户邮箱地址,非常适合建立索引。
  • 查询选择性(Query selectivity): 能为给定查询返回较小子集文档的字段也是很好的候选。
  • 排序(Sorting): 若您经常按某个字段对结果排序,在该字段上创建索引会显著加快排序操作。

对查询的影响

索引对性能影响巨大。带有索引的查询通常会比不带索引的同类查询快上数个数量级。

// Conceptual illustration of performance difference

// Without an index on 'brand':
// MongoDB performs a full collection scan, checking every document.
// This is slow, especially with millions of documents.
db.products.find({ brand: "Razer" });

// With an index on 'brand':
// MongoDB uses the index to quickly locate and return only the matching documents.
// This is very fast.
db.products.find({ brand: "Razer" });

若需进一步优化查询,可以使用 db.collection.explain() 方法分析查询性能并查看索引的使用情况。

结语:迈向 MongoDB 熟练之路

您刚刚完成了 MongoDB Query API 的速成课程!现在,您已了解如何执行核心 CRUD 操作,使用强大的过滤器检索特定数据,通过投影塑造输出,并利用聚合管道转换数据。您也对通过索引提升性能有了基本认识。

本指南为您打下了坚实基础,而掌握这些概念的最佳方式就是实践。建议您多尝试不同的查询,并查阅完整文档以探索更高级的操作符与功能。编码愉快!

MongoDB 查询 API 常见问题

find() 和 aggregate() 的主要区别是什么?

find() 方法用于进行带可选过滤与投影的简单文档检索。而 aggregate() 则是一个功能强大的多阶段数据处理框架,允许以更复杂的方式对文档进行转换、分组、排序与重塑。简言之,find() 用于按原样读取文档,aggregate() 用于从数据中提炼新的洞见。

为什么 MongoDB 在 find() 操作中返回的是游标而非实际文档?

游标是一种高效处理潜在大结果集的方式。通过返回游标,MongoDB 避免一次性将所有文档加载到内存。这对性能与可扩展性至关重要,尤其在处理数百万文档时。游标允许您按需遍历结果,并以批次方式获取文档。

何时应使用 updateOne() 与 updateMany()?

当您只需修改匹配过滤条件的第一个文档时,请使用 updateOne()。这非常适用于预计仅有一个文档匹配的场景,例如按唯一标识符过滤。若希望更新所有满足过滤条件的文档,请使用 updateMany()。

索引如何提升查询性能?

索引是存储集合数据中小而有序子集的特殊数据结构。它们能显著加速查询,使 MongoDB 无需扫描整个集合即可快速定位文档。数据库无需逐个检查每条文档,而是利用索引直接跳转到匹配文档,从而使操作速度提升数个数量级。

我能在聚合中使用投影吗?

是的,投影是聚合管道中的关键组成部分。$project 阶段用于重塑文档、添加新字段或移除现有字段。它常在 $match 或 $group 等阶段之后使用,以格式化管道输出,让您对最终数据结构拥有精细的控制。

Karen Zhang

作者

Karen Zhang

Karen 是一名数据工程师,热衷于构建可扩展的数据平台。她拥有使用 Terraform 进行基础设施自动化的经验,并很乐于通过博客文章和教程分享她的所学。Karen 也是一位社区建设者,热衷于促进数据专业人士之间的联结。

主题