分片集群中唯一索引仅在单个分片生效,非全局约束;要真正生效,去重字段必须参与分片键,否则会出现重复数据。

分片集群中唯一索引为什么不起作用
因为唯一索引在分片集群里只在单个分片上生效,不是全局约束。比如你对 email 字段建了 {email: 1} 唯一索引,但分片键是 {_id: "hashed"},那两条相同 email 的文档可能被路由到不同分片,各自插入成功——查出来就是重复数据。
真正起效的前提只有一个:去重字段必须参与分片键。要么单独做分片键(sh.shardCollection("db.users", {email: 1})),要么作为复合分片键的一部分(如 {email: 1, createdAt: 1})。否则,createIndex({email: 1}, {unique: true}) 就是假安全。
- 别指望
upsert自动兜底:如果查询条件没带分片键,mongos 可能只查一个分片,漏掉其他分片里的同值文档 - 应用层先
find再insert有竞态风险,尤其高并发写入场景 - 哈希分片键(如
{hash_email: "hashed"})可行,但得在写入前用相同哈希算法预计算并存入字段,确保路由一致
聚合查询加 allowDiskUse: true 不是可选项
在分片集群上跑 $group 或 $lookup,所有匹配文档会被拉到 mongos 节点做归并。内存不够时直接报错:Exceeded memory limit for $group, but didn't allow external sort。这不是配置问题,是架构限制。
必须显式加上 { allowDiskUse: true },否则聚合失败。但要注意副作用:磁盘排序比内存慢,且会占用临时空间。
-
$match阶段越早过滤越好,比如加时间范围、状态字段,大幅减少进入$group的文档量 - 避免用高基数字段(如
timestamp、_id)做$group的_id,容易撑爆内存或磁盘 - 分片键字段参与
$match条件时,mongos 才能下推到对应分片执行,否则全量扫描+网络传输
复合索引字段顺序必须匹配查询模式
分片集群不改变 MongoDB 索引的最左前缀原则,但放大了设计失误的代价。比如你建了 {status: 1, createdAt: 1, userId: 1},但实际查询只用 {userId: 1},这个索引完全无效——mongos 不会跨分片做索引跳过,只能全表扫。
优先按高频查询的等值字段 → 范围字段 → 排序字段顺序排列。例如常用查询是 {tenantId: "abc", status: "active", createdAt: {$gte: ...}},索引就该是 {tenantId: 1, status: 1, createdAt: 1}。
- 分片键字段必须放在复合索引最前面,否则无法利用索引下推(mongos 无法确定该查哪些分片)
- 排序字段若与查询条件无关,尽量不加进索引——增加写开销,还可能让索引过大导致内存压力
- 用
explain("executionStats")看executionStages里有没有IXSCAN,以及nReturned是否接近totalDocsExamined
分片键本身要不要建索引
分片键字段自动有索引,MongoDB 强制要求。但这个索引是单字段升序,不能覆盖你的业务查询需求。比如分片键是 {region: 1},而你要查 {region: "cn", status: "paid"} 并按 amount 倒序,就得额外建 {region: 1, status: 1, amount: -1}。
注意:分片键索引不可删除,也不能改类型(比如从升序改成哈希),否则整个集合分片结构失效。
- 哈希分片键(
{region: "hashed"})虽然分布均匀,但不支持范围查询,也导致所有范围类索引失效 - 如果分片键是复合的(如
{orgId: 1, timestamp: 1}),它天然支持以orgId为前缀的查询,但timestamp单独查询仍需额外索引 - 分片键字段值分布要足够离散,避免“热分片”——比如用
createdAt当分片键,新数据全写入最新分片,其他分片闲置

















