range查询慢大概率是索引字段顺序错误;必须严格遵循esr规则(等值→排序→范围)建复合索引,如{userid: 1, createdat: -1, status: 1},且索引方向须与sort一致,$in也受esr约束。

Range查询慢,大概率是索引字段顺序错了
MongoDB对$gt、$lt、$gte、$lte这类范围操作的加速能力,完全依赖复合索引中“等值字段→排序字段→范围字段”的严格顺序。一旦把范围字段(比如createdAt)放在前面,后面所有字段就失效——{createdAt: -1, userId: 1}这个索引,查{userId: "u123"}时根本用不上。
必须按ESR规则建复合索引:Equality → Sort → Range
ESR不是建议,是B-tree索引生效的硬性前提。例如查“某用户最近7天订单”,正确索引是:
db.orders.createIndex({ userId: 1, createdAt: -1, status: 1 })
对应查询:
ZenHeart zenlink 与 zenlink-mcp 为 OpenClaw,涵盖安装、MCP 注册、钩子接线、入站轮询及升级维护。
db.orders.find({
userId: "u123",
createdAt: { $gte: ISODate("2026-05-04") },
status: "completed"
}).sort({ createdAt: -1 })
-
userId是等值条件,精准截断数据集 -
createdAt是范围条件,但因前置有等值字段,B-tree只需定位到该用户的起始叶节点,再连续扫描 -
status虽是等值,但放Range之后无法用于过滤;若需按status筛选,应把它提到Range前,或拆成独立等值字段
升序 vs 降序影响排序阶段是否走索引
索引方向必须和.sort()一致,否则排序不走索引。比如:
- 索引
{userId: 1, createdAt: -1}支持.sort({createdAt: -1}) - 但不支持
.sort({createdAt: 1})——会触发in-memory sort,explain里能看到stage: SORT - 若业务既需要倒序查最新,又需要正序查最早,且频率都高,可考虑建两个方向索引,权衡磁盘与查询速度
容易被忽略的坑:$in不算纯Range,但行为接近
$in在语义上是多个等值,但MongoDB内部会为每个值做一次索引查找并合并结果。它比$or友好,但仍受ESR约束:
- 查询
{userId: "u123", amount: {$in: [99, 199, 299]}}能用{userId: 1, amount: 1}索引 - 但
{amount: {$in: [99, 199, 299]}, userId: "u123"}不能——因为amount在前,userId被跳过 - 如果
$in数组很大(如上百个值),实际性能可能退化,此时应评估是否转为范围或预计算分桶字段
真正卡住Range查询的,往往不是没建索引,而是索引建反了、方向配错了、或者误以为$in能绕过ESR。检查explain("executionStats")里的totalKeysExamined和totalDocsExamined比值,如果远大于1,说明索引在“扫键不命中”,立刻回头核对字段顺序。










