分片键选错会导致写入热点,因低基数或单调递增字段(如createdat)使新数据持续写入同一分片,引发cpu、i/o及锁排队飙升,其他分片闲置。

为什么分片键选错会导致写入热点
分片键决定数据在各个分片上的分布方式。如果选了一个低基数、单调递增的字段(比如 _id 默认 ObjectId 或 createdAt),新写入的数据会持续追加到同一个分片的末尾,导致该分片 CPU、磁盘 I/O 和 WiredTiger 写锁排队严重,其他分片却空闲——这就是典型的写入热点。
常见错误现象包括:sh.status() 显示某分片 chunk 数量远超其他分片;db.currentOp() 中大量 insert 或 update 操作卡在 LOCK 状态;监控中该分片的 wt.cache.tracked_dirty_bytes 持续飙升。
- 避免用时间戳类字段单独作分片键,哪怕加了哈希也不推荐(
hashed分片对范围查询不友好) - 优先选择高基数、写入分布均匀、且查询中高频出现的字段组合,例如
{userId: 1, orderId: 1} - 若业务强依赖时间范围查询,可考虑将时间字段与用户 ID 组合成复合分片键,并启用
zone sharding控制冷热数据分布
如何验证当前分片键是否已引发竞争
直接查分片负载不靠感觉,得看真实指标。先运行 sh.status() 观察各分片 chunk 分布是否倾斜;再用 db.collection.stats() 对比不同分片上 count 和 size 的差异程度。
更关键的是实时竞争信号:db.adminCommand({currentOp: {secs_running: {$gt: 1}}}) 中若大量操作停留在 waitingForLock 或 acquiringLock,尤其集中在同一分片的 primary 节点上,基本可以判定是分片键设计问题。
- 检查
mongostat输出中的netIn/netOut和conn,热点分片连接数和网络吞吐通常显著高于均值 - 通过
db.serverStatus().metrics.operation查看各分片的writeOps分布,偏差超过 3 倍就值得警惕 - 注意:WiredTiger 引擎下,即使没锁等待,
wt.cache.heartbeat频繁刷脏页也可能掩盖底层争用,需结合page faults/sec综合判断
调整分片键前必须做的三件事
MongoDB 不支持在线修改分片键,一旦分片集合创建完成,就不能直接改。所谓“调整”,本质是重建集合+迁移数据,代价不小,必须前置验证清楚。
- 用
explain("executionStats")在目标字段组合上模拟高频查询,确认新分片键能覆盖主要读场景(尤其是totalKeysExamined接近nReturned) - 在测试环境用真实流量压测新分片键,重点观察
shard key distribution图表是否平滑,避免引入新的倾斜 - 评估迁移窗口期:使用
mongodump+mongorestore --shardsvr方案时,需预留足够时间应对 chunk 迁移失败后的手动干预
真正容易被忽略的点是:分片键字段不能是数组、不能含 $ 符号、且所有文档必须有该字段值(null 也不行)。上线前漏掉任一校验,迁移中途就会 abort。











