唯一索引在分片集合中无法保证全局唯一性,因其仅在单分片内校验;必须以完整片键为最左前缀(如片键{orgId:1},则索引需为{"orgId":1,"email":1})才能生效,且应用层仍需兜底处理重复。

唯一索引不报错但插入重复数据,是因为它只在单分片内校验
执行 db.users.createIndex({"email": 1}, {unique: true}) 在分片集合上可能成功返回,但后续插入相同 email 值的文档不会报错。这不是 bug,而是架构限制:MongoDB 没有跨分片的全局唯一性协调器。mongos 路由时发现查询条件不含片键,会把写请求发往任意分片,该分片只检查自己本地数据,完全不知道其他分片是否存在同值。
唯一索引生效的前提是索引键必须以完整片键为最左前缀
假设片键是 {orgId: 1},那么只有 {"orgId": 1, "email": 1} 这类索引能保证全局唯一;{"email": 1}、{"createdAt": -1, "email": 1} 或 {"email": 1, "orgId": 1}(顺序错)都不行。
- 用
sh.status()确认当前片键定义 - 用
db.users.getIndexes()检查索引key字段是否严格以片键字段开头 - 复合片键如
{tenantId: 1, userId: 1},唯一索引必须是{tenantId: 1, userId: 1, email: 1},不能跳过任一字段
重建索引不能只 drop + create,还要兼顾查询性能和后台创建
删掉无效索引后,直接建新索引可能阻塞写入,且遗漏辅助查询需求:
- 先执行
db.users.dropIndex({"email": 1})(如果存在) - 再建合规索引:
db.users.createIndex({"orgId": 1, "email": 1}, {unique: true, background: true}) - 若业务常按
email单字段查询,需额外建非唯一索引:db.users.createIndex({"email": 1}),否则该查询无法利用复合索引前缀
即使索引合规,应用层仍要兜底处理重复
分片集群下唯一约束存在固有窗口期风险:
- 块迁移过程中,短暂时间内两分片可能同时持有相同唯一键值
- 写关注
w: 1时主节点崩溃,未同步到从节点,导致“看似成功实则丢失” - 路由节点 mongos 与 config server 元数据不同步的极短时间差
这些都不是配置错误,而是分布式系统本质决定的——索引只能提供强约束倾向,不能替代应用层的幂等判断或重试逻辑。











