赞
踩
在之前的2篇文章中分别分析了mongod和mongo的启动流程,下面开始将分析mongodb的查询,由于查询部分流程比较长,将分成mongo端的请求,mongod端的数据库的加载,mongod query的选取,mongod文档的匹配与数据的响应几部分来分析。
首先进入mongo的查询请求部分.mongo的查询请求部分归纳起来很简单就是将请求分装成一个Message结构,然后将其发送到服务端,等待服务端的相应数据,取得数据最后显示结果.下面来看具体流程分析吧.
当我们点击db.coll.find({x:1})时按照上一篇文章的讲解,我们首先来到了mongo/shell/dbshell.cpp
上一篇文章分析到了客户端查询请求的发送,接着分析服务端的处理动作,分析从服务端响应开始到数据库
正确加载止,主要流程为数据库的读入过程与用户的认证.
mongod服务对于客户端请求的处理在mongo/db/db.cpp MyMessageHandler::process中,其中调用了
函数assembleResponse完成请求响应,我们就从这个函数开始入手分析,代码很长,删除一些支流或者不相关的代码.
前进到receivedQuery,其解析了接收到的数据,然后调用runQuery负责处理查询,然后出来runQuery抛出的异常,直接进入runQuery.
NamespaceDetails : this is the "header" for a collection that has all its details.
It's in the .ns file and this is a memory mapped region (thus the pack pragma above).
_init函数执行完毕,网上回到Database::Database()函数:
未通过将返回false,返回false,将导致mongod向客户端发送未认证信息,客户端的操作请求失败
本文到这里结束,主要是搞清楚了mongod接收到来自客户端请求后的执行流程到数据库的加载,重要的
是明白ns文件的作用,普通数据文件xx.0,xx.1的映射,下一篇文章我们将继续分析查询请求的处理.
本文链接:http://blog.csdn.net/yhjj0108/article/details/8255968
作者: yhjj0108,杨浩
上一篇文章分析了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}}),那么这里其范围就游标执行全表扫描.
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
继续来看游标的产生:
NamespaceDetailsTransient::getCursor->CursorGenerator::generate
索引如:db.coll.ensureIndex({x:1})时,当不存在x:[xx,xxx,xxxx]这种数据时那么这个索引x就是单值索引,当插入一条数据中
包括了x:[xx,xxx,xxx]这种array结构的x时,x变为多值索引.多值索引简单来说就是就是对于array中的每一个之建立一个索引.
继续前进到_singleKey对象的构造函数:
回到MultiPlanScanner::init继续前进看看:QueryPlanSet::make函数.
在继续之前这里需要说明的是mongodb的plan分5种:
时会产生一个空的range,进而产生完全无法匹配的状况.
Optimal: FieldRangeSetPair中每一个域都在索引中,这是一个最优的索引,根据这个索引产生的plan
将是最优的,不需要再考虑其它plan了.
Helpful: 选择的索引能够覆盖FieldRangeSetPair中的部分域,这个索引是有用的,虽然可能会多搜索
一些不会匹配其它域的document.在没有Optimal索引的情况下会根据Helpful索引建立plan
有多个Helpful的索引将建立多plan.
Unhelpful:无用的索引,不会考虑,似乎和Impossible差不多.
Disallowed: 如果使用这个索引查询数据可能会出错,这里有一个sparse的概念.mongodb的普通索引
是会索引无关数据的,举例来说有索引{x:1},插入一条数据{y:10},那么索引也会把这条数据
索引了,但是建立sparse索引db.xxx.ensureIndex({x:1},{sparse:true})那么这里的索引将
不再索引{y:10}这条数据了.对于sparse索引并且存在类似{z:{$exist:false}}这种情况,那么
使用该索引结果可能是不正确的不考虑该索引.
下面继续看代码:
继续来看看newPlan函数,这个函数包括了一个plan的构造.其同样是new一个QueryPlan然后调用其init函数:
条件就能够达到要求找到最优的plan{y:1,x:1},为什么不选择这个plan呢,难道说考虑到要插入这种
{y:40}这种数据吗,虽然{x:1}这种索引没有y域但是其还是会对这个{y:40}数据加入索引啊,数目并不会
比{y:1,x:1}这个索引的数目多啊,而且{y:1,x:1},但是后来我发现我忽略了一个问题,索引{y:1,x:1}无法直接定
位到x的范围,那么查询的无关的document数目可能比{x:1}这个索引查询的数目多,对于mongodb优化考
虑的是如何得到最少的document扫描数目,所以{y:1,x:1}也只能是一个可考虑的索引而无法成为最优的
索引,所以要想让查询使用这个索引只能使用hint了.
这篇文件就暂时写到这里,后面还有很多内容,一篇文章写下来太多,还是分成两篇文章吧,关于plan的
选取请看下一篇文章.
本文链接:http://blog.csdn.net/yhjj0108/article/details/8257081
作者:yhjj0108,杨浩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。