MongoDB分片集群中复合索引必须以分片键或其最左前缀开头,否则无法路由查询;哈希字段仅支持等值查询,浮点型哈希易致数据倾斜;高效索引本质是提升路由精度而非“局部”。

分片键本身不能直接建局部复合索引
MongoDB 不允许在分片键(shard key)上创建“局部”(non-sharded / non-globally-scoped)复合索引。所有索引在分片集群中默认都是全局的,且必须兼容分片键结构。如果你看到“局部索引”这个说法,大概率是混淆了 partialFilterExpression(部分索引)或 sparse: true(稀疏索引)这类限制索引覆盖范围的机制,而非物理上只存在于某一分片。
真正能用的“高效复合索引”必须以分片键为前缀
这是硬性约束:任何想被查询优化器用于分片路由的复合索引,其字段顺序必须以分片键(或其最左前缀)开头。否则,mongos 无法将查询下推到具体分片,只能广播查询,性能断崖式下跌。
- 假设分片键是
{region: 1, user_id: "hashed"},那么有效复合索引只能是:{region: 1, user_id: "hashed", status: 1}、{region: 1, created_at: -1},但不能是{status: 1, region: 1}或{user_id: "hashed", status: 1} - 哈希字段(如
user_id: "hashed")在复合索引中仅支持等值查询,不能用于范围扫描;它后面的字段才可承担范围/排序任务 - 如果分片键含哈希字段,且你常按非哈希前缀字段做范围查询(例如查某 region 内最近 7 天的记录),就把该字段放在哈希字段之前,并确保它在分片键里是前缀
避免对浮点型哈希字段建复合索引
哈希索引会对浮点数做截断处理——convertShardKeyToHashed() 会把 2.3、2.9 全转成同一个整数哈希值。一旦你在分片键里用了浮点字段并声明为 "hashed",再拿它参与复合索引,就等于主动引入高冲突风险。
- 错误示例:
db.orders.createIndex({amount: "hashed", status: 1}),其中amount是 float64 类型 - 后果:多个不同金额映射到同一哈希桶,导致数据倾斜 + 查询结果遗漏(因哈希值不唯一)
- 正确做法:分片键中的浮点字段应避免
"hashed";如必须哈希,请先 cast 成整数或字符串,或换用range分片
真正要优化的不是“局部”,而是查询路由粒度
所谓“高效”,本质是让 mongos 尽可能把查询精准路由到最少数量的分片。这取决于分片键的设计和查询条件是否能匹配其前缀,而不是索引本身是否“局部”。一个没用好前缀的复合索引,哪怕字段再多,也救不了广播查询。
- 检查方式:用
.explain("executionStats")看executionStages.shards数量,以及每个分片的totalDocsExamined - 高频误区:以为加个
{status: 1}单字段索引就能加速{status: "paid"}查询——在分片集群中,它大概率被忽略,因为不带分片键前缀 - 关键动作:优先确认你的查询是否总带
region或{region, user_id}这类分片键子集;不带,就别指望复合索引能高效











