MongoDB分片集群中全局索引必须包含分片键或其前缀,否则无法路由查询、失去全局效力;唯一索引还强制要求完整包含分片键字段及顺序,哈希分片键上禁止建唯一索引,且创建失败易留半成品索引。

在分片集群中,db.collection.createIndex() 直接创建的索引默认就是全局索引(即对整个集合生效),但能否真正“全局生效”取决于你是否在分片键上或其前缀上建索引——否则 MongoDB 会拒绝创建,或索引仅在部分分片上可用,查询时无法下推,性能归零。
为什么不能随便在非分片键字段上建全局索引
分片集群里,MongoDB 要求所有全局索引必须满足:索引的 key 字段要么包含分片键(完整匹配或前缀),要么是哈希索引({ shardKey: "hashed" })且该字段本身就是分片键。否则:
- 执行
db.collection.createIndex({ status: 1 })会成功,但实际只在每个分片上独立建了本地索引;mongos 不会将其视为可路由的全局索引 - 带
{ status: "active" }的查询仍会广播到所有分片(scatter-gather),无法利用索引加速 -
explain("executionStats")显示nReturned很小但totalDocsExamined极高,说明索引没被下推
在分片键基础上添加辅助字段的正确写法
假设你的集合按 { region: 1, userId: 1 } 分片,现在想加速 { region: "us", status: "paid" } 查询,就必须让 region 出现在索引最左位置:
- ✅ 正确:
db.orders.createIndex({ region: 1, status: 1 })——region是分片键前缀,索引可全局路由 - ✅ 正确:
db.orders.createIndex({ region: 1, userId: 1, status: 1 })—— 完整分片键 + 辅助字段,支持更精细过滤 - ❌ 错误:
db.orders.createIndex({ status: 1, region: 1 })——status在前,无法用于路由,等同于本地索引 - ⚠️ 注意:
db.orders.createIndex({ region: 1, createdAt: -1 })虽合法,但如果查询只用{ createdAt: { $gt: ... } },依然无法路由,必须带上region
唯一索引在分片集群中的硬限制
跨分片的唯一约束只能靠分片键保证。如果你试图建一个不包含分片键的唯一索引(如 { email: 1 }),MongoDB 会报错:
Cannot create unique index with non-unique shard key
原因很直接:没有全局协调器验证跨分片唯一性。解决路径只有两条:
- 把
email加进分片键(例如改为{ email: "hashed" }或复合分片键{ email: 1, _id: 1 }),再建{ email: 1 } - 放弃全局唯一,改用应用层+单文档原子操作(
findAndModify+upsert)做冲突检测
后台建索引与 oplog 窗口的实际影响
即使语法合法,建索引过程仍可能失败,常见于:
- oplog 太小:索引构建耗时 > oplog 可回滚时间窗口 → secondary 节点同步延迟超限,自动降级为 stale
- 负载均衡未停:
sh.stopBalancer()没执行,迁移中建索引会导致部分 chunk 缺失索引,后续查询结果不一致 - 写入未暂停:唯一索引要求全量校验,期间写入新文档可能触发重复值,导致建索引中断并留下半成品索引
所以 createIndex() 前必须确认:sh.getBalancerState() 返回 false,且 rs.printSecondaryReplicationInfo() 显示所有 secondary 的 optime lag


















