哈希复合分片能解决单调递增分片键导致的写入热点问题;需选用高基数字段(如ObjectId、UUID或长整型字符串),避免浮点数和低基数字段,创建{"userId":"hashed","createdAt":1}索引并执行sh.shardCollection,验证chunks均匀分布。

当你的MongoDB集合使用单调递增字段(如时间戳、自增ID)作为分片键时,写入会持续堆积在单个分片的高值数据段,导致写瓶颈和负载严重不均——哈希复合索引能从根本上打散这种热点,让写请求均匀落到所有分片。
确认分片键字段具备高基数且支持哈希
检查候选字段是否为 ObjectId、UUID 或长整型字符串,避免使用浮点数或低基数字段(如 status: "active"/"inactive")。【哈希索引对浮点数会截断为64位整数,2.3、2.2、2.9 将产生完全相同的哈希值,引发写冲突】
运行 db.collection.stats() 查看该字段 distinct 值数量,必须远大于分片数(例如 100 万 distinct 值对应 8 个分片)。
创建带哈希前缀的复合索引
方法一:使用 mongosh 执行 createIndex 命令
db.collection.createIndex({ "userId": "hashed", "createdAt": 1 })
方法二:若已有范围索引但未分片,先删除旧索引再重建——注意:删除索引会导致查询性能瞬时下降,建议在低峰期操作。
这一步必须完成,否则 sh.shardCollection() 会报错“分片键无索引支持”。
执行哈希复合分片
第一步:确保数据库已启用分片(sh.enableSharding("mydb"))
第二步:调用 sh.shardCollection() 指定哈希复合键
sh.shardCollection("mydb.collection", { "userId": "hashed", "createdAt": 1 })
第三步:验证分片状态
sh.status() → 查看 collection 行的 shard key 是否显示为 { "userId" : "hashed" , "createdAt" : 1 },且 chunks 已开始在各分片间分布
注意:unique: true 不可用于哈希分片键,传入将直接报错。
验证写入是否真正均衡
向集合插入 1000 条测试文档,每条 userId 随机生成(如 ObjectId() 或 crypto.randomUUID())
执行以下命令查看各分片 chunk 数量分布:
sh.status().shards.forEach(s => print(`${s._id}: ${s.databases.find(d => d.name === 'mydb')?.collections.find(c => c.name === 'collection')?.chunks || 0} chunks`))
若最大值与最小值相差超过 30%,说明哈希前缀字段实际基数不足或存在大量 null/missing 值——需回到第一步重新评估字段选择。

















