网站推广业务,建行网站登录,广州微信网站建设咨询,各大网站什么时候恢复彩色正如在MongoDB关系的最后一章中所看到的#xff0c;为了在MongoDB中实现规范化的数据库结构#xff0c;我们使用了引用关系的概念#xff0c;也被称为手动引用#xff0c;在这个概念中#xff0c;我们手动将被引用文档的id存储在其他文档中。然而#xff0c;在一个文档包…正如在MongoDB关系的最后一章中所看到的为了在MongoDB中实现规范化的数据库结构我们使用了引用关系的概念也被称为手动引用在这个概念中我们手动将被引用文档的id存储在其他文档中。然而在一个文档包含来自不同集合的引用的情况下我们可以使用MongoDB DBRefs。
DBRefs与手工引用 作为一个例子我们将使用DBRefs而不是手动引用考虑一个数据库我们在不同的集合address_home、address_office、address_mailing等中存储不同类型的地址家庭、办公室、邮件等。现在当一个用户集合的文档引用一个地址时它也需要根据地址类型来指定查找哪个集合。在这种情况下如果一个文档引用了许多集合的文档我们应该使用DBRefs。
使用DBRefs 在 DBRefs 中有三个字段 --
$ref - 这个字段指定了被引用文档的集合
$id - 这个字段指定了被引用文档的_id字段
$db - 这是一个可选的字段包含被引用文档所在的数据库的名称。
考虑一个具有DBRef字段地址的用户文档样本如代码片段所示
{_id:ObjectId(53402597d852426020000002),address: {$ref: address_home,$id: ObjectId(534009e4d852427820000002),$db: tutorialspoint},contact: 987654321,dob: 01-01-1991,name: Tom Benzamin
}
这里的地址DBRef字段指定引用的地址文件位于tutorialspoint数据库下的address_home集合中其id为534009e4d852427820000002。
下面的代码动态地在$ref参数指定的集合在我们的例子中是address_home中寻找一个id为DBRef中$id参数指定的文档。
var user db.users.findOne({name:Tom Benzamin})
var dbRef user.address
db[dbRef.$ref].findOne({_id:(dbRef.$id)})
上述代码返回存在于address_home集合中的以下地址文件 -
{_id : ObjectId(534009e4d852427820000002),building : 22 A, Indiana Apt,pincode : 123456,city : Los Angeles,state : California
} 什么是覆盖式查询 根据MongoDB的官方文档覆盖式查询是一个查询其中
查询中的所有字段都是一个索引的一部分。 在查询中返回的所有字段都在同一个索引中。 由于查询中的所有字段都是索引的一部分MongoDB会匹配查询条件并使用相同的索引返回结果而无需实际查看文档内部。由于索引存在于RAM中从索引中获取数据要比通过扫描文档获取数据快得多。
使用覆盖式查询 为了测试覆盖式查询请考虑用户集合中的以下文档
{_id: ObjectId(53402597d852426020000003),contact: 987654321,dob: 01-01-1991,gender: M,name: Tom Benzamin,user_name: tombenzamin
}
我们将首先使用下面的查询为用户集合的性别和用户名称字段创建一个复合索引------。
db.users.createIndex({gender:1,user_name:1})
{createdCollectionAutomatically : false,numIndexesBefore : 1,numIndexesAfter : 2,ok : 1
}
现在这个索引将涵盖以下查询 --
db.users.find({gender:M},{user_name:1,_id:0})
{ user_name : tombenzamin }
这就是说对于上述查询MongoDB不会去寻找数据库文件。相反它将从索引数据中获取所需的数据这是非常快的。
由于我们的索引不包括_id字段我们已经明确地将它从我们的查询结果集中排除因为MongoDB默认在每个查询中返回_id字段。所以下面的查询不会被包含在上面创建的索引中------。
db.users.find({gender:M},{user_name:1})
{ _id : ObjectId(53402597d852426020000003), user_name : tombenzamin }
最后请记住一个索引不能覆盖一个查询如果---。
任何被索引的字段是一个数组 任何一个被索引的字段是一个子文件 分析查询是衡量数据库和索引设计是否有效的一个非常重要的方面。我们将学习经常使用的$explain和$hint查询。
使用$explain $explain操作符提供了关于查询、查询中使用的索引和其他统计数据的信息。在分析你的索引的优化程度时它非常有用。
在上一章中我们已经为用户集合中的字段gender和user_name创建了一个索引使用的查询方式如下
db.users.createIndex({gender:1,user_name:1})
{numIndexesBefore : 2,numIndexesAfter : 2,note : all indexes already exist,ok : 1
}我们现在将在以下查询中使用$explain --
db.users.find({gender:M},{user_name:1,_id:0}).explain()
上述 explain() 查询返回以下分析结果 -
{queryPlanner : {plannerVersion : 1,namespace : mydb.users,indexFilterSet : false,parsedQuery : {gender : {$eq : M}},queryHash : B4037D3C,planCacheKey : DEAAE17C,winningPlan : {stage : PROJECTION_COVERED,transformBy : {user_name : 1,_id : 0},inputStage : {stage : IXSCAN,keyPattern : {gender : 1,user_name : 1},indexName : gender_1_user_name_1,isMultiKey : false,multiKeyPaths : {gender : [ ],user_name : [ ]},isUnique : false,isSparse : false,isPartial : false,indexVersion : 2,direction : forward,indexBounds : {gender : [[\M\, \M\]],user_name : [[MinKey, MaxKey]]}}},rejectedPlans : [ ]},serverInfo : {host : Krishna,port : 27017,version : 4.2.1,gitVersion : edf6d45851c0b9ee15548f0f847df141764a317e},ok : 1
}
我们现在来看看这个结果集中的字段 -
indexOnly的真值表示这个查询使用了索引。
cursor字段指定了使用的游标类型。BTreeCursor类型表示使用了一个索引并且给出了所使用的索引的名称。BasicCursor表示在没有使用任何索引的情况下进行了一次全扫描。
n表示返回的匹配文档的数量。
nscannedObjects表示扫描的文件总数。
nscanned表示扫描的文档或索引条目的总数。
使用$hint $hint操作符强制查询优化器使用指定的索引来运行查询。当你想用不同的索引来测试一个查询的性能时这特别有用。
db.users.find({gender:M},{user_name:1,_id:0}).hint({gender:1,user_name:1})
{ user_name : tombenzamin }
为了分析上述查询使用$explain -
db.users.find({gender:M},{user_name:1,_id:0}).hint({gender:1,user_name:1}).explain()
由此得出以下结果−
{queryPlanner : {plannerVersion : 1,namespace : mydb.users,indexFilterSet : false,parsedQuery : {gender : {$eq : M}},queryHash : B4037D3C,planCacheKey : DEAAE17C,winningPlan : {stage : PROJECTION_COVERED,transformBy : {user_name : 1,_id : 0},inputStage : {stage : IXSCAN,keyPattern : {gender : 1,user_name : 1},indexName : gender_1_user_name_1,isMultiKey : false,multiKeyPaths : {gender : [ ],user_name : [ ]},isUnique : false,isSparse : false,isPartial : false,indexVersion : 2,direction : forward,indexBounds : {gender : [[\M\, \M\]],user_name : [[MinKey, MaxKey]]}}},rejectedPlans : [ ]},serverInfo : {host : Krishna,port : 27017,version : 4.2.1,109gitVersion : edf6d45851c0b9ee15548f0f847df141764a317e},ok : 1
} 原子操作的模型数据 推荐的维护原子性的方法是将所有相关的信息经常更新的信息使用嵌入式文件保存在一个文件中。这将确保单个文档的所有更新都是原子性的。
假设我们创建了一个名为productDetails的集合并在其中插入了一个文档如下所示
db.createCollection(products)
{ ok : 1 }db.productDetails.insert({_id:1,product_name: Samsung S3,category: mobiles,product_total: 5,product_available: 3,product_bought_by: [{customer: john,date: 7-Jan-2014},{customer: mark,date: 8-Jan-2014}]}
)
WriteResult({ nInserted : 1 })在这份文件中我们在product_bought_by字段中嵌入了购买产品的客户的信息。现在每当有新客户购买产品时我们将首先使用product_available字段检查该产品是否仍然可用。如果可用我们将减少product_available字段的值并在product_bought_by字段中插入新客户的嵌入式文档。我们将使用findAndModify命令来实现这一功能因为它可以在同一时间内搜索和更新文档。
db.products.findAndModify({ query:{_id:2,product_available:{$gt:0}}, update:{ $inc:{product_available:-1}, $push:{product_bought_by:{customer:rob,date:9-Jan-2014}} }
})
我们的嵌入式文档和使用findAndModify查询的方法确保了产品购买信息只有在产品可用时才会被更新。而整个交易都在同一个查询中是原子性的。
与此相反考虑一下这样的情况我们可能把产品的可用性和谁购买了该产品的信息分开保存。在这种情况下我们将首先使用第一个查询来检查产品是否可用。然后在第二个查询中我们将更新购买信息。然而有可能在这两个查询的执行过程中有其他用户购买了该产品而该产品已不再可用。在不知道这一点的情况下我们的第二个查询将根据我们第一个查询的结果来更新购买信息。这将使数据库不一致因为我们已经售出了一个不可用的产品。 高级索引
我们在名为用户的集合中插入了以下文件如下图所示
db.users.insert({address: {city: Los Angeles,state: California,pincode: 123},tags: [music,cricket,blogs],name: Tom Benzamin}
)
上述文件包含一个地址子文件和一个标签阵列。
阵列字段的索引 假设我们想根据用户的标签来搜索用户文档。为此我们将在集合中的标签数组上创建一个索引。
在数组上创建索引又会为其每个字段创建单独的索引条目。所以在我们的例子中当我们在tags数组上创建索引时将为其值music、cricket和blogs创建单独的索引。
要在tags数组上创建一个索引请使用以下代码
db.users.createIndex({tags:1})
{
createdCollectionAutomatically : false,
numIndexesBefore : 2,
numIndexesAfter : 3,
ok : 1
}在创建索引之后我们可以像这样在集合的tags字段上进行搜索 db.users.find({tags:cricket}).pretty()
{_id : ObjectId(5dd7c927f1dd4583e7103fdf),address : {city : Los Angeles,state : California,pincode : 123},tags : [music,cricket,blogs],name : Tom Benzamin
}要验证是否使用了正确的索引请使用以下解释命令−
db.users.find({tags:cricket}).explain()
这将给出以下结果−
{queryPlanner : {plannerVersion : 1,namespace : mydb.users,indexFilterSet : false,parsedQuery : {tags : {$eq : cricket}},queryHash : 9D3B61A7,planCacheKey : 04C9997B,winningPlan : {stage : FETCH,inputStage : {stage : IXSCAN,keyPattern : {tags : 1},indexName : tags_1,isMultiKey : false,multiKeyPaths : {tags : [ ]},isUnique : false,isSparse : false,isPartial : false,indexVersion : 2,direction : forward,indexBounds : {tags : [[\cricket\, \cricket\]]}}},rejectedPlans : [ ]},serverInfo : {host : Krishna,port : 27017,version : 4.2.1,gitVersion : edf6d45851c0b9ee15548f0f847df141764a317e},ok : 1
}上述命令的结果是 cursorBtreeCursor tags_1这证实了正确的索引被使用。
对子文档字段进行索引 假设我们想根据城市、州和平码字段来搜索文件。由于所有这些字段都是地址子文件字段的一部分我们将在子文件的所有字段上创建一个索引。
为了在子文档的所有三个字段上创建索引请使用以下代码
db.users.createIndex({address.city:1,address.state:1,address.pincode:1})
{numIndexesBefore : 4,numIndexesAfter : 4,note : all indexes already exist,ok : 1
}一旦建立了索引我们就可以利用这个索引来搜索任何一个子文件字段如下所示 db.users.find({address.city:Los Angeles}).pretty()
{_id : ObjectId(5dd7c927f1dd4583e7103fdf),address : {city : Los Angeles,state : California,pincode : 123},tags : [music,cricket,blogs],name : Tom Benzamin
}
记住查询表达式必须遵循指定的索引的顺序。因此上面创建的索引将支持以下查询 -
db.users.find({address.city:Los Angeles,address.state:California}).pretty()
{_id : ObjectId(5dd7c927f1dd4583e7103fdf),address : {city : Los Angeles,state : California,pincode : 123},tags : [music,cricket,blogs],name : Tom Benzamin
}索引的局限性
额外的开销 每个索引都会占用一些空间并在每次插入、更新和删除时造成开销。因此如果你很少使用你的集合进行读取操作不使用索引是有意义的。
内存的使用 由于索引被存储在RAM中你应该确保索引的总大小不超过RAM的限制。如果总大小增加了RAM的大小它将开始删除一些索引导致性能损失。
查询限制 索引不能用于使用--的查询。
正则表达式或否定运算符如$nin, $not, 等。 算术运算符如$mod等等。 $where条款 因此建议你总是检查你的查询的索引使用情况。
索引键的限制 从2.6版本开始如果现有的索引字段的值超过了索引键的限制MongoDB将不会创建一个索引。
插入超过索引键限制的文档 如果任何文档的索引字段值超过了索引键限制MongoDB将不会将该文档插入到一个有索引的集合。mongorestore和mongoimport工具的情况也是如此。
最大范围 一个集合不能有超过64个索引。 索引名称的长度不能超过125个字符。 一个复合索引最多可以有31个字段的索引。