Zone范围重叠不会导致写入失败,但会使balancer拒绝迁移chunk且静默跳过,sh.moveChunk()也返回成功却实际未执行,重叠检测仅发生在迁移时而非写入时。

Zone范围重叠不会直接导致写入失败,但会让balancer拒绝迁移chunk
很多人看到“范围不可重叠”的文档警告,第一反应是“写数据会报错”,其实不是。MongoDB本身不校验zone范围是否重叠,sh.updateZoneKeyRange() 命令照样能执行成功,插入新文档也照常路由、写入、返回200。真正出问题的是后续的自动平衡行为——balancer 在尝试迁移 chunk 时,一旦发现目标分片上已有另一个 zone 覆盖了同一段键范围,就会直接跳过该 chunk,且不报错、不重试、不记录 warning。结果就是:数据持续写入,但 chunk 长期卡在原分片,冷热分离策略彻底失效。
重叠范围让sh.moveChunk()静默失败
当你手动调用 sh.moveChunk() 想把存量数据挪到指定分片时,如果目标分片的 zone 范围和已有 zone 重叠,命令会返回 { "ok" : 1 },看起来成功了,但实际上 chunk 根本没动。这是因为 moveChunk 的底层逻辑依赖 balancer 的约束检查,而重叠触发的是“约束冲突”,不是“错误异常”。你得靠 sh.status() 对比迁移前后 chunk 分布,或查 config.chunks 集合确认位置。
- 重叠检测只发生在 moveChunk 或 balancer 运行时,不是写入时
- 没有日志提示“zone conflict”,只有 config.migrations 表里缺失对应记录
- 如果你用脚本批量迁移,必须在每次
sh.moveChunk()后加 sleep +sh.status()校验,不能只信返回值
哈希分片下重叠更隐蔽,但后果一样严重
对 {"uid": "hashed"} 这类分片键,你设的 range 是哈希值区间,比如 {uid: Long("123")} → {uid: Long("456")}。如果另一条 sh.updateZoneKeyRange() 把 {uid: Long("400")} → {uid: Long("500")} 也绑给了不同分片,就构成重叠。此时 uid=100 和 uid=200 可能被哈希到同一个 long 值附近,两个 zone 都声称“这个哈希值归我管”,balancer 就彻底懵了。
- 哈希值无法肉眼判断是否重叠,必须用
convertShardKeyToHashed()实际算一遍再比对 - 别用字符串或十进制数直接写范围,
Long("123")和NumberLong(123)在某些驱动里解析结果不同 - 范围边界必须严格用
MinKey/MaxKey占位,不能用null或空对象,否则整个 range 定义无效,等于没设
真正难的不是避免重叠,而是验证它是否存在
MongoDB 没提供类似 sh.checkZoneOverlap() 的命令。你得自己查 config.tags 和 config.zones,按 shard key 类型(范围 or 哈希)分别解析每条 range 的实际覆盖空间,再做区间交集判断。尤其是当 shard key 是复合字段(如 {ts: 1, region: 1})时,range 是二维空间,重叠判定要转成 lexicographic order 比较,稍有不慎就漏掉边界情况。这也是为什么线上集群一旦 zone 配置变复杂,最好用脚本固化校验逻辑,而不是靠人眼 review。











