MongoDB 高级教程

MongoDB 关系

MongoDB 的关系表示多个文档之间在逻辑上的相互联系。php

文档间能够经过嵌入和引用来创建联系。正则表达式

MongoDB 中的关系能够是:mongodb

  • 1:1 (1对1)
  • 1: N (1对多)
  • N: 1 (多对1)
  • N: N (多对多)

 

接下来咱们来考虑下用户与用户地址的关系。数据库

一个用户能够有多个地址,因此是一对多的关系。编程

如下是 user 文档的简单结构:数组

{
   "_id":ObjectId("52ffc33cd85242f436000001"),
   "name": "Tom Hanks",
   "contact": "987654321",
   "dob": "01-01-1991"
}

如下是 address 文档的简单结构:浏览器

{
   "_id":ObjectId("52ffc4a5d85242602e000000"),
   "building": "22 A, Indiana Apt",
   "pincode": 123456,
   "city": "Los Angeles",
   "state": "California"
} 

嵌入式关系

使用嵌入式方法,咱们能够把用户地址嵌入到用户的文档中:缓存

   "_id":ObjectId("52ffc33cd85242f436000001"),
   "contact": "987654321",
   "dob": "01-01-1991",
   "name": "Tom Benzamin",
   "address": [
      {
         "building": "22 A, Indiana Apt",
         "pincode": 123456,
         "city": "Los Angeles",
         "state": "California"
      },
      {
         "building": "170 A, Acropolis Apt",
         "pincode": 456789,
         "city": "Chicago",
         "state": "Illinois"
      }]
} 

以上数据保存在单一的文档中,能够比较容易的获取和维护数据。 你能够这样查询用户的地址:安全

>db.users.findOne({"name":"Tom Benzamin"},{"address":1})

注意:以上查询中 db 和 users 表示数据库和集合。服务器

这种数据结构的缺点是,若是用户和用户地址在不断增长,数据量不断变大,会影响读写性能。

引用式关系

引用式关系是设计数据库时常常用到的方法,这种方法把用户数据文档和用户地址数据文档分开,经过引用文档的 id 字段来创建关系。

{
   "_id":ObjectId("52ffc33cd85242f436000001"),
   "contact": "987654321",
   "dob": "01-01-1991",
   "name": "Tom Benzamin",
   "address_ids": [
      ObjectId("52ffc4a5d85242602e000000"),
      ObjectId("52ffc4a5d85242602e000001")
   ]
}

以上实例中,用户文档的 address_ids 字段包含用户地址的对象id(ObjectId)数组。

咱们能够读取这些用户地址的对象id(ObjectId)来获取用户的详细地址信息。

这种方法须要两次查询,第一次查询用户地址的对象id(ObjectId),第二次经过查询的id获取用户的详细地址信息。

>var result = db.users.findOne({"name":"Tom Benzamin"},{"address_ids":1})
>var addresses = db.address.find({"_id":{"$in":result["address_ids"]}})

MongoDB 数据库引用

在上一章节MongoDB关系中咱们提到了MongoDB的引用来规范数据结构文档。

MongoDB 引用有两种:

  • 手动引用(Manual References)
  • DBRefs

DBRefs vs 手动引用

考虑这样的一个场景,咱们在不一样的集合中 (address_home, address_office, address_mailing, 等)存储不一样的地址(住址,办公室地址,邮件地址等)。

这样,咱们在调用不一样地址时,也须要指定集合,一个文档从多个集合引用文档,咱们应该使用 DBRefs。


使用 DBRefs

DBRef的形式:

{ $ref : , $id : , $db : }

三个字段表示的意义为:

  • $ref:集合名称
  • $id:引用的id
  • $db:数据库名称,可选参数

如下实例中用户数据文档使用了 DBRef, 字段 address:

{ "_id":ObjectId("53402597d852426020000002"), "address": { "$ref": "address_home", "$id": ObjectId("534009e4d852427820000002"), "$db": "w3cschoolcc"}, "contact": "987654321", "dob": "01-01-1991", "name": "Tom Benzamin" }

address DBRef 字段指定了引用的地址文档是在 address_home 集合下的 w3cschoolcc 数据库,id 为 534009e4d852427820000002。

如下代码中,咱们经过指定 $ref 参数(address_home 集合)来查找集合中指定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的文档中说明,覆盖查询是如下的查询:

  • 全部的查询字段是索引的一部分
  • 全部的查询返回字段在同一个索引中

因为全部出如今查询中的字段是索引的一部分, MongoDB 无需在整个数据文档中检索匹配查询条件和返回使用相同索引的查询结果。

由于索引存在于RAM中,从索引中获取数据比经过扫描文档读取数据要快得多。


使用覆盖索引查询

为了测试盖索引查询,使用如下 users 集合:

{
   "_id": ObjectId("53402597d852426020000002"),
   "contact": "987654321",
   "dob": "01-01-1991",
   "gender": "M",
   "name": "Tom Benzamin",
   "user_name": "tombenzamin"
}

咱们在 users 集合中建立联合索引,字段为 gender 和 user_name :

>db.users.ensureIndex({gender:1,user_name:1})

如今,该索引会覆盖如下查询:

>db.users.find({gender:"M"},{user_name:1,_id:0})

也就是说,对于上述查询,MongoDB的不会去数据库文件中查找。相反,它会从索引中提取数据,这是很是快速的数据查询。

因为咱们的索引中不包括 _id 字段,_id在查询中会默认返回,咱们能够在MongoDB的查询结果集中排除它。

下面的实例没有排除_id,查询就不会被覆盖:

>db.users.find({gender:"M"},{user_name:1})

最后,若是是如下的查询,不能使用覆盖索引查询:

      • 全部索引字段是一个数组
      • 全部索引字段是一个子文档

 

MongoDB 查询分析

MongoDB 查询分析能够确保咱们建议的索引是否有效,是查询语句性能分析的重要工具。

MongoDB 查询分析经常使用函数有:explain() 和 hint()。


使用 explain()

explain 操做提供了查询信息,使用索引及查询统计等。有利于咱们对索引的优化。

接下来咱们在 users 集合中建立 gender 和 user_name 的索引:

>db.users.ensureIndex({gender:1,user_name:1})
</p>
<p>如今在查询语句中使用 explain :</p>
<pre>
>db.users.find({gender:"M"},{user_name:1,_id:0}).explain()

以上的 explain() 查询返回以下结果:

{
   "cursor" : "BtreeCursor gender_1_user_name_1",
   "isMultiKey" : false,
   "n" : 1,
   "nscannedObjects" : 0,
   "nscanned" : 1,
   "nscannedObjectsAllPlans" : 0,
   "nscannedAllPlans" : 1,
   "scanAndOrder" : false,
   "indexOnly" : true,
   "nYields" : 0,
   "nChunkSkips" : 0,
   "millis" : 0,
   "indexBounds" : {
      "gender" : [
         [
            "M",
            "M"
         ]
      ],
      "user_name" : [
         [
            {
               "$minElement" : 1
            },
            {
               "$maxElement" : 1
            }
         ]
      ]
   }
}

如今,咱们看看这个结果集的字段:

        • indexOnly: 字段为 true ,表示咱们使用了索引。
        • cursor:由于这个查询使用了索引,MongoDB 中索引存储在B树结构中,因此这是也使用了 BtreeCursor 类型的游标。若是没有使用索引,游标的类型是 BasicCursor。这个键还会给出你所使用的索引的名称,你经过这个名称能够查看当前数据库下的system.indexes集合(系统自动建立,因为存储索引信息,这个稍微会提到)来获得索引的详细信息。
        • n:当前查询返回的文档数量。
        • nscanned/nscannedObjects:代表当前此次查询一共扫描了集合中多少个文档,咱们的目的是,让这个数值和返回文档的数量越接近越好。
        • millis:当前查询所需时间,毫秒数。
        • indexBounds:当前查询具体使用的索引。

使用 hint()

虽然MongoDB查询优化器通常工做的很不错,可是也可使用 hint 来强制 MongoDB 使用一个指定的索引。

这种方法某些情形下会提高性能。 一个有索引的 collection 而且执行一个多字段的查询(一些字段已经索引了)。

以下查询实例指定了使用 gender 和 user_name 索引字段来查询:

>db.users.find({gender:"M"},{user_name:1,_id:0}).hint({gender:1,user_name:1})

可使用 explain() 函数来分析以上查询:

>db.users.find({gender:"M"},{user_name:1,_id:0}).hint({gender:1,user_name:1}).explain()

MongoDB 原子操做

mongodb不支持事务,因此,在你的项目中应用时,要注意这点。不管什么设计,都不要要求mongodb保证数据的完整性。

可是mongodb提供了许多原子操做,好比文档的保存,修改,删除等,都是原子操做。

所谓原子操做就是要么这个文档保存到Mongodb,要么没有保存到Mongodb,不会出现查询到的文档没有保存完整的状况。


原子操做数据模型

考虑下面的例子,图书馆的书籍及结帐信息。

实例说明了在一个相同的文档中如何确保嵌入字段关联原子操做(update:更新)的字段是同步的。

book = { _id: 123456789, title: "MongoDB: The Definitive Guide", author: [ "Kristina Chodorow", "Mike Dirolf" ], published_date: ISODate("2010-09-24"), pages: 216, language: "English", publisher_id: "oreilly", available: 3, checkout: [ { by: "joe", date: ISODate("2012-10-15") } ] }

你可使用 db.collection.findAndModify() 方法来判断书籍是否可结算并更新新的结算信息。

在同一个文档中嵌入的 available 和 checkout 字段来确保这些字段是同步更新的:

db.books.findAndModify ( { query: { _id: 123456789, available: { $gt: 0 } }, update: { $inc: { available: -1 }, $push: { checkout: { by: "abc", date: new Date() } } } } )

原子操做经常使用命令

$set

用来指定一个键并更新键值,若键不存在并建立。

{ $set : { field : value } }

$unset

用来删除一个键。

{ $unset : { field : 1} }

$inc

$inc能够对文档的某个值为数字型(只能为知足要求的数字)的键进行增减的操做。

{ $inc : { field : value } }

$push

用法:

{ $push : { field : value } }

把value追加到field里面去,field必定要是数组类型才行,若是field不存在,会新增一个数组类型加进去。

$pushAll

同$push,只是一次能够追加多个值到一个数组字段内。

{ $pushAll : { field : value_array } }

$pull

从数组field内删除一个等于value值。

{ $pull : { field : _value } }

$addToSet

增长一个值到数组内,并且只有当这个值不在数组内才增长。

$pop

删除数组的第一个或最后一个元素

{ $pop : { field : 1 } }

$rename

修改字段名称

{ $rename : { old_field_name : new_field_name } }

$bit

位操做,integer类型

{$bit : { field : {and : 5}}}

偏移操做符

> t.find() { "_id" : ObjectId("4b97e62bf1d8c7152c9ccb74"), "title" : "ABC", "comments" : [ { "by" : "joe", "votes" : 3 }, { "by" : "jane", "votes" : 7 } ] } > t.update( {'comments.by':'joe'}, {$inc:{'comments.$.votes':1}}, false, true ) > t.find() { "_id" : ObjectId("4b97e62bf1d8c7152c9ccb74"), "title" : "ABC", "comments" : [ { "by" : "joe", "votes" : 4 }, { "by" : "jane", "votes" : 7 } ] }


MongoDB 高级索引

考虑如下文档集合(users ):

{
   "address": {
      "city": "Los Angeles",
      "state": "California",
      "pincode": "123"
   },
   "tags": [
      "music",
      "cricket",
      "blogs"
   ],
   "name": "Tom Benzamin"
}

以上文档包含了 address 子文档和 tags 数组。


索引数组字段

假设咱们基于标签来检索用户,为此咱们须要对集合中的数组 tags 创建索引。

在数组中建立索引,须要对数组中的每一个字段依次创建索引。因此在咱们为数组 tags 建立索引时,会为 music、cricket、blogs三个值创建单独的索引。

使用如下命令建立数组索引:

>db.users.ensureIndex({"tags":1})

建立索引后,咱们能够这样检索集合的 tags 字段:

>db.users.find({tags:"cricket"})

为了验证咱们使用使用了索引,可使用 explain 命令:

>db.users.find({tags:"cricket"}).explain()

以上命令执行结果中会显示 "cursor" : "BtreeCursor tags_1" ,则表示已经使用了索引。


索引子文档字段

假设咱们须要经过city、state、pincode字段来检索文档,因为这些字段是子文档的字段,因此咱们须要对子文档创建索引。

为子文档的三个字段建立索引,命令以下:

>db.users.ensureIndex({"address.city":1,"address.state":1,"address.pincode":1})

一旦建立索引,咱们可使用子文档的字段来检索数据:

>db.users.find({"address.city":"Los Angeles"})   

记住查询表达式必须遵循指定的索引的顺序。因此上面建立的索引将支持如下查询:

>db.users.find({"address.city":"Los Angeles","address.state":"California"}) 

一样支持如下查询:

>db.users.find({"address.city":"LosAngeles","address.state":"California","address.pincode":"123"})


MongoDB 索引限制


额外开销

每一个索引占据必定的存储空间,在进行插入,更新和删除操做时也须要对索引进行操做。因此,若是你不多对集合进行读取操做,建议不使用索引。


内存(RAM)使用

因为索引是存储在内存(RAM)中,你应该确保该索引的大小不超过内存的限制。

若是索引的大小大于内存的限制,MongoDB会删除一些索引,这将致使性能降低。


查询限制

索引不能被如下的查询使用:

  • 正则表达式及非操做符,如 $nin, $not, 等。
  • 算术运算符,如 $mod, 等。
  • $where 子句

因此,检测你的语句是否使用索引是一个好的习惯,能够用explain来查看。


索引键限制

从2.6版本开始,若是现有的索引字段的值超过索引键的限制,MongoDB中不会建立索引。


插入文档超过索引键限制

若是文档的索引字段值超过了索引键的限制,MongoDB不会将任何文档转换成索引的集合。与mongorestore和mongoimport工具相似。


最大范围

  • 集合中索引不能超过64个
  • 索引名的长度不能超过128个字符
  • 一个复合索引最多能够有31个字段

 

MongoDB ObjectId


在前面几个章节中咱们已经使用了MongoDB 的对象 Id(ObjectId)。

在本章节中,咱们将了解的ObjectId的结构。

ObjectId 是一个12字节 BSON 类型数据,有如下格式:

  • 前4个字节表示时间戳
  • 接下来的3个字节是机器标识码
  • 紧接的两个字节由进程id组成(PID)
  • 最后三个字节是随机数。

MongoDB中存储的文档必须有一个"_id"键。这个键的值能够是任何类型的,默认是个ObjectId对象。

在一个集合里面,每一个文档都有惟一的"_id"值,来确保集合里面每一个文档都能被惟一标识。

MongoDB采用ObjectId,而不是其余比较常规的作法(好比自动增长的主键)的主要缘由,由于在多个 服务器上同步自动增长主键值既费力还费时。


建立新的ObjectId

使用如下代码生成新的ObjectId:

>newObjectId = ObjectId()

上面的语句返回如下惟一辈子成的id:

ObjectId("5349b4ddd2781d08c09890f3")

你也可使用生成的id来取代MongoDB自动生成的ObjectId:

>myObjectId = ObjectId("5349b4ddd2781d08c09890f4")

建立文档的时间戳

因为 ObjectId 中存储了 4 个字节的时间戳,因此你不须要为你的文档保存时间戳字段,你能够经过 getTimestamp 函数来获取文档的建立时间:

>ObjectId("5349b4ddd2781d08c09890f4").getTimestamp()

以上代码将返回 ISO 格式的文档建立时间:

ISODate("2014-04-12T21:49:17Z")

ObjectId 转换为字符串

在某些状况下,您可能须要将ObjectId转换为字符串格式。你可使用下面的代码:

>new ObjectId().str

以上代码将返回Guid格式的字符串::

5349b4ddd2781d08c09890f3

MongoDB Map Reduce

Map-Reduce是一种计算模型,简单的说就是将大批量的工做(数据)分解(MAP)执行,而后再将结果合并成最终结果(REDUCE)。

MongoDB提供的Map-Reduce很是灵活,对于大规模数据分析也至关实用。

MapReduce 命令

如下是MapReduce的基本语法:

>db.collection.mapReduce(
   function() {emit(key,value);},  //map 函数
   function(key,values) {return reduceFunction},   //reduce 函数
   {
      out: collection,
      query: document,
      sort: document,
      limit: number
   }
)

使用 MapReduce 要实现两个函数 Map 函数和 Reduce 函数,Map 函数调用 emit(key, value), 遍历 collection 中全部的记录, 将 key 与 value 传递给 Reduce 函数进行处理。

Map 函数必须调用 emit(key, value) 返回键值对。

参数说明:

  • map :映射函数 (生成键值对序列,做为 reduce 函数参数)。
  • reduce 统计函数,reduce函数的任务就是将key-values变成key-value,也就是把values数组变成一个单一的值value。。
  • out 统计结果存放集合 (不指定则使用临时集合,在客户端断开后自动删除)。
  • query 一个筛选条件,只有知足条件的文档才会调用map函数。(query。limit,sort能够随意组合)
  • sort 和limit结合的sort排序参数(也是在发往map函数前给文档排序),能够优化分组机制
  • limit 发往map函数的文档数量的上限(要是没有limit,单独使用sort的用处不大)

如下实例在集合 orders 中查找 status:"A" 的数据,并根据 cust_id 来分组,并计算 amount 的总和。


使用 MapReduce

考虑如下文档结构存储用户的文章,文档存储了用户的 user_name 和文章的 status 字段:

>db.posts.insert({
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "mark",
   "status":"active"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "mark",
   "status":"active"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "mark",
   "status":"active"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "mark",
   "status":"active"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "mark",
   "status":"disabled"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "runoob",
   "status":"disabled"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "runoob",
   "status":"disabled"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "runoob",
   "status":"active"
})
WriteResult({ "nInserted" : 1 })

如今,咱们将在 posts 集合中使用 mapReduce 函数来选取已发布的文章(status:"active"),并经过user_name分组,计算每一个用户的文章数:

>db.posts.mapReduce( 
   function() { emit(this.user_name,1); }, 
   function(key, values) {return Array.sum(values)}, 
      {  
         query:{status:"active"},  
         out:"post_total" 
      }
)

以上 mapReduce 输出结果为:

{
        "result" : "post_total",
        "timeMillis" : 23,
        "counts" : {
                "input" : 5,
                "emit" : 5,
                "reduce" : 1,
                "output" : 2
        },
        "ok" : 1
}

结果代表,共有 5 个符合查询条件(status:"active")的文档, 在map函数中生成了 5 个键值对文档,最后使用reduce函数将相同的键值分为 2 组。

 

具体参数说明:

  • result:储存结果的collection的名字,这是个临时集合,MapReduce的链接关闭后自动就被删除了。
  • timeMillis:执行花费的时间,毫秒为单位
  • input:知足条件被发送到map函数的文档个数
  • emit:在map函数中emit被调用的次数,也就是全部集合中的数据总量
  • ouput:结果集合中的文档个数(count对调试很是有帮助)
  • ok:是否成功,成功为1
  • err:若是失败,这里能够有失败缘由,不过从经验上来看,缘由比较模糊,做用不大

使用 find 操做符来查看 mapReduce 的查询结果:

>db.posts.mapReduce( 
   function() { emit(this.user_name,1); }, 
   function(key, values) {return Array.sum(values)}, 
      {  
         query:{status:"active"},  
         out:"post_total" 
      }
).find()

以上查询显示以下结果,两个用户 tom 和 mark 有两个发布的文章:

{ "_id" : "mark", "value" : 4 }
{ "_id" : "runoob", "value" : 1 }

用相似的方式,MapReduce能够被用来构建大型复杂的聚合查询。

Map函数和Reduce函数可使用 JavaScript 来实现,使得MapReduce的使用很是灵活和强大。

 

MongoDB 全文检索

全文检索对每个词创建一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先创建的索引进行查找,并将查找的结果反馈给用户的检索方式。

这个过程相似于经过字典中的检索字表查字的过程。

MongoDB 从 2.4 版本开始支持全文检索,目前支持15种语言(暂时不支持中文)的全文索引。

  • danish
  • dutch
  • english
  • finnish
  • french
  • german
  • hungarian
  • italian
  • norwegian
  • portuguese
  • romanian
  • russian
  • spanish
  • swedish
  • turkish

启用全文检索

MongoDB 在 2.6 版本之后是默认开启全文检索的,若是你使用以前的版本,你须要使用如下代码来启用全文检索:

>db.adminCommand({setParameter:true,textSearchEnabled:true})

或者使用命令:

mongod --setParameter textSearchEnabled=true

建立全文索引

考虑如下 posts 集合的文档数据,包含了文章内容(post_text)及标签(tags):

{
   "post_text": "enjoy the mongodb articles on Runoob",
   "tags": [
      "mongodb",
      "runoob"
   ]
}

咱们能够对 post_text 字段创建全文索引,这样咱们能够搜索文章内的内容:

>db.posts.ensureIndex({post_text:"text"})

使用全文索引

如今咱们已经对 post_text 创建了全文索引,咱们能够搜索文章中的关键词 runoob:

>db.posts.find({$text:{$search:"runoob"}})

如下命令返回了以下包含 runoob 关键词的文档数据:

{ 
   "_id" : ObjectId("53493d14d852429c10000002"), 
   "post_text" : "enjoy the mongodb articles on Runoob", 
   "tags" : [ "mongodb", "runoob" ]
}

若是你使用的是旧版本的 MongoDB,你可使用如下命令:

>db.posts.runCommand("text",{search:"runoob"})

使用全文索引能够提升搜索效率。


删除全文索引

删除已存在的全文索引,可使用 find 命令查找索引名:

>db.posts.getIndexes()

经过以上命令获取索引名,本例的索引名为post_text_text,执行如下命令来删除索引:

>db.posts.dropIndex("post_text_text")


MongoDB 正则表达式

正则表达式是使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。

许多程序设计语言都支持利用正则表达式进行字符串操做。

MongoDB 使用 $regex 操做符来设置匹配字符串的正则表达式。

MongoDB使用PCRE (Perl Compatible Regular Expression) 做为正则表达式语言。

不一样于全文检索,咱们使用正则表达式不须要作任何配置。

考虑如下 posts 集合的文档结构,该文档包含了文章内容和标签:

{
   "post_text": "enjoy the mongodb articles on runoob",
   "tags": [
      "mongodb",
      "runoob"
   ]
}

使用正则表达式

如下命令使用正则表达式查找包含 runoob 字符串的文章:

>db.posts.find({post_text:{$regex:"runoob"}})

以上查询也能够写为:

>db.posts.find({post_text:/runoob/})

不区分大小写的正则表达式

若是检索须要不区分大小写,咱们能够设置 $options 为 $i。

如下命令将查找不区分大小写的字符串 runoob:

>db.posts.find({post_text:{$regex:"runoob",$options:"$i"}})

集合中会返回全部包含字符串 runoob 的数据,且不区分大小写:

{
   "_id" : ObjectId("53493d37d852429c10000004"),
   "post_text" : "hey! this is my post on  runoob", 
   "tags" : [ "runoob" ]
} 

数组元素使用正则表达式

咱们还能够在数组字段中使用正则表达式来查找内容。 这在标签的实现上很是有用,若是你须要查找包含以 run 开头的标签数据(ru 或 run 或 runoob), 你可使用如下代码:

>db.posts.find({tags:{$regex:"run"}})

优化正则表达式查询

  • 若是你的文档中字段设置了索引,那么使用索引相比于正则表达式匹配查找全部的数据查询速度更快。

     

  • 若是正则表达式是前缀表达式,全部匹配的数据将以指定的前缀字符串为开始。例如: 若是正则表达式为 ^tut ,查询语句将查找以 tut 为开头的字符串。

这里面使用正则表达式有两点须要注意:

正则表达式中使用变量。必定要使用eval将组合的字符串进行转换,不能直接将字符串拼接后传入给表达式。不然没有报错信息,只是结果为空!实例以下:

var name=eval("/" + 变量值key +"/i"); 

如下是模糊查询包含title关键词, 且不区分大小写:

title:eval("/"+title+"/i")    // 等同于 title:{$regex:title,$Option:"$i"}   


MongoDB 管理工具: Rockmongo

RockMongo是PHP5写的一个MongoDB管理工具。

经过 Rockmongo 你能够管理 MongoDB服务,数据库,集合,文档,索引等等。

它提供了很是人性化的操做。相似 phpMyAdmin(PHP开发的MySql管理工具)。

Rockmongo 下载地址:http://rockmongo.com/downloads Rockmongo 管理工具


简介

主要特征:

  • 使用宽松的New BSD License协议
  • 速度快,安装简单
  • 支持多语言(目前提供中文、英文、日文、巴西葡萄牙语、法语、德语、俄语、意大利语)
  • 系统
    • 能够配置多个主机,每一个主机能够有多个管理员
    • 须要管理员密码才能登入操做,确保数据库的安全性
  • 服务器
    • 服务器信息 (WEB服务器, PHP, PHP.ini相关指令 ...)
    • 状态
    • 数据库信息
  • 数据库
    • 查询,建立和删除
    • 执行命令和Javascript代码
    • 统计信息
  • 集合(至关于表)
    • 强大的查询工具
    • 读数据,写数据,更改数据,复制数据,删除数据
    • 查询、建立和删除索引
    • 清空数据
    • 批量删除和更改数据
    • 统计信息
  • GridFS
    • 查看分块
    • 下载文件

安装

需求

  • 一个能运行PHP的Web服务器,好比Apache Httpd, Nginx ...
  • PHP - 须要PHP v5.1.6或更高版本,须要支持SESSION
    • 为了能链接MongoDB,你须要安装php_mongo扩展

快速安装

  • 下载安装包
  • 解压到你的网站目录下
  • 用编辑器打开config.php,修改host, port, admins等参数
  • 在浏览器中访问index.php,好比说:http://localhost/rockmongo/index.php
  • 使用用户名和密码登陆,默认为"admin"和"admin"
  • 开始玩转MongoDB!

参考文章:http://rockmongo.com/wiki/introduction?lang=zh_cn

 

MongoDB GridFS

GridFS 用于存储和恢复那些超过16M(BSON文件限制)的文件(如:图片、音频、视频等)。

GridFS 也是文件存储的一种方式,可是它是存储在MonoDB的集合中。

GridFS 能够更好的存储大于16M的文件。

GridFS 会将大文件对象分割成多个小的chunk(文件片断),通常为256k/个,每一个chunk将做为MongoDB的一个文档(document)被存储在chunks集合中。

 

GridFS 用两个集合来存储一个文件:fs.files与fs.chunks。

每一个文件的实际内容被存在chunks(二进制数据)中,和文件有关的meta数据(filename,content_type,还有用户自定义的属性)将会被存在files集合中。

如下是简单的 fs.files 集合文档:

{
   "filename": "test.txt",
   "chunkSize": NumberInt(261120),
   "uploadDate": ISODate("2014-04-13T11:32:33.557Z"),
   "md5": "7b762939321e146569b07f72c62cca4f",
   "length": NumberInt(646)
}

如下是简单的 fs.chunks 集合文档:

{
   "files_id": ObjectId("534a75d19f54bfec8a2fe44b"),
   "n": NumberInt(0),
   "data": "Mongo Binary Data"
}

GridFS 添加文件

如今咱们使用 GridFS 的 put 命令来存储 mp3 文件。 调用 MongoDB 安装目录下bin的 mongofiles.exe工具。

打开命令提示符,进入到MongoDB的安装目录的bin目录中,找到mongofiles.exe,并输入下面的代码:

>mongofiles.exe -d gridfs put song.mp3

GridFS 是存储文件的数据名称。若是不存在该数据库,MongoDB会自动建立。Song.mp3 是音频文件名。

 

使用如下命令来查看数据库中文件的文档:

>db.fs.files.find()

以上命令执行后返回如下文档数据:

{
   _id: ObjectId('534a811bf8b4aa4d33fdf94d'), 
   filename: "song.mp3", 
   chunkSize: 261120, 
   uploadDate: new Date(1397391643474), md5: "e4f53379c909f7bed2e9d631e15c1c41",
   length: 10401959 
}

咱们能够看到 fs.chunks 集合中全部的区块,如下咱们获得了文件的 _id 值,咱们能够根据这个 _id 获取区块(chunk)的数据:

>db.fs.chunks.find({files_id:ObjectId('534a811bf8b4aa4d33fdf94d')})

以上实例中,查询返回了 40 个文档的数据,意味着mp3文件被存储在40个区块中。

 

MongoDB 固定集合(Capped Collections)

MongoDB 固定集合(Capped Collections)是性能出色且有着固定大小的集合,对于大小固定,咱们能够想象其就像一个环形队列,当集合空间用完后,再插入的元素就会覆盖最初始的头部的元素!


建立固定集合

咱们经过createCollection来建立一个固定集合,且capped选项设置为true:

>db.createCollection("cappedLogCollection",{capped:true,size:10000})

还能够指定文档个数,加上max:1000属性:

>db.createCollection("cappedLogCollection",{capped:true,size:10000,max:1000})

判断集合是否为固定集合:

>db.cappedLogCollection.isCapped()

若是须要将已存在的集合转换为固定集合可使用如下命令:

>db.runCommand({"convertToCapped":"posts",size:10000})

以上代码将咱们已存在的 posts 集合转换为固定集合。


固定集合查询

固定集合文档按照插入顺序储存的,默认状况下查询就是按照插入顺序返回的,也可使用$natural调整返回顺序。

>db.cappedLogCollection.find().sort({$natural:-1})

固定集合的功能特色

能够插入及更新,但更新不能超出collection的大小,不然更新失败,不容许删除,可是能够调用drop()删除集合中的全部行,可是drop后须要显式地重建集合。

在32位机子上一个cappped collection的最大值约为482.5M,64位上只受系统文件大小的限制。


固定集合属性及用法

属性

  • 属性1:对固定集合进行插入速度极快
  • 属性2:按照插入顺序的查询输出速度极快
  • 属性3:可以在插入最新数据时,淘汰最先的数据

用法

  • 用法1:储存日志信息
  • 用法2:缓存一些少许的文档

MongoDB 自动增加

MongoDB 没有像 SQL 同样有自动增加的功能, MongoDB 的 _id 是系统自动生成的12字节惟一标识。

但在某些状况下,咱们可能须要实现 ObjectId 自动增加功能。

因为 MongoDB 没有实现这个功能,咱们能够经过编程的方式来实现,如下咱们将在 counters 集合中实现_id字段自动增加。


使用 counters 集合

考虑如下 products 文档。咱们但愿 _id 字段实现 从 1,2,3,4 到 n 的自动增加功能。

{
  "_id":1,
  "product_name": "Apple iPhone",
  "category": "mobiles"
}

为此,建立 counters 集合,序列字段值能够实现自动长:

>db.createCollection("counters")

如今咱们向 counters 集合中插入如下文档,使用 productid 做为 key:

{
  "_id":"productid",
  "sequence_value": 0
}

sequence_value 字段是序列经过自动增加后的一个值。

使用如下命令插入 counters 集合的序列文档中:

>db.counters.insert({_id:"productid",sequence_value:0})

建立 Javascript 函数

如今,咱们建立函数 getNextSequenceValue 来做为序列名的输入, 指定的序列会自动增加 1 并返回最新序列值。在本文的实例中序列名为 productid 。

>function getNextSequenceValue(sequenceName){
   var sequenceDocument = db.counters.findAndModify(
      {
         query:{_id: sequenceName },
         update: {$inc:{sequence_value:1}},
         new:true
      });
   return sequenceDocument.sequence_value;
}

使用 Javascript 函数

接下来咱们将使用 getNextSequenceValue 函数建立一个新的文档, 并设置文档 _id 自动为返回的序列值:

>db.products.insert({
   "_id":getNextSequenceValue("productid"),
   "product_name":"Apple iPhone",
   "category":"mobiles"})

>db.products.insert({
   "_id":getNextSequenceValue("productid"),
   "product_name":"Samsung S3",
   "category":"mobiles"})

就如你所看到的,咱们使用 getNextSequenceValue 函数来设置 _id 字段。

为了验证函数是否有效,咱们可使用如下命令读取文档:

>db.products.find()

以上命令将返回如下结果,咱们发现 _id 字段是自增加的:

{ "_id" : 1, "product_name" : "Apple iPhone", "category" : "mobiles"}

{ "_id" : 2, "product_name" : "Samsung S3", "category" : "mobiles" }
相关文章
相关标签/搜索