Yii2 MongoDB查询:Shell比PHP快的原因排查(文档扫描差异)
问题描述
在Mongo Shell与Yii2 PHP框架中执行MongoDB count查询时,出现显著性能差异:
Mongo Shell查询语句
db.runCommand({ explain:{ count:'galleries', query:{ "$and": [ { "status": { "$eq": "published" }, "type": { "$ne": "picture" }, "series": { "$in": [ " there are 96 items here " ] } }, { "publishedAt": { "$lte": " DATE " } } ] } } });
Shell端执行统计
executionStats: { executionSuccess: true, nReturned: 0, executionTimeMillis: 49, totalKeysExamined: 28682, totalDocsExamined: 0, }
Yii2 PHP查询代码
$filters = [ '$and' => [ [ "status" => ['$in' => ["published"]], "type" => ['$nin' => ["video"]], "photos" => ['$exists' => true, '$not' => ['$size' => 0]], "series" => ['$in' => [" 96 items "]] ], ["publishedAt" => ['$lte' => " DATE "]] ] ]; $collection = Yii::$app->mongodb->getCollection('galleries'); $count = $collection->count($filters);
PHP端执行统计
"planningTimeMicros": 3296, "keysExamined": 27998, "docsExamined": 27946, "fromPlanCache": true, "durationMillis": 412
两者使用相同索引,且均为最新版本的PHP与MongoDB,核心差异为PHP端扫描了大量匹配文档,而Shell端未扫描文档,性能悬殊的原因如下:
原因分析
查询条件差异导致无法使用覆盖索引:
Shell的查询条件中,status用$eq、type用$ne,且无额外的photos字段判断;而PHP代码中status用单值$in(逻辑等价$eq但索引处理逻辑有差异)、type用$nin,还新增了photos: {$exists: true, $not: {$size: 0}}的复合条件。这些新增条件无法被现有索引覆盖,MongoDB必须回表加载文档才能验证条件是否满足,而Shell的查询完全可以通过索引键完成计数,无需访问文档。Yii2组件的count实现逻辑差异:
Yii2的MongoDB组件count()方法底层可能并未直接调用MongoDB原生的count命令,而是采用了find()查询后再统计数量的方式。当查询存在无法索引覆盖的条件时,这种方式会触发全文档扫描;而Shell直接使用count命令,MongoDB可以利用索引进行快速计数优化。执行计划选择差异:
即使索引相同,查询条件的细微差异会让MongoDB选择不同的执行计划。Shell的查询命中了覆盖索引执行计划,仅需扫描索引键即可得到计数;PHP的查询因$nin和photos条件限制,无法使用覆盖索引,只能先通过索引筛选候选文档,再逐个加载文档验证条件,最终导致大量文档扫描和更长的执行时间。
内容的提问来源于stack exchange,提问作者Goendg

