MongoDB分片集群强制要求唯一索引必须完全包含分片键字段及顺序,否则createIndex直接报错;哈希分片键上禁止创建唯一索引;不满足条件会导致全局唯一性无法保障或索引创建失败残留。

唯一索引不包含分片键时,createIndex 直接报错
MongoDB 分片集群拒绝创建任何 unique: true 索引,只要它不完全包含当前集合的分片键字段及顺序。错误信息非常明确:"cannot create unique index over { email: 1 } while enforcing uniqueness over the entire cluster because the index does not contain the shard key"。这不是配置问题,是内核级校验——mongos 在收到建索引请求后,会立刻比对索引字段是否为分片键的超集,不匹配就终止,连尝试分发到 shard 的机会都没有。
常见误操作包括:
- 以为
{ email: 1 }加上unique: true就能全局去重,没意识到分片键是{ tenantId: 1 } - 复合分片键是
{ orgId: 1, userId: -1 },却建了{ userId: 1, email: 1 }唯一索引(顺序和方向都不对) - 分片键含哈希字段如
{ _id: "hashed" },却试图建{ _id: 1, email: 1 }唯一索引(必须写成_id: "hashed"才合法)
哈希分片键上根本不能设唯一索引
哪怕你把哈希字段显式写进索引定义,MongoDB 仍会拒绝:"cannot create unique index over { userId: \"hashed\" } because hashed indexes do not support unique constraints"。原因在于哈希值无序、不可比较,无法支撑唯一性检查所需的范围扫描、冲突定位和原子更新逻辑。不是“暂时不支持”,是设计上就不兼容。
这意味着:
- 如果你用
{ userId: "hashed" }作分片键,就别指望靠数据库层保证userId全局唯一 -
{ userId: "hashed", email: 1 }这种组合索引,哪怕加了unique: true,也会被拒绝 - 想兼顾均匀分布与唯一性?只能换升序分片键(如
{ userId: 1 }),或在应用层用 Redis Set + 事务兜底
为什么非得强制包含分片键?
根本原因是:分片后数据物理分散,MongoDB 没有跨分片两阶段提交能力。如果唯一约束不含分片键,两个不同分片可能同时插入 { email: "a@b.com" },彼此根本不知道对方的存在——mongos 不会、也不能在写入前协调所有 shard 做全局查重。
而带上分片键后,比如分片键是 { tenantId: 1 },那所有 tenantId: 123 的文档必然落在同一分片。该分片本地就能用 B-tree 完成唯一性校验,等价于单机行为。这是唯一可行的、无需分布式锁的实现路径。
所以这个限制不是“为了安全加的”,而是“不这么设计就做不到”。
想实现单字段全局唯一,但又不能改分片键怎么办?
没有绕过规则的办法。所谓“全局唯一邮箱”,在分片集群里天然无法靠单字段唯一索引落地。只有两种现实选择,且都带代价:
- 建复合唯一索引,如分片键是
{ tenantId: 1 },就建{ tenantId: 1, email: 1 }→ 保证“每个租户内邮箱唯一”,但不同租户可重名 - 应用层先
find({ email: "x" })再insertOne→ 多一次 round-trip,且存在窗口期冲突(需配合重试 + 幂等 key) - 用
updateOne({ email: "x" }, { $setOnInsert: doc }, { upsert: true })→ 要求email字段有非唯一索引,否则查询慢到不可用
最容易被忽略的一点:索引创建失败时,部分 shard 可能已建好索引,其余未完成,形成“半成品”状态。此时写入行为不一致——有些分片会拦住重复数据,有些不会,最终导致实际数据违反唯一性。上线前务必用 sh.status() 和 db.collection.getIndexes() 逐 shard 核验索引一致性。


















