mongodb源码分析(六)查询3之mongod的cursor的产生

本文深入分析了MongoDB中cursor的生成过程,特别是mongod如何创建各种类型的游标,如BasicCursor、ReverseCursor等,并详细解释了查询优化过程,包括基本的查询计划选择、$or查询的优化以及排序策略。文章通过实例探讨了索引的选择和多计划扫描的管理,揭示了MongoDB查询执行的内部机制。
摘要由CSDN通过智能技术生成

        上一篇文章分析了mongod的数据库加载部分,下面这一篇文章将继续分析mongod cursor的产生,这里cursor

的生成应该是mongodb系统中最复杂的部分.下面先介绍几个关于mongodb的游标概念.


basicCursor: 直接扫描整个collection的游标,可设置初始的扫描位置,扫描为顺序扫描.

ReverseCursor: 反向扫描游标,相对于顺序扫描,这里是反向扫描.

ReverseCappedCursor: cap集合的反向扫描游标.

ForwardCappedCursor: cap集合的顺序扫描游标.

GeoCursorBase:     空间地理索引游标的基类,我并未阅读相关代码,感兴趣的自己研究吧.

BtreeCursor:    mongodb的一般数据索引扫描游标,这个游标完成对于索引的扫描.

MultiCursor:     有待研究.

QueryOptimizerCursor:  经过优化的扫描游标,多plan扫描时或者对于查询中有$or的语句且$or语句其作用时由于

优化查询的游标. 这里将简单描述其流程.

1. 如果是类似这种db.coll.find()的查询则将直接返回一个BasicCursor的游标扫描全表.

2. 如果是简单的id查询如db.coll.find(_id:xxx),且允许_id查询plan的情况下直接查询_id索引,返回一个_id索引

    的BtreeCursor.

3.根据查询整理出查询值的范围,作为优化查询范围的依据,如:db.coll.find({x:{$lt:100,$gt:20}}),那么这里其范围就
   是[20.100],这个范围只有在对应的变量是索引的情况下起作用,如x为其中的一个索引,那么这里的范围将帮助其
   游标BtreeCursor首先直接将查询范围定位到[20,100]的位置,这个工作对于由Btree组织的索引来说很简单.简单
   来说就是优化查询范围.但是若x不是索引那么这里得到的查询范围将是无用的,这里将返回一个BasicCursor的

   游标执行全表扫描.

4.根据得到的所有的查询域的范围比如说x:[10,20],y:[4,6]这种选取查询计划(QueryPlan).查询计划的选取这里举个

    例子,有x,y两个查询域.index有{x:1},{y:1},{x:1,y:1}这几个索引,那么选取计划时发现只有索引{x:1,y:1}完全满足查

    询计划,其是最优的,那么确定选取这个索引为查询索引.返回唯一的QueryPlan,最后生成一个确切的

    BtreeCursor.但是如果没有{x:1,y:1}这个索引怎么办呢?那么剩下两个索引{x:1},{y:1}都部分包含了查询域,他们

    都是有作用的,于是乎生成了两个QueryPlan,一个对应于索引{x:1},一个对应于索引{y:1},于是乎使用

    QueryOptimizerCursor这个cursor管理两个BtreeCursor,每次交替执行两个BtreeCursor的查询,直到一个

    BtreeCursor查询完毕,那么这个plan是所有plan中最优的,将其缓存起来,下一次同样查询时直接选择这个plan作

    为查询的plan.因为两个plan中首先完成扫描的plan查询的次数最少.那么两个plan都查询到的同一条满足查询要

    求的数据怎么办,查询结尾会有一个对于满足要求的document地址的记录,如果一条满足要求的document的地址

    已经在记录中了,就不再记录这个document.

5.$or查询的优化,对于一个$or举例来说明:{$or:[{x:1},{y:2},{z:3,a:4}]}这样的查询请求,这样要当$or中的每一个查

    询域,中至少一个域是可用的索引比如说有索引x,y,a那么这个$or才是有意义的.如果这个$or有意义,那么这里将

    使用QueryOptimizerCursor来处理每一个$or中的查询域,比如说{x:1},然后这里退化到4,plan的选取,$or中的查

    询一个一个的执行.回过头来分析,如果没有索引y,那么对于这个$or的查询因为满足y:2的文档将会被返回,那么

    只能扫描全表,这时即使有索引x,z或者a这种也不能避免全表的扫描,那么这里的$or就变得没有优化的意义了. 

    另外如果查询指定了sort(xxx:1)按照某些域排序或者设定了最大值最小值$or也是无意义的.

6. 查询结束后的排序,当指定了如db.coll.find({x:1,y:2}).sort(z:1),这种需要按照z升序排列的查询时,这种情况就要

    考虑当没有索引z时,那么排序是肯定避免不了的,查询的结果会放到一个map中,map按照z的升序来排序,当排序

    的文档总大小超过了默认热32M最大值时会返回错误,提醒你应该为z域建立索引了.下面来看有索引时的状况.

(1),索引为{x:1},{z:1},如果这里两个索引查询的文档数目一样多,那么优先选择{x:1},因为建立索引时其比较靠前,然

     后还是得排序.

(2)索引{x:1,z:1},{z:1,x:1},由于第一个索引查出来的顺序是按照x的顺序来排列的,那么还是得排序,第二个索引不需

     要排序,但是考虑最优QueryPlan的选取是找到最先执行完plan的索引,这里仍然不会选取{z:1,x:1}这个plan,而

     是会选取{x:1,z:1}这个plan.考虑到两个索引还不直观,这里加入一个{x:1},{x:1,z:1},{z:1,x:1},那么这里将会选择第

     一个索引{x:1}.要让mongod选择{z:1,x:1}这plan只能使用db.coll.find({x:{$lt:5,$gt:0}).sort({z:1}).hint({z:1,x:1}),

     总觉得这是一个bug,mongod应该能够修正这种情况才对,应该能自己选择最优的索引{z:1,x:1}才对.这里有一篇

     10gen的工程师谈mongodb索引优化的文章可以一看:

     http://www.csdn.net/article/2012-11-09/2811690-optimizing-mongodb-compound

上面介绍了那么多的流程情况下面正式进入代码分析阶段.接上篇文章runQuery->queryWithQueryOptimizer

    string queryWithQueryOptimizer( int queryOptions, const string& ns,
                                    const BSONObj &jsobj, CurOp& curop,
                                    const BSONObj &query, const BSONObj &order,
                                    const shared_ptr<ParsedQuery> &pq_shared,
                                    const BSONObj &oldPlan,
                                    const ConfigVersion &shardingVersionAtStart,
                                    scoped_ptr<PageFaultRetryableSection>& parentPageFaultSection,
                                    scoped_ptr<NoPageFaultsAllowed>& noPageFault,
                                    Message &result ) {

        const ParsedQuery &pq( *pq_shared );
        shared_ptr<Cursor> cursor;
        QueryPlanSummary queryPlan;
        
        if ( pq.hasOption( QueryOption_OplogReplay ) ) {//用于oplog的回放的游标.
            cursor = FindingStartCursor::getCursor( ns.c_str(), query, order );
        }
        else {
            cursor =//本文的主要分析的部分,游标的获取
                NamespaceDetailsTransient::getCursor( ns.c_str(), query, order, QueryPlanSelectionPolicy::any(),
                                                      0, pq_shared, false, &queryPlan );
        }
        scoped_ptr<QueryResponseBuilder> queryResponseBuilder
                ( QueryResponseBuilder::make( pq, cursor, queryPlan, oldPlan ) );
        bool saveClientCursor = false;
        OpTime slaveReadTill;
        ClientCursor::Holder ccPointer( new ClientCursor( QueryOption_NoCursorTimeout, cursor,
                                                         ns ) );
        for( ; cursor->ok(); cursor->advance() ) {
            bool yielded = false;//这里的查询机制,当查询时间超过了一个给定的值,这里为10ms或者在一段时间内调用该函数超过了128次,又或者cursor指向的文档不在内存中,那么这里将睡眠一会儿,睡眠的时间由当前系统中读取读者数目r和写者数目w,由10*r+w决定,单位为ms,最大值不超过1000000.
            if ( !ccPointer->yieldSometimes( ClientCursor::MaybeCovered, &yielded ) ||//睡眠前会通知游标保存当前位置
                !cursor->ok() ) {//这里是睡眠完成后发现当前游标失效了
                cursor.reset();
                queryResponseBuilder->noteYield();
                // !!! TODO The queryResponseBuilder still holds cursor.  Currently it will not do
                // anything unsafe with the cursor in handoff(), but this is very fragile.
                //
                // We don't fail the query since we're fine with returning partial data if the
                // collection was dropped.
                // NOTE see SERVER-2454.
                // TODO This is wrong.  The cursor could be gone if the closeAllDatabases command
                // just ran.
                break;
            }
            if ( yielded ) {//发生过yield,这是由两种情况构成,要么关心的数据不在内存,要么
                queryResponseBuilder->noteYield();//clientCursor超过了ccPointer->_yieldSometimesTracker规定的yield时间
            }
            if ( pq.getMaxScan() && cursor->nscanned() > pq.getMaxScan() ) {//超过了用户查询时设置的最大的扫描扫描数目
                break;
            }
            if ( !queryResponseBuilder->addMatch() ) {//具体查询的文档的匹配过程,下一篇文章将介绍
                continue;
            }
            // Note slave's position in the oplog.
            if ( pq.hasOption( QueryOption_OplogReplay ) ) {
                BSONObj current = cursor->current();
                BSONElement e = current["ts"];
                if ( e.type() == Date || e.type() == Timestamp ) {
                    slaveReadTill = e._opTime();
                }
            }
            if ( !cursor->supportGetMore() || pq.isExplain() ) {
                if ( queryResponseBuilder->enoughTotalResults() ) {
                    break;
                }
            }
            else if ( queryResponseBuilder->enoughForFirstBatch() ) {
                // if only 1 requested, no cursor save
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值