当字段null值占比高(如95%)时,应使用partialFilterExpression: {field: {$exists: true, $ne: null}}创建部分索引,避免索引臃肿;需统计null占比、验证索引命中与大小,并确保复合索引中过滤条件与查询模式匹配。

当集合中某个字段存在大量null值(比如95%的文档该字段为null),直接为该字段创建普通索引会导致索引体积臃肿、写入变慢、内存占用高;使用部分索引可精准跳过所有null值文档,让索引只覆盖有效数据,显著提升查询效率与资源利用率。
确认字段null值占比并评估必要性
先运行聚合统计,避免盲目建索引:db.collection.aggregate([{$group: {_id: "$field", count: {$sum: 1}}}, {$sort: {count: -1}}]) 或更直接地查null数量:db.collection.countDocuments({field: null}) 和 db.collection.countDocuments({field: {$exists: true, $ne: null}})。若非空值不足10%,部分索引就非常值得做。
注意:MongoDB将undefined、null、缺失字段三者在{field: null}查询中视为等价,但部分索引的过滤表达式必须显式写出判定逻辑,不能依赖隐式行为。
创建仅包含非null值的单字段部分索引
执行命令:db.collection.createIndex({field: 1}, {partialFilterExpression: {field: {$type: "string"}}}) —— 这里用$type: "string"仅适用于确定该字段非null时必为字符串类型。
更通用且安全的写法是:db.collection.createIndex({field: 1}, {partialFilterExpression: {field: {$exists: true, $ne: null}}})。这个表达式明确排除了字段不存在、字段值为null、字段值为undefined的全部文档,【必须同时写$exists和$ne条件,缺一不可】;只写{$ne: null}会意外包含缺失字段的文档(因为缺失字段不满足$ne: null,但也不满足$eq: null,属于“未定义比较”,MongoDB将其排除在索引之外——但行为易混淆,不推荐)。
为多条件查询构建复合部分索引
方法一:当查询固定带status = "active"且field非null时:db.collection.createIndex({status: 1, field: 1}, {partialFilterExpression: {status: "active", field: {$exists: true, $ne: null}}})。该索引能支撑{status: "active", field: "abc"}这类查询,也能用于{status: "active"}范围扫描(因status在前缀位置)。
方法二:若field常参与排序,把field放前面:db.collection.createIndex({field: 1, status: 1}, {partialFilterExpression: {field: {$exists: true, $ne: null}, status: "active"}})。此时.find({field: {$gt: "a"}, status: "active"}).sort({field: 1})可完全走索引,包括过滤、排序、投影。
注意:partialFilterExpression中的字段无需出现在索引键模式里,但索引键中未出现在过滤表达式里的字段,其选择性会下降——例如只对field建部分索引,却用{field: {$gt: "x"}, other: 123}查询,other字段无索引支持,仍要全表扫描匹配部分索引命中的子集。
验证部分索引是否生效
第一步:执行带.explain("executionStats")的查询,如db.collection.find({field: "hello"}).explain("executionStats");检查输出中executionStages.stage是否为IXSCAN,且nReturned与totalDocsExamined相等(说明没回表、没额外过滤)。
第二步:比对索引大小,运行db.collection.stats().indexDetails,找到你建的部分索引条目,观察size和count字段——它的count应接近你之前统计的非null文档数,而非总文档数。
第三步:故意查一个null值,如db.collection.find({field: null}).explain(),确认executionStages.stage变成COLLSCAN,证明MongoDB确实没用该部分索引——这是预期行为,不是bug。

















