Skip to content

MongoDB - 投影

在 MongoDB 中,投影(Projection)是一种机制,用于从文档中仅选择必要的字段,而不是检索整个文档。这是优化性能的关键实践,特别是通过减少网络流量和客户端内存使用。

find() 方法支持一个投影文档,用于指定要返回的字段。您可以将其作为 find() 的第二个参数传递,或者在现代驱动程序和 shell 中更常见地通过链式调用 .project() 方法。

规则很简单:使用 1 表示包含字段,使用 0 表示排除字段。默认情况下,_id 字段总是包含在内,除非明确排除。

// 现代首选语法,使用 .project()
db.COLLECTION_NAME.find({/* query */}).project({/* projection */});
// 传统语法
db.COLLECTION_NAME.find({/* query */}, {/* projection */});

考虑一个包含详细用户配置文件的 users 集合:

{
"_id": ObjectId("..."),
"username": "alex_doe",
"email": "alex.doe@example.com",
"profile": {
"firstName": "Alex",
"lastName": "Doe",
"dob": ISODate("1990-05-15T00:00:00Z")
},
"loginHistory": [ /* ... 大型数组 ... */ ]
}

如果我们只需要显示用户名和电子邮件列表,检索庞大的 loginHistory 数组将是浪费的。我们可以使用投影来只获取所需的内容。

// 仅包含 username 和 email,并排除 _id
db.users.find({}).project({ username: 1, email: 1, _id: 0 });

结果将如下所示,仅包含投影的字段:

{
"username": "alex_doe",
"email": "alex.doe@example.com"
}

您可以使用点表示法来投影嵌入文档中的字段。

// 获取用户名和 profile 中的 firstName
db.users.find({}).project({
username: 1,
"profile.firstName": 1,
_id: 0
});

结果:

{
"username": "alex_doe",
"profile": {
"firstName": "Alex"
}
}
  • 包含与排除: 您不能在同一个投影中混合使用包含(1)和排除(0)指定符,只有一个例外:在包含投影中,您始终可以显式排除 _id 字段(_id: 0)。
  • _id 字段:_id 字段默认始终返回。它是您在使用包含投影时唯一可以显式排除的字段。
  • 性能提升:始终只投影应用程序所需的字段。这可以最大限度地减少网络上的数据传输,并降低应用程序的处理负载。
  • 覆盖查询:投影是创建“覆盖查询”的关键。当查询的过滤器和投影可以完全由索引满足时,MongoDB 无需从磁盘读取文档,从而带来巨大的性能提升。要使查询成为覆盖查询,投影中的所有字段都必须是索引的一部分。

一个常见的错误是尝试混合包含和排除。

// 这将导致错误
db.users.find({}).project({ username: 1, loginHistory: 0 });
// 错误消息:
// "Projection cannot have a mix of inclusion and exclusion"

要实现这一点,您必须要么包含除 loginHistory 之外的所有字段,要么排除 loginHistory(这将返回所有其他字段)。