热点问题源于分片键设计、写入模式与查询路径三者耦合失衡;单调递增字段(如order_id)导致写入集中、chunk频繁分裂迁移,引发读写热点;哈希分片牺牲范围查询能力,可能降低吞吐;复合分片键、客户端分桶、查询优化与运维监控是有效缓解手段。

热点文档问题不是靠“避免”就能解决的,它本质是分片键设计 + 写入模式 + 查询路径三者耦合失衡的结果。直接改分片键或加缓存都可能治标不治本。
为什么 order_id 或 timestamp 会引发热点
单调递增字段作为分片键时,新写入文档总落在同一 chunk 的末尾,该 chunk 持续变大、频繁分裂、最终被迁移——但迁移还没完成,新写入又来了。结果是:
- 某个 shard 的
netIn和opcounters.insert明显高于其他节点(mongostat可见) -
sh.status()中出现大量moving或pending状态的 chunk - 查询该分片键范围的请求(如
{order_id: {$gt: 1000000}})全部路由到同一个 shard,形成读热点
哈希分片不能无脑套用 {"order_id": "hashed"}
哈希能打散写入,但代价是彻底放弃范围查询能力。如果业务中仍有 {$gte: X, $lt: Y} 类查询,MongoDB 就必须发起分散-聚集(scatter-gather),把请求发给所有 shard,再在 mongos 层合并结果——吞吐量反而下降。
- 确认你是否真有高频范围查询:查慢日志里
command.find.planSummary是否含IXSCAN+ 大范围min/max - 若必须保留范围能力,考虑复合分片键,例如
{"region": 1, "order_id": 1},用高基数字段前置分流 - 若已用哈希且发现
shard0000响应延迟突增,大概率是该 shard 承载了过多getMore请求(见下条)
getMore 请求打散后导致单个 mongos 节点过载
批量查询返回大量数据时,find 和后续多个 getMore 必须由同一个 mongos 处理,否则报 CursorNotFound。但负载均衡(如源 IP Hash)无法保证这点——客户端第一次连 A mongos,后续 getMore 可能被轮到 B mongos。
- 检查
db.adminCommand("connPoolStats")输出中各 mongos 的totalCreated和active连接数是否严重不均 - 应用层应在发起
find后,显式复用同一连接句柄做getMore,而不是新建连接 - 云环境建议关闭源 IP Hash,改用会话保持(sticky session)策略,确保 cursor 生命周期内绑定固定 mongos
真正有效的热点缓解路径是分层控制
没有银弹配置能一键消除热点。生产中有效做法是组合干预:
- 写入层:对高频写入字段(如
user_id)做客户端哈希分桶,再映射到分片键,避免原始值直传 - 查询层:对确定为热点的文档(如运营后台查“最新10条订单”),强制走
hint({"_id": 1})+ 覆盖索引,绕开分片键路由 - 运维层:定期跑
db.collection.stats().sharded,对比各 shard 的count和size标准差,>30% 就要介入
最容易被忽略的是:热点往往不在文档本身,而在查询路径——比如一个没加 .limit(100) 的聚合管道,会把全量中间结果拉到 mongos 内存里排序,瞬间压垮单个节点。


















