哈希分片键必须满足索引前提、字段约束和查询模式匹配:需预先创建"hashed"索引(非空集合不自动建),避免浮点数截断冲突,且仅支持等值查询,范围查询会广播;复合键可兼顾区域与均匀性;修改成本高,须提前验证基数、精度与查询模式。

哈希分片键不是“选出来就能用”,必须满足索引前提、字段约束和查询模式匹配,否则写入会失败或数据倾斜严重。
哈希分片前必须确保分片键字段有对应索引
mongos 不会自动为你创建哈希索引——它只在 sh.shardCollection() 时对空集合“顺手”建索引;如果集合已有数据,sh.shardCollection() 会直接报错:cannot shard collection unless it has a supporting index。
- 先手动建哈希索引:
db.vast.createIndex({ _id: "hashed" })(注意:必须是"hashed",不是1或-1) - 确认索引存在:
db.vast.getIndexes()中能看到{"key": {"_id": "hashed"}, "name": "_id_hashed_"} - 若字段非
_id(比如用user_id),同样要先建db.users.createIndex({ user_id: "hashed" }),再分片
哈希分片键字段值不能是任意浮点数
MongoDB 对浮点数做哈希前会截断为 64 位整数,导致 2.3、2.9、2.999 全部哈希成同一个值,引发写冲突或覆盖。
- 避免用
float字段做哈希分片键,尤其当值集中在小数点后几位时 - 若业务确需用数值型 ID,优先转成
NumberLong或字符串再存;或改用ObjectId、UUID等天然高基数类型 - 验证哈希值分布:
convertShardKeyToHashed(2.3)和convertShardKeyToHashed(2.9)在 mongosh 中执行,看是否相等
哈希分片后等值查询能路由到单一分片,范围查询却必须广播
这是哈希分片最常被误用的点:以为“哈希了就啥都能快”。实际只有 { field: value } 这种精确匹配才能定向,{ field: { $gt: 100 } } 或 { field: { $in: [1,2,3] } } 都会触发广播查询(发到所有 shard)。
- 如果你的高频查询是时间范围、ID 区间、状态枚举,哈希分片反而比范围分片慢得多
- 复合哈希键(如
{ region: 1, user_id: "hashed" })可缓解:前缀字段region支持区域分片,后缀哈希保证该区域内数据均匀 - 不要试图用哈希分片键做排序:
sort({ field: 1 })无法利用哈希索引顺序,性能差且结果不保证全局有序
哈希分片键一旦选定,修改成本极高
从 MongoDB 5.0 起支持 reshardCollection,但哈希键重分片仍需全量扫描+重建索引,期间写操作受阻,且旧 chunk 清理不及时可能引发元数据膨胀。
- 上线前务必用真实数据量级压测:
sh.status()查看各 shard 的chunks数量和文档数是否均衡(偏差 >20% 就算异常) - 避免用单调递增字段(如自增 int)直接哈希——虽然比范围分片好,但哈希空间利用率仍偏低;优先选
ObjectId或带随机前缀的字符串 - 如果未来可能要按时间范围查,又想写入均匀,考虑组合键:
{ ts: 1, _id: "hashed" },用ts做范围分区基础,_id哈希打散热点
真正决定哈希分片成败的,不是命令敲得对不对,而是你有没有提前验证字段基数、浮点精度、查询模式这三件事。漏掉任何一项,上线后都得停机重分片。

















