shard key必须有对应索引,因为mongodb需依赖该索引高效路由读写操作;缺失时mongos会广播到所有分片,导致全分片扫描和性能劣化。

为什么 shard key 必须有对应索引
MongoDB 要求每个分片集合的 shard key 字段必须存在**支持该键前缀的、非稀疏的、非唯一(除非是 _id)的升序/降序单字段或复合索引**。没有这个索引,mongos 无法高效路由写入和查询——它会退化为向所有分片广播操作,也就是你看到的“扫描全表”现象(实际是全分片扫描)。错误日志里常出现类似 "cannot shard collection without an index on the shard key" 或查询计划中 "stage": "SHARD_MERGE" 后跟大量 "stage": "COLLSCAN"。
检查缺失索引的三步定位法
别猜,直接查。连接到任意 mongos 实例,运行:
sh.status()
看目标集合是否显示 shard key;再连到任一分片的主节点(不是 mongos),执行:
db.getCollection('your_collection').getIndexes()
确认输出中是否存在以 shard key 字段开头、类型为 1 或 -1 的索引。常见遗漏情况包括:
- 只建了
{fieldA: 1, fieldB: 1},但shard key是{fieldA: 1}—— 这个可以复用 -
shard key是{a: 1, b: -1},但只建了{a: 1, b: 1}—— 方向不匹配,不可用 - 索引加了
sparse: true—— 分片键索引严禁稀疏 - 索引建在副本集的 secondary 上 —— 必须在 primary 上建,且同步完成
创建正确索引的实操要点
在分片集合所在分片的 primary 节点上执行,不是 mongos:
db.your_collection.createIndex({ "user_id": 1, "created_at": -1 }, { background: true })
关键注意事项:
-
background: true必须加,否则会阻塞整个分片的读写(尤其大集合) - 字段顺序和方向必须与
shard key完全一致,包括大小写和嵌套路径(如"meta.status") - 不要加
unique: true(除非shard key就是_id),否则分片迁移会失败 - 如果集合已有海量数据,索引创建可能耗时数小时,期间
sh.status()中该集合状态可能显示为"jumbo chunks"或迁移卡住,属正常现象
验证索引是否生效
索引建完不等于立刻可用。先确认状态:
db.your_collection.getIndexes()
再强制触发一次简单查询并看执行计划:
db.your_collection.find({ "user_id": "abc123" }).explain("executionStats")
重点检查两个字段:
-
"shards"数组里是否只列出 1 个分片(说明路由精准) -
"executionStages.stage"是否为"IXSCAN"而非"COLLSCAN"
如果仍广播到多个分片,大概率是索引方向/顺序错,或没在正确的分片 primary 上建——MongoDB 不会自动跨分片同步索引,每个分片得单独建。
最易被忽略的是:分片键一旦选定就不能改,但索引可以补。很多人卡在“以为 mongos 会代劳”,其实索引必须手动、逐一分片、严格对齐地建好,缺一不可。











