先用db.collection.distinct("shard_key_field").length检查唯一值数量,若远小于文档总数即基数不足;再聚合统计前10项占比超30%即确认倾斜;哈希分片键无法提升低基数字段的实际分散度,复合键中若高阶字段基数低仍会导致数据聚堆;预分片前须停用auto-split与balancer,并确保分片键字段全员存在且类型一致。

怎么快速确认分片键值是否真的倾斜
别只看 sh.status() 输出的 chunk 数量,那玩意儿只告诉你“有多少块”,不告诉你“每块多重”。真正要查的是字段值的实际分布:先跑 db.collection.distinct("shard_key_field").length,如果结果远小于集合文档总数(比如千万级集合只返回几十个唯一值),基本就坐实了基数不足;再用聚合看头部集中度:db.collection.aggregate([{$group: {_id: "$shard_key_field", count: {$sum: 1}}}, {$sort: {count: -1}}, {$limit: 10}]),前10项加起来占总文档数超30%,就是典型倾斜。
Hashed 分片键为什么有时没用
哈希不是万能解药,它只打散值的物理位置,不增加逻辑基数。如果原始字段只有 "A"、"B"、"C" 三个取值,哪怕加了 {"field": "hashed"},哈希后也还是最多落到 3 个 chunk 上——数据照样堆在少数分片。更隐蔽的问题是复合键:{tenant_id: "hashed", created_at: 1} 看似合理,但若 tenant_id 实际只有 5 个有效值,那每个租户的所有时间数据仍被锁死在同一个分片里,毫无分散效果。
手动预分片前必须停掉两件事
预分片不是切完就完,错一步后续就乱套:
• 必须先执行 sh.disableAutoSplit("db.coll"),否则刚切好的 chunk 立刻被 auto-split 拆得七零八落;
• 必须运行 sh.stopBalancer(),不然 balancer 会在你切片过程中抢着迁移,导致 chunk 归属错位;
• 还得确认分片键本身区分度够高,db.collection.distinct("shard_key").length 接近文档总数,否则预分再多段也没意义——所有段都指向同一组值。
重建分片键时最容易漏掉的三个硬性条件
MongoDB 不允许直接改分片键,必须重建集合,但重建不是导出再导入那么简单:
• 执行 sh.shardCollection("db.coll", {"new_shard_key": 1}) 前,确保该字段在**所有文档中都存在且类型一致**,否则报错 cannot shard collection with missing or inconsistent shard key value;
• 导入必须用 mongorestore --drop,漏掉 --drop 会导致旧数据残留,新旧数据混杂引发逻辑错误;
• 新分片键要避开 null 和空字符串——MongoDB 把所有 null 当作同一个键值,极易造成单 chunk 膨胀成 jumbo chunk。
null 或类型混用。这不像语法错误会立刻报错,而是在写入一段时间后才暴露为不可分裂的 jumbo chunk,那时再修就得二次停机。


















