不会,resharding 不会自动清理旧分片键下的历史数据,仅基于新分片键重新分布当前有效文档;旧数据仍物理留存原位置,可能形成“僵尸数据”,需人工定位并配合 _cleanupOrphaned 手动清理。

resharding 会自动清理旧分片键数据吗?不会
resharding 不会主动删除或迁移旧分片键下已写入的数据,它只负责基于新分片键重新分布**当前有效文档**。那些在变更前被错误路由到非目标分片的“僵尸数据”(比如旧分片键值导致文档落在本不该归属的 chunk 范围内),resharding 完全不感知、不处理——它们既不在新 chunk 映射里,也不触发任何校验或清理动作。
这类数据本质是“伪孤儿”:元数据认为它属于某分片,但按新分片键规则它根本不在合法范围内。_cleanupOrphaned 命令也扫不出来,因为它仍落在某个 chunk 的 min/max 内(只是这个 chunk 是按旧键划分的)。
- resharding 过程中,MongoDB 会为新分片键构建全新 chunks,并把符合新范围的文档批量迁移;旧 chunk 不会被删,旧数据也不会被重写或标记
- 如果你曾手动
moveChunk或绕过 mongos 直接往某分片写入过数据,resharding 更不会回溯修正 - 执行
sh.reshardCollection("db.col", { newKey: 1 })后,sh.status()显示的 chunk 分布全是新键逻辑,但磁盘上旧数据原封不动躺在老位置
怎么定位和确认僵尸数据存在
关键不是查“有没有”,而是确认“哪些分片上存了按新分片键不该存在的数据”。需要交叉比对三处信息:
- 用
db.getSiblingDB("config").chunks.find({ ns: "db.col" }).sort({ min: 1 })拿到当前所有 chunk 的min/max(按新分片键) - 登录每个分片 mongod(不是 mongos),
use db后运行db.col.find().limit(10).forEach(printjson)抽样看实际文档的分片键值 - 对抽样文档,手算它是否落在任一 chunk 的
min≤ 键值 ≤max范围内;如果全部不满足,就是僵尸数据 - 特别注意类型隐式转换问题:比如旧键存的是
NumberLong("123"),新键范围却是NumberInt(123),比较时会因 BSON 类型不一致被判为“越界”
清理僵尸数据只能靠人工 + _cleanupOrphaned 组合
_cleanupOrphaned 本身不解决分片键变更问题,但它可以作为最后一步——在你人工确认并删除完明确的僵尸文档后,用来兜底清理迁移残留的真正孤儿(比如 chunk 移走后源分片漏删的副本)。
- 先在疑似分片上执行
db.col.deleteMany({ "shardKeyField": { $not: { $gte: minVal, $lte: maxVal } } })(需按 chunk 范围分段构造条件,避免全表扫描) - 删除操作必须在分片 mongod 上执行,且要确保该分片当前是 replica set primary,否则写入失败
- 删完后,再运行
db.runCommand({ _cleanupOrphaned: "col" })—— 这步只起保险作用,不是主力手段 - 严禁在 balancer 运行中或 chunk 正在迁移时执行
_cleanupOrphaned,否则返回"cannot cleanup orphaned data while balancing"
最易被忽略的风险点:resharding 后的查询行为变化
resharding 完成后,mongos 会按新分片键路由查询,但旧数据仍物理存在于原分片上。这意味着:
- 带分片键的精确查询(如
{ shardKey: 123 })能命中新分布,但不会去旧位置捞数据 → 僵尸数据彻底“不可见” - 不带分片键的
find({})或范围查询(如{ shardKey: { $gt: 100 } })可能扫到僵尸数据,造成结果重复或逻辑错乱 - 聚合管道若含
$lookup或跨集合操作,而被关联集合未同步 reshared,更容易暴露数据错位 - 真正麻烦的是应用层缓存:如果之前缓存了旧分片键下的文档 ID 和位置,resharding 后这些缓存会持续指向无效物理地址











