$ type 查询默认不走索引是因为b-tree索引不存储bson类型信息,需用partialfilterexpression指定$type条件创建部分索引才能命中;支持复合类型+范围联合优化,但须避免使用无效别名如"number"。

$type 查询为什么默认不走索引
因为 $type 本身不直接对应传统 B-tree 索引的排序逻辑——MongoDB 的普通单字段索引(如 { age: 1 })会按值排序,但不会记录每个值的 BSON 类型。所以即使你对 age 建了索引,db.coll.find({ age: { $type: "int" } }) 仍大概率触发全集合扫描(COLLSCAN),尤其在混存多种类型时。
用部分索引(partial index)强制只索引目标类型
部分索引允许你指定 filter 条件,只对满足条件的文档建索引。配合 $type,可精准锁定某类数据建索引,大幅提升查询效率。
例如,只给 age 字段为整数的文档建索引:
db.users.createIndex(
{ age: 1 },
{ partialFilterExpression: { age: { $type: "int" } } }
)
之后这个查询就能命中索引:
db.users.find({ age: { $type: "int" } })
常见错误和注意点:
- 部分索引的
filter必须是「确定性表达式」,不能含$expr、聚合变量等;$type在这里合法 - 如果字段可能为
null或缺失,$type: "null"不会被上述索引覆盖——需要单独建一个{ partialFilterExpression: { age: { $type: "null" } } }索引 - 多个类型要索引?不能写
$type: ["int", "double"]在partialFilterExpression里(语法不支持),得建两个独立的部分索引 - 索引名建议带类型标识,比如
age_int_partial,避免后期运维混淆
复合部分索引:类型 + 值范围联合优化
真实业务中往往不是只查“是不是 int”,而是“是 int 且大于 18”。这时把类型判断和值过滤一起塞进部分索引,效果更明显:
db.users.createIndex(
{ age: 1, createdAt: -1 },
{
partialFilterExpression: {
age: { $type: "int" },
age: { $gt: 0 }
}
}
)
这个索引能同时加速:
db.users.find({ age: { $type: "int", $gt: 18 } })db.users.find({ age: { $type: "int", $gte: 25 } }).sort({ createdAt: -1 })
但要注意:partialFilterExpression 中重复字段(如上面两个 age 条件)是合法的,MongoDB 会合并为 AND 逻辑;而索引字段顺序仍影响排序和范围查询能力。
验证是否真走索引 & 容易忽略的坑
别光看 .explain() 里有没有 IXSCAN,重点看 executionStats.nReturned 和 executionStats.totalDocsExamined 是否接近——如果后者远大于前者,说明索引没真正过滤掉非目标类型文档。
最容易被忽略的三点:
-
$type: "number"是无效别名(MongoDB 不识别),必须用"int"、"double"、"long"等具体类型名,或数字代码16、1、18 - 部分索引对
$exists: false文档完全不索引,所以{ age: { $type: "int", $exists: true } }才是安全写法,否则可能漏掉本该匹配的文档 - 如果集合有大量
age: null或age: "25"文档,它们不会进入$type: "int"部分索引,但查询时仍需扫描它们来确认类型——这部分开销不会消失,只是从全量降为“非目标类型子集”











