sh.status()仅显示chunk数量,无法反映真实数据量分布,易掩盖因jumbo chunk或写入热点导致的严重倾斜。

为什么 sh.status() 看起来均衡,实际却严重倾斜
因为 sh.status() 默认只显示每个分片的 chunks 数量,不反映真实数据量。一个分片可能有 120 个 chunk,但其中 119 个是空的、1 个是 jumbo chunk(超 64MB 且无法分裂),结果 95% 数据全压在单一分片上。这种情况在范围分片(如按 createTime: 1)+ 时间集中写入时极常见——新数据全落在最新 chunk,旧 chunk 长期不动,balancer 永远不触发迁移。
手动预分片前必须确认的三件事
预分片不是“多切几刀”就完事,错一步后续几乎无法自动修复:
- 确认分片键区分度足够:
db.collection.distinct("shard_key").length返回值要接近文档总数,否则预分再多 chunk 也堆在同一个值上 - 禁用自动分裂:
sh.disableAutoSplit("db.coll"),避免预分后立刻被 auto-split 打乱节奏 - 确保 balancer 处于暂停状态:
sh.stopBalancer(),否则预分过程中它会抢着迁移,导致 chunk 归属混乱
用 splitAt() + moveChunk() 精准控制初始分布
预分片的核心是绕过 auto-split 的被动响应,主动把 key 范围切成等宽段,并指定每个 chunk 初始归属分片。例如对哈希分片键 {_id: "hashed"} 不适用,但对范围分片键 {ts: 1} 有效:
use admin
// 先查出时间范围
db.runCommand({splitAt: "db.coll", bounds: [{ts: ISODate("2024-01-01")}, {ts: ISODate("2024-07-01")}], shard: "shard0001"})
db.runCommand({splitAt: "db.coll", bounds: [{ts: ISODate("2024-07-01")}, {ts: ISODate("2025-01-01")}], shard: "shard0002"})
db.runCommand({splitAt: "db.coll", bounds: [{ts: ISODate("2025-01-01")}, {ts: ISODate("2025-07-01")}], shard: "shard0003"})
注意:splitAt 只能在主分片上执行;bounds 必须严格递增且覆盖全集;每个 shard 参数必须是真实存在的分片 ID(从 sh.status() 输出中复制,别手敲)。
预分后必须做的验证和收尾动作
预分完成不等于问题解决,漏掉验证大概率白干:
- 连到每个分片直查真实负载:
mongo --host shard0001:27018→use db→db.coll.stats({scale: 1024*1024}),比对size和count - 检查 config.chunks 是否已生成对应记录:
db.getSiblingDB("config").chunks.find({ns: "db.coll"}).sort({min: 1}) - 重新启用 balancer 前,先运行一次
sh.startBalancer()并立刻用sh.getBalancerState()确认返回true,再等 2 分钟看sh.isBalancerRunning()是否稳定为false(说明无待迁移任务)
真正难的不是切 chunk,而是让每个 chunk 的起始 key 能匹配业务写入节奏——比如按天预分,但业务凌晨批量导入,结果当天所有数据又挤进第一个 chunk。这种细节,sh.status() 里永远看不到。


















