
为什么 db.collection.find({field: null}) 很慢?
MongoDB 的 B-tree 索引默认不存储 null 值(除非字段显式为 null 且索引设置了 partialFilterExpression 或使用了稀疏索引),更关键的是:标准索引会包含所有文档的键(即使值为 null),但查询 {field: null} 实际匹配两类数据——显式存了 null 的文档,以及该字段根本不存在的文档。MongoDB 必须扫描索引中所有缺失/空值条目,而默认索引没有对这类“空态”做优化区分,导致全索引扫描或回表。
用部分索引跳过 NULL,只索引非空值
部分索引(partialFilterExpression)不是用来加速查 null 的,而是反过来:明确告诉 MongoDB “只把满足条件的文档加入索引”,从而缩小索引体积、提升写入和非空查询性能。查 null 本身仍需全集合扫描,但多数场景真正需要的是“快速查非空”,这时部分索引才起效。
- 创建仅索引
status非空的复合索引:db.orders.createIndex( { status: 1, createdAt: -1 }, { partialFilterExpression: { status: { $exists: true, $ne: null } } } ) -
$exists: true排除字段缺失的文档;$ne: null排除显式存了null的文档 - 这个索引不会包含任何
status: null或status缺失的文档,因此find({status: "shipped"})可以高效命中,且索引体积更小 - 注意:如果业务中
null占比高,索引大小下降明显,但find({status: null})依然无法用上它
想真正加速 find({field: null})?加一个显式标记字段
原生 MongoDB 没有“空值专用索引”,最稳妥的实操方案是把“空逻辑”转为“可索引字段”。比如增加一个布尔字段 hasField,在应用层写入时同步维护:
- 插入/更新时:如果
field为null或缺失,设hasField: false;否则设hasField: true - 然后建普通索引:
db.collection.createIndex({ hasField: 1, field: 1 }) - 查非空:
find({hasField: true})→ 走索引 - 查空:
find({hasField: false})→ 同样走索引,且结果精准(不含误判) - 缺点是写放大 + 应用逻辑耦合,但这是目前唯一能稳定让
null查询变快的方式
稀疏索引 vs 部分索引:别混用
稀疏索引(sparse: true)只跳过字段缺失的文档,但依然包含 field: null 的条目;部分索引靠表达式控制,更精确。二者不能同时指定(MongoDB 会报错 "sparse and partialFilterExpression cannot both be specified")。
- 错误写法:
db.coll.createIndex({a: 1}, { sparse: true, partialFilterExpression: { a: { $ne: null } } }) - 正确选择其一:要排除缺失+
null,用部分索引;只要排除缺失(允许null入索引),用稀疏索引 - 部分索引支持任意表达式(如
{a: {$type: "string"}}),灵活性远高于稀疏索引
null 查询慢,取决于空值出现频率和查询权重。如果 find({x: null}) 是低频管理操作,通常没必要改数据模型;但如果每秒执行上百次,那标记字段就是必须的。


















