唯一索引在分片集群中必须以片键为前缀才真正全局唯一,否则因广播路由导致去重失效;字段顺序必须严格片键在左,且需结合业务查询、排序需求设计,并辅以应用层校验与后台异步创建。

唯一索引必须带片键前缀,否则根本不是“全局唯一”
在分片集群里执行 db.users.createIndex({"email": 1}, {unique: true}) 不会报错,但插入重复 email 时照样成功——这不是 bug,是架构限制。mongos 路由时发现查询不含片键,就广播到所有分片;每个分片只检查自己那部分数据,漏掉其他分片的冲突值。真正能跨分片强制去重的唯一索引,key 字段必须以片键开头,比如片键是 {orgId: 1},那就得建 {"orgId": 1, "email": 1}。
复合唯一索引字段顺序错了,等于白建
即使加了片键前缀,字段顺序不对也会让索引失效或性能变差:
- 片键字段必须严格出现在最左侧,不能跳过或换位,
{"email": 1, "orgId": 1}无效 - 如果业务常查
email单独值,这个复合索引无法支撑等值查询(缺少email前缀),得额外建一个非唯一索引{"email": 1} - 排序需求影响顺序:若还要按
createdAt分页,且email值常重复,可考虑{"orgId": 1, "email": 1, "createdAt": -1},但注意范围查询字段(如$gt)应放最后
后台创建 + 应用层兜底,缺一不可
重建索引时直接阻塞写入会影响线上服务:
- 用
background: true参数避免锁表:db.users.createIndex({"orgId": 1, "email": 1}, {unique: true, background: true}) - 即使索引合规,块迁移、写关注
w:1下主节点崩溃等边界场景仍可能产生短暂重复,应用层必须做最终一致性校验(例如插入前先findOne检查) - 别依赖
getIndexes()输出里有"unique": true就认为安全——先跑sh.status()确认片键,再比对索引key是否严格前缀匹配
高频写入下,哈希片键比范围片键更抗热点
如果片键本身基数低(比如 orgId 只有几十个取值),即使加了前缀的唯一索引也救不了性能:
- 数据严重倾斜,某几个分片吞下大部分写请求,CPU 和磁盘 IO 持续拉满
- 改用哈希片键可缓解:
sh.shardCollection("mydb.users", {"orgId": "hashed"}),此时唯一索引需为{"orgId": "hashed", "email": 1} - 但注意:哈希片键不支持范围查询(
$lt/$gt),如果业务需要按orgId查区间,就得换策略或接受广播查询代价
实际部署中,最容易被忽略的是“片键不变,但业务查询模式变了”——比如最初只按 orgId 查,后来加了大量单 email 查询,这时光靠前缀索引撑不住,必须补非唯一索引+应用层缓存+必要时重构片键。











