必须用哈希索引——因单调递增字段用范围索引会导致写入热点(70% chunk集中单分片),而哈希索引可均匀分布写入;但范围查询将广播至所有分片,等值查询和复合前缀查询则仍高效。

在MongoDB分片集群中,面对单调递增的_id或时间戳类字段,必须决定用哈希索引还是范围索引作为分片键——选错会导致写入热点、查询广播激增、数据严重倾斜。
先看写入模式是否制造热点
检查你最频繁插入的字段值是否随时间持续增大,例如ObjectId、毫秒时间戳、自增ID。如果是,【范围索引会把所有新文档塞进同一个分片】,因为范围分片按键值大小切块,最新值永远落在MaxKey附近的数据块上。
执行 db.collection.stats() → 查看 chunks 字段分布:若超过70%的 chunk 集中在单一分片,就是典型热点信号。
此时哈希索引是刚需——它把 1、2、3、1000 这些连续值打散成完全不相关的哈希值,强制写入均匀落到各分片。
再评估核心查询类型
方法一:等值查询(如 find({ userId: "u123" }))→ 哈希索引和范围索引都支持,mongos 能精准路由到单一分片。
方法二:范围查询(如 find({ createdAt: { $gt: ISODate("2025-01-01") } }))→ 【哈希索引完全无法使用】,MongoDB 只能向所有分片广播查询,性能断崖式下降。
方法三:前缀匹配(如复合键 { region: 1, userId: "hashed" } 上查 { region: "CN" })→ 范围部分生效,哈希部分打散,这是复合哈希分片的典型优势场景。
验证字段基数与数据特征
第一步:统计候选分片键的去重值数量
db.collection.aggregate([ { $group: { _id: "$candidateField", count: { $sum: 1 } } }, { $count: "distinctCount" } ])
第二步:判断结果
若 distinctCount
第三步:避开浮点陷阱
如果候选字段含浮点数(如 price: 2.34),【绝对不可直接建哈希索引】,MongoDB 会先把 2.34 截断为整数 2 再哈希,导致 2.01、2.99 全部映射到同一哈希值,引发严重查询误命中和写入冲突。
动手测试哈希值分布(关键验证步骤)
进入 mongosh,对几个真实键值运行 convertShardKeyToHashed():
convertShardKeyToHashed( ObjectId("60a8b1c2d3e4f5a6b7c8d9e0") )
convertShardKeyToHashed( ISODate("2025-03-15T10:30:00Z") )
convertShardKeyToHashed( "user_789" )
观察返回的 Long 值是否跨度足够大(例如从 1234567890123456789 到 9876543210987654321);如果多个输入返回相同或相邻哈希值,说明该字段不适合哈希分片。
这一步必须做,不能跳过——理论推演不如实测哈希输出可靠。

















