分片键选择不当导致写入集中引发局部热点,根本原因是基数低或单调递增;应改用哈希分片或复合键,且分片键不可直接修改。

分片键选择不当导致写入集中到少数分片
锁争用严重往往不是全局锁问题,而是数据分布不均造成的局部热点——写操作持续打在同一个分片的少数 chunk 上,WiredTiger 的文档级锁在此场景下仍会排队等待。根本原因常是分片键基数低或单调递增(如 _id 默认 ObjectId、时间戳字段)。
检查方法:连接 mongos 后运行 sh.status(),观察各 shard 的 chunks 数量是否相差 3 倍以上;再查 db.collection.stats().shardDistribution 看数据倾斜比例。
- 避免用
created_at、timestamp单独作分片键,改用哈希分片({ field: "user_id", "hashed": true })或复合键(如{ region: 1, user_id: 1 }) - 已有集合需重新分片时,先确保目标分片键在全量数据中具备高基数,再执行
sh.shardCollection("db.coll", { "user_id": "hashed" }) - 哈希分片后无法支持范围查询,若业务强依赖范围扫描,可考虑 zone sharding + 范围预划分
oplog 同步瓶颈引发主从延迟与锁堆积
副本集内主节点写入快、从节点同步慢时,oplog 积压会导致主节点的 WiredTiger cache 持续刷盘,间接抬高写锁等待时间。这不是分片层的锁,但会放大整个集群的写入毛刺。
典型现象:监控显示 repl.lagMillis 持续 >500ms,同时 metrics.record.moves 频繁上升(chunk 迁移加剧锁竞争)。
- 立即检查 oplog 大小:运行
db.oplog.rs.stats().size / (1024*1024*1024),若 50GB,说明 oplog 不足 - 扩容 oplog 必须在主节点执行:
db.runCommand({ replSetResizeOplog: 1, size: 40960 })(单位 MB),注意该命令不阻塞服务但需 replica set 处于稳定状态 - 确认从节点配置了
syncSourceHost指向网络延迟最低的源,而非默认轮询所有节点
批量写入未控制并发度触发 chunk 拆分风暴
单次插入超 10 万文档、且分片键值高度集中时,MongoDB 可能在短时间内触发多次 chunk 拆分(split)和迁移(moveChunk),这些元数据操作本身会持有 config server 锁,并阻塞其他写请求。
错误日志里会出现 cannot split chunk 或 moveChunk commit failed,同时 shardingStatistics 中 chunksMoved 激增。
- 批量写入前用
sh.status()确认目标分片 chunk 数量,若单 shard chunk sh.splitAt("db.coll", { "shard_key": "xxx" }) - 应用层控制并发:将大批次切分为每批 5000–10000 文档,间隔 100ms 提交,避免触发自动均衡器(balancer)高频干预
- 临时关闭 balancer 执行大导入:
sh.stopBalancer(),导入完成后再sh.startBalancer()
客户端连接池与写关注(write concern)配置失当
看似无关,但过高的 w: "majority" 或过大的连接池会延长事务生命周期,使锁持有时间不可控。尤其在跨数据中心部署时,majority 等待可能卡住数秒。
现象:currentQueue.writers 持续 >10,globalLock.activeClients.writers 波动剧烈,但单个写操作耗时并不高。
- 非强一致性场景改用
w: 1或w: 2,避免等待全部从节点确认 - 驱动连接池参数调低:
connectionsPerHost建议 ≤20,maxWaitTime≤60000,防止线程无限排队 - 写操作显式指定
journal: false(仅限允许短暂丢失的埋点类数据)
sh.shardCollection() 前,用真实流量样本跑一遍 sh.status(true) 的分布模拟。











