Zone Sharding 不适用于租户隔离,因其本质是物理位置控制而非逻辑隔离;正确方案是以 tenantId 为分片键并配合应用层强制过滤。

不能直接用 Zone Sharding 按租户 ID 实现数据隔离 —— 它不是为租户隔离设计的,强行套用会路由失效、数据错落、运维失控。
Zone Sharding 的本质是物理位置控制,不是逻辑租户隔离
Zone Sharding 的目标是把某段键值范围的数据“固定落到指定分片”,常用于地域就近(如 beijing_zone → shard01),或硬件分级(如 ssd_zone → 高配分片)。它不感知“租户”语义,也不提供多租户所需的访问控制、权限边界或元数据隔离。
常见错误现象:sh.addShardToZone("shard01", "tenant-a") 执行成功,但插入 {tenantId: "tenant-a"} 时数据仍散落在多个分片 —— 根本原因是没建分片键、没绑定键值范围、或分片键不是 tenantId。
- Zone 名称(如
"tenant-a")只是标签,无业务含义;必须配合sh.updateZoneKeyRange()显式绑定键值区间 - 若分片键是
{tenantId: 1, createdAt: 1},那范围必须写成{tenantId: "tenant-a", createdAt: MinKey}到{tenantId: "tenant-a", createdAt: MaxKey},不能只写"tenant-a" - 已有数据不会自动迁移,得手动
moveChunk,且 chunk 边界必须严格对齐分片键结构
真正适合租户隔离的方案:分片键 + 租户前缀 + 应用层过滤
生产环境最稳的租户隔离路径是:用 tenantId 作分片键(范围或哈希),让同一租户所有文档天然落在同一批 chunk 上,再靠应用层确保每次查询都带 tenantId 条件。
示例(范围分片):
sh.shardCollection("saas.orders", {tenantId: 1})
这样所有 tenantId: "acme" 的订单会聚在连续 chunk 中,mongos 路由精准,跨分片查询极少。
- 哈希分片(
{tenantId: "hashed"})适合租户量极大、单租户数据量不均衡的场景,但丧失范围查询能力 - 必须为
tenantId建唯一索引(尤其搭配unique: true时),否则sh.shardCollection()会失败 - 空集合才能用
numInitialChunks预分片;非空集合需先建索引再分片
为什么不用 Zone Sharding 做租户隔离?三个硬伤
第一,配置链太长:要依次执行 sh.enableSharding() → sh.shardCollection() → sh.addShardToZone() → sh.updateZoneKeyRange(),漏一步就全失效。
第二,租户动态增删成本高:每新增一个租户,就得加 zone、绑范围、调分片关联 —— 这不是运维动作,是发布级变更。
第三,无法防御越权访问:Zone 不拦查询,db.orders.find({userId: "u123"}) 仍会扫所有分片;租户隔离必须靠应用层强制 tenantId 过滤 + 查询拦截器(如 Spring Data MongoDB 的 @PreAuthorize 或自定义 QueryInterceptor)。
复杂点在于租户数据倾斜和冷热分离
如果某些租户数据量暴涨(比如头部客户日增百万订单),仅靠 tenantId 分片会导致 chunk 不断分裂、迁移压力大。这时可考虑复合键:{tenantId: 1, day: 1},把时间维度引入分片键,既保租户隔离,又分散单 chunk 压力。
但注意:day 字段必须不可变、非空、有明确业务含义;若用 createdAt 时间戳,得转成 YYYY-MM-DD 字符串或整数,避免浮点精度导致范围错位。











