MongoDB智能查询优化的问题
自动 查询优化是 MongoDB一个专门设计的功能。简言之,这个功能就是通过对查询进行分析,从而判断出更有利的索引使用策略。而这个智能的功能,实际潜伏着一些问题。
传统的查询优化是通过对语句进行语义分析来进行索引使用的预判,而MongoDB使用了一种更简单粗暴的方式,对查询的实际执行情况进行分析,它同时执行多条查询,每一条查询使用不同的索引,当其中一条返回时,终止其它的查询,并记录这次的结果,以后会优先使用返回更快的索引。直到类似的查询又变得慢了,它再进行新的测试得出新的索引使用方式。
最近在工作中遇到这样的问题,一个collection偶尔出现大量的慢查询,通过对慢日志进行分析,这些慢查询的nscan都很高,但是按正常的索引使用逻辑,这些查询都能够直接使用索引,不会导致这么高的慢查询。通过我们在事后的测试也能证实这一点。那倒底发生了什么,导致MongoDB不走正确的索引呢?
后来通过测试发现,每个我们执行几个特别大的skip后,后续一段skip不大的查询也都会不使用正确的索引。于是我们想到是智能索引优化的问题。
而这个问题最终在于,MongoDB的智能查询优化并没有那么智能。它的判断粒度为query条件,而skip和limit都不在其判断之中( 见此)。也就是说,如果你使用了正常索引,但是由于很大的skip需要跳过很多条记录,从而进一步导致查询变慢,MongoDB可能会对所有此query的查询都弃用此正常索引。
这样的结果就是,我们几次比较大的skip查询后,后续小的skip查询也用不到索引了。因为几次大的skip会导致MongoDB重新进行查询优化,重新检测合适的索引。而这时候可能它测试时使用的也是一个skip过大的查询,就导致它放弃了对正确索引的使用。
而为什么它又慢慢恢复了呢,因为后续的查询操作由于用不到索引,会有很多慢查询,MongoDB发现这种情况后会再次进行查询优化检测,这次它发现使用正确索引后会更快,于是它又开始选择正确的索引。所以,当你发现你的慢日志里有些查询并没有用到你希望用到的索引时,可能你遇到这种问题了。
情况就是这么简单,那么,如何规避这种影响呢。主要有两件事情需要做:
第一就是放弃这种所谓的智能,让一切可掌控。具体方法就是在查询时添加hint调用,强制MongoDB使用我们指定的索引。这能解决一半问题,就是skip较小的情况下总是能使用合适的索引。但是skip很大的情况下依然会很慢。所以还有下面的优化。
第二是优化查询方式,在我们的业务中,大的skip主要来自于用户直接访问最后一页或几页,这时候通过skip来获取经常需要skip几十万条数据。所以我们通过对获取记录在总记录中的位置进行判断,如果需要的数据更接近于列表末尾,那么就使用相反的sort条件,从后往前获取数据,这样skip就大大减小了。对应的nscan(扫描行数)也大大减少。原来导致MongoDB需要重新测试的情况也就自然消失了。