分片键倾斜导致查询优化器基于失真统计生成次优计划:第一步查stats确认数据分布不均;第二步用explain观察winningplan是否退化为shard_merge;第三步对比倾斜值与均衡值的执行计划差异,验证倾斜引发ixscan失效、全分片扫描或聚合阻塞单一分片。

当MongoDB分片集群中分片键值分布严重倾斜,部分chunk承载远超平均的数据量,查询优化器会基于失真的统计信息生成次优执行计划,导致本该走索引扫描的查询被迫全分片扫描、聚合阶段卡在单一分片、甚至误选低效索引。
倾斜如何污染查询优化器的决策依据
第一步:执行db.collection.stats() → 查看avgObjSize和count,若某shard上chunk数量极少但size占比超60%,说明该分片实际承载了绝大多数文档;
第二步:运行db.collection.explain("queryPlanner").find({shard_key_field: "skewed_value"}) → 观察winningPlan中stage是否为SHARD_MERGE而非IXSCAN,这表示mongos已放弃下推索引扫描,改为汇总所有分片结果后本地过滤;
第三步:对比同一查询在非倾斜值上的explain输出 → 若对"balanced_value"能命中IXSCAN+FETCH,而对"skewed_value"退化为COLLSCAN或SHARD_MERGE,则确认倾斜直接导致计划降级。
优化器依赖每个分片上报的元数据(如索引基数、文档分布直方图)做代价估算。当95%文档集中在3个chunk里,其余分片上报的“低密度”统计会误导优化器认为索引选择性高、范围扫描廉价——实际执行时,mongos发现目标值只落在一个分片,却因缺乏该分片内部真实分布信息,无法启用局部高效计划。
倾斜引发的两类典型计划劣化
方法一:范围查询变全分片扫描
对复合分片键{tenant_id: "hashed", created_at: 1}执行db.coll.find({tenant_id: "t-001", created_at: {$gt: ISODate("2025-01-01")}}),若tenant_id仅5个取值,所有t-001数据锁死在单一分片;但优化器仍按“hashed字段均匀”假设,认为created_at范围可跨分片裁剪,最终生成需向全部分片广播的查询计划。
方法二:聚合管道在单一分片阻塞
执行db.coll.aggregate([{$match: {status: "pending"}}, {$group: {_id: "$user_id", cnt: {$sum: 1}}}])时,若status="pending"占总量80%且全部落入shard0,则$group阶段无法并行,所有中间结果必须在shard0内存中完成聚合——【此时executionStats中的nReturned可能极小,但totalDocsExamined高达千万级,且shard0的CPU持续100%】。
为什么explain看不到倾斜导致的计划问题
直接在mongos上运行explain()只能看到路由层视角的winningPlan,无法反映各分片内部实际执行路径;
必须登录具体过载分片的primary节点,对相同查询执行db.collection.explain("executionStats") → 这里才能看到真实stage树,比如发现IXSCAN后紧跟大量FILTER而非预期的PROJECTION;
注意:sh.status()显示的chunk数量均衡≠数据量均衡,【务必用db.printShardingStatus()配合手动计算各shard的storageSize/numChunks比值】,比值偏差超3倍即存在隐性倾斜。











