在MongoDB中,复合索引是包含多个字段的索引,部分索引则是只对满足指定过滤条件的文档创建的索引,两者结合可以在特定查询场景下大幅提升性能。Mongoose作为Node.js环境下操作MongoDB的主流ODM,提供了完整的接口支持复合索引和部分索引的创建与查询。
复合索引与部分索引基础概念
复合索引
复合索引是建立在多个字段上的索引,查询时如果条件匹配索引的前缀字段,就可以利用该索引加速查询。例如对age和name字段创建复合索引,查询age字段或者同时查询age和name字段时都可以使用该索引。
部分索引
部分索引只会为集合中满足指定过滤条件的文档创建索引条目,相比普通索引可以减少索引存储空间,提升写入性能,同时针对符合条件的查询有更好的优化效果。过滤条件通过partialFilterExpression参数定义。
创建带部分索引的复合索引
首先需要在Mongoose的Schema中定义复合索引,并指定部分索引的过滤条件。以下是一个用户集合的示例,我们创建一个age和status的复合索引,只对status为active的文档建立索引:
const mongoose = require('mongoose');
const { Schema } = mongoose;
// 定义用户Schema
const userSchema = new Schema({
name: String,
age: Number,
status: String,
email: String
});
// 创建复合部分索引:对age和status字段建立复合索引,只对status为active的文档生效
userSchema.index(
{ age: 1, status: 1 },
{
partialFilterExpression: { status: 'active' },
name: 'age_status_partial_idx' // 可选,指定索引名称方便后续管理
}
);
// 创建模型
const User = mongoose.model('User', userSchema);
上述代码中,partialFilterExpression定义了部分索引的过滤规则,只有status字段值为active的文档才会被加入到这个复合索引中。
查询复合部分索引的文档
要触发复合部分索引生效,查询条件需要满足两个部分:一是匹配复合索引的字段,二是满足部分索引的过滤条件。以下是几种常见的查询场景:
场景1:查询条件匹配索引字段且满足过滤条件
当查询条件包含复合索引的前缀字段,同时满足部分索引的过滤条件时,查询会自动使用对应的部分索引:
// 查询age大于18且status为active的用户,会命中复合部分索引
async function queryActiveAdultUsers() {
try {
const users = await User.find({
age: { $gt: 18 },
status: 'active'
}).exec();
return users;
} catch (err) {
console.error('查询失败:', err);
throw err;
}
}
场景2:查询条件包含全部索引字段但不符合过滤条件
如果查询条件包含了复合索引的所有字段,但是不满足部分索引的过滤条件,那么不会使用该部分索引,可能会走全表扫描或者其他可用索引:
// 查询age大于18且status为inactive的用户,不会命中上述复合部分索引
async function queryInactiveAdultUsers() {
try {
const users = await User.find({
age: { $gt: 18 },
status: 'inactive'
}).exec();
return users;
} catch (err) {
console.error('查询失败:', err);
throw err;
}
}
场景3:查询条件不匹配索引前缀
复合索引遵循前缀匹配原则,如果查询条件只包含复合索引的非前缀字段,即使满足部分索引过滤条件,也不会使用该复合索引:
// 只查询status为active的用户,不包含age字段,不会命中age和status的复合索引
async function queryActiveUsers() {
try {
const users = await User.find({
status: 'active'
}).exec();
return users;
} catch (err) {
console.error('查询失败:', err);
throw err;
}
}
查询验证与注意事项
可以通过explain()方法查看查询是否使用了目标索引,验证索引是否生效:
// 查看查询执行计划,确认是否使用了部分索引
async function checkIndexUsage() {
const explainResult = await User.find({
age: { $gt: 18 },
status: 'active'
}).explain();
console.log('查询执行计划:', explainResult);
}
需要注意的几点:
- 部分索引只在查询条件满足
partialFilterExpression定义的过滤条件时才会被使用,查询条件中必须显式包含过滤条件的字段,或者过滤条件的字段是索引的前缀字段。 - 复合索引的字段顺序很重要,查询条件最好按照索引定义的字段顺序来写,保证前缀匹配生效。
- 如果集合中有多个索引都满足查询条件,MongoDB查询优化器会自动选择最优的索引,不需要手动指定。
- 部分索引的过滤条件支持MongoDB的所有查询操作符,比如
$gt、$in、$exists等,可以根据实际需求灵活定义。
常见误区
很多开发者会误以为只要查询条件满足部分索引的过滤条件,不管是否匹配复合索引的字段都能使用该索引,这是错误的。复合部分索引需要同时满足复合索引的匹配规则和部分索引的过滤规则才会生效。另外,不要在查询条件中遗漏部分索引的过滤字段,否则索引无法被触发,可能导致查询性能下降。