参考资料:
《MongoDB:The Definitive Guide》 http://book.douban.com/subject/4746684/
《MongoDB官方文档》 http://www.mongodb.org/display/DOCS/Home
什么是MongoDB:
MongoDB是10gen公司开发的一个NoSQL产品,它的主要特点是:
- 非关系式,面向文档形的数据库(document-oriented database)。
- schema-free——无需为数据库建模,可以随时改变数据的形式。
- JSON-like文档——可以用类似JavaScript的方式操作文档对象。
- MapReduce——支持大规模数据聚合(Aggregation)。
- 速度非常快——索引支持,内存映射引擎(memory-mapped engine),查询动态优化。
- 易于扩展——允许自动划分数据到多台服务器,动态扩展。
实际应用:
MongoDB已经用在很多实际的生产环境当中了,比如以下
更多案例可以看 http://www.mongodb.org/display/DOCS/Production+Deployments
安装:
访问 http://www.mongodb.org/display/DOCS/Quickstart
选择你的操作系统来安装MongoDB
MongoDB安装完成之后,默认会有一个MongoShell可供交互使用
huangz@pad:~$ mongo MongoDB shell version: 1.4.4 url: test connecting to: test type "exit" to exit type "help" for help >
可以使用JavaScript的语法来在MongoShell中进行操作。
使用help可以查看常用命令
> help HELP show dbs show database names show collections show collections in current database show users show users in current database show profile show most recent system.profile entries with time >= 1ms use <db name> set curent database to <db name> db.help() help on DB methods db.foo.help() help on collection methods db.foo.find() list objects in collection foo db.foo.find( { a : 1 } ) list objects in foo where a == 1 it result of the last line evaluated; use to further iterate
数据:文档、集合和数据库
文档(document)是MongoDB中最基本的数据组织形式,每个文档以Key-Value(键-值对)的方式组织起来。
{"greeting" : "Hello World!"}
一个文档可以有多个Key-Value组合,每个Value可以是不同的类型,比如String、Integer、List等等。
{ "name" : "huangz", "sex" : "male", "age" : 20 }
Key的遵循以下规则:
- "\0"不能使用
- 带有"."号,"_"号和"$"号前缀的Key被保留
- 大小写有区别,Age不同于age
- 同一个文档不能有相同的Key
- 除了上面几条规则外,其他所有UTF-8字符都可以使用
将多个文档组织起来,就形成了集合(collection)。如果将文档比作关系数据库中的行(row)的话,那么集合就是数据库中的表(table)。
在关系数据库(如MySQL)中,在同一个数据库表里面,总是有相同的行(row),比如你有一个student表,里面有id,name,age,class,grade几个row,那么整个student只能有相同的几个行。
但是在MongoDB当中,你可以利用schema-free特性,在一个集合中,储存多个有不同Key、不同类型的文档,比如你可以在一个student集合里面,有如下格式的文档:
{ "name" : "huangz", "age" : 20, "sex" : "male" } { "name" : "jack", "class" : 3, "grade" : 3 }
在这个student集合里面,并不要求每个文档都要有同样的Key和同样的类型,一切随意。
集合的命名规则和文档的命名规则大概相似,另外要记住的是
- system集合是被保留的
另外,“.”号的使用在集合当中是允许的,它们被成为子集合(Subcollection);比如你有一个blog集合,你可以使用blog.title,blog.content或者blog.author来帮组你更好地组织集合。
将多个集合组织起来,就形成了数据库(database)。单个MongoDB实例可以使用多个数据库,每个数据库都是独立运作的,可以有单独的权限,每个数据库的数据被分开保存在不同的文件里。
数据库也有命名规则:
- 不能使用空字符""或者空格" "、点号"."、美元符"$"、斜线"/"、反斜线"\"和"\0"
- 只能用小写
- 必须少于64个字节(byte)
另外, 在MongoDB中(不包括GridFS),单个文档大小不得超过4mb(版本>=1.7则是16MB)。
MongoDB不对数据做任何处理(它只是单纯的把文档保存起来),所以你可以淡定面对SQL Injection之类的攻击。
数据类型:
上面说过,MongoDB的基本数据是以key-value为单位的,key只能是字符串,但value的数据类型则多种多样,这些类型基本从BSON格式的基础上进行一些添加和修改而来,以下是一些常见类型。
/* 空值 null */ { "name" : null } /* 布尔值 boolean */ { "flag" : true } /* 数值 包括32bit、64bit整数integer类型和64bit浮点floating point类型 */ { "copies" : 300, "price" : 60.8 } /* 字符串 string */ { "dbname" : "MongoDB" } /* 日期 date */ { "post_time" : new Date() } /* 正则表达式 regular expression */ { "name_match" : /huangz/i } /* 数组类型 array */ { "tags" : ["mongodb", "nosql"] } /* 嵌套文档 embedded document 一个文档里面Key的值可以是另外一个文档 */ { "name" : "huangz", "phone" : { "home" : 123321, "mobile" : 15820123123} } /* id 每个MongoDB文档都必须有一个叫作"_id"的Key, 同一集合里面的文档_id必须各不相同。 id可以在创建文档的时候指定,可以是任何类型,无指定时,默认使用ObjectId对象,自动生成不同的id。 ObjectId对象生成id(12位)的规则如下: 0-3 : Timestamp,时间撮 4-6 : Machine,机器代码 7-8 : PID,MongoDB的程序id 9-11: Increment,一个自增量 */ { "_id" : ObjectId("4da025ac5149e6d915098c59"), "name" : "huangz", "phone" : { "home" : 33123123, "mobile" : 15820123123 } }
不必担心自动生成id会重复,因为它足够大。
如果想模仿关系数据库,生成一个连续自增的id,可以使用类似如下的代码来实现:
/* 生成连续自增id */ > db.person.id = 0 0 > db.person.insert({ ... "_id" : db.person.id += 1, ... "name" : "huangz" ... }) > db.person.insert({ ... "_id" : db.person.id += 1, ... "name" : "jack", ... }) > db.person.find() { "_id" : 1, "name" : "huangz" } { "_id" : 2, "name" : "jack" } > db.person.find({"_id" : 1}) { "_id" : 1, "name" : "huangz" }
基本语法:
了解MongoDB的基本数据类型和组织方式后,我们可以测试以下常见的CRUD操作。
/* 选择数据库和集合,这里使用test数据库 */ > use test switched to db test /* CREATE 创建一个post文档,并将post添加到test.blog集合里面 */ > post = { ... "title" : "First day with MongoDB", ... "author" : "huangz", ... "content" : "Today, i try MongoDB, it's great!", ... "date" : new Date() ... } { "title" : "First day with MongoDB", "author" : "huangz", "content" : "Today, i try MongoDB, it's great!", "date" : "Sat Apr 09 2011 16:21:51 GMT+0800 (CST)" } > db.blog.insert(post) /*这里的db是一个指向test数据库的变量 */ /* READ 查询创立的post文档,MongoDB提供了包括find和findOne等多种查询方式 find函数的参数如果为空字典,则是查询整个集合,显示最先20条记录(record), find函数也可以使用参数,作为查询条件, findOne函数和find类似,但是只返回符合条件的第一条记录 */ > db.blog.find() { "_id" : ObjectId("4da017c55149e6d915098c57"), "title" : "First day with MongoDB", "author" : "huangz", "content" : "Today, i try MongoDB, it's great!", "date" : "Sat Apr 09 2011 16:24:26 GMT+0800 (CST)" } > db.blog.find({"author" : "huangz"}) { "_id" : ObjectId("4da0177a0fcb4b53ae3ba9fc"), "title" : "First day with MongoDB", "author" : "huangz", "content" : "Today, i try MongoDB, it's great!", "date" : "Sat Apr 09 2011 16:21:51 GMT+0800 (CST)" } > db.blog.findOne() { "_id" : ObjectId("4da0177a0fcb4b53ae3ba9fc"), "title" : "First day with MongoDB", "author" : "huangz", "content" : "Today, i try MongoDB, it's great!", "date" : "Sat Apr 09 2011 16:21:51 GMT+0800 (CST)" } /* UPDATE 更新post记录,为它增加一个Key名字为comments的列表。*/ > post { "title" : "First day with MongoDB", "author" : "huangz", "content" : "Today, i try MongoDB, it's great!", "date" : "Sat Apr 09 2011 16:24:26 GMT+0800 (CST)" } > post.comments = [] [ ] > db.blog.update({"title" : "First day with MongoDB"}, post) > post { "title" : "First day with MongoDB", "author" : "huangz", "content" : "Today, i try MongoDB, it's great!", "date" : "Sat Apr 09 2011 16:24:26 GMT+0800 (CST)", "comments" : [ ] } /* DELETE 删除post,下面两种方法等效 */ db.blog.remove(post) db.blog.remove({"title" : "First day with MongoDB"})
深入查询:
1.条件查询
除了使用
db.collection.find()
查询集合里所有文档外,我们可以指定特定的查询条件,比如以下语句只查询age为20的人。
> db.user.find({"age": 20}) { "_id" : ObjectId("4d7aff454dca002afb000000"), "age" : 20, "name" : "huangz" }
还可以使用多个查询条件,比如以下语句不但查询age为20的人,还查询name为huangz的人。
> db.user.find({"age": 20, "name" : "huangz"}) { "_id" : ObjectId("4d7aff454dca002afb000000"), "age" : 20, "name" : "huangz" }
还可以指定返回文档的数目
> db.user.find().limit(1) { "_id" : ObjectId("4da070040d03918e09fe7dac"), "name" : "huangz", "age" : 20, "favorite fruit" : "pear" }
以及跳过指定数目的文档
/* 所有文档 */ > db.user.find() { "_id" : ObjectId("4da070040d03918e09fe7dac"), "name" : "huangz", "age" : 20, "favorite fruit" : "pear" } { "_id" : ObjectId("4da070180d03918e09fe7dad"), "name" : "jack", "age" : 20, "favorite fruit" : "apple" } { "_id" : ObjectId("4da0702d0d03918e09fe7dae"), "name" : "peter", "age" : 30, "favorite fruit" : "banana" } /* 跳过第一个文档 */ > db.user.find().skip(1) { "_id" : ObjectId("4da070180d03918e09fe7dad"), "name" : "jack", "age" : 20, "favorite fruit" : "apple" } { "_id" : ObjectId("4da0702d0d03918e09fe7dae"), "name" : "peter", "age" : 30, "favorite fruit" : "banana" }
排序指定文档的key,国际惯例,1为升序,-1为降序。
/* 以name项的降序排列文档 */ > db.user.find().sort({"name": -1}) { "_id" : ObjectId("4da0702d0d03918e09fe7dae"), "name" : "peter", "age" : 30, "favorite fruit" : "banana" } { "_id" : ObjectId("4da070180d03918e09fe7dad"), "name" : "jack", "age" : 20, "favorite fruit" : "apple" } { "_id" : ObjectId("4da070040d03918e09fe7dac"), "name" : "huangz", "age" : 20, "favorite fruit" : "pear" }
2.指定返回项
如果你只是对文档的某个(或数个)key感兴趣,那么你可以在查询时,指定要返回的key(注意:"_id"项默认总是被返回的,除非你明确指定不返回"_id"项)。
/* 所有结果 */ > db.user.find() { "_id" : ObjectId("4da070040d03918e09fe7dac"), "name" : "huangz", "age" : 20, "favorite fruit" : "pear" } { "_id" : ObjectId("4da070180d03918e09fe7dad"), "name" : "jack", "age" : 20, "favorite fruit" : "apple" } { "_id" : ObjectId("4da0702d0d03918e09fe7dae"), "name" : "peter", "age" : 30, "favorite fruit" : "banana" } /* 只显示age为20岁的人所喜欢的水果,其他不显示。(比如一个水果贩子它只关心水果的受欢迎程度,不管你姓甚名谁。) */ > db.user.find({"age": 20}, {"favorite fruit": 1}) { "_id" : ObjectId("4da070040d03918e09fe7dac"), "favorite fruit" : "pear" } { "_id" : ObjectId("4da070180d03918e09fe7dad"), "favorite fruit" : "apple" } /* 除了favorite fruit项之外,都显示 */ > db.user.find({"age": 20}, {"favorite fruit": 0}) { "_id" : ObjectId("4da070040d03918e09fe7dac"), "name" : "huangz", "age" : 20 } { "_id" : ObjectId("4da070180d03918e09fe7dad"), "name" : "jack", "age" : 20 } /* 但是似乎没有办法同时指定显示和不显示。。。 > db.user.find({"age": 20}, {"favorite fruit": 0, "name": 1}) error: { "$err" : "You cannot currently mix including and excluding fields. Contact us if this is an issue." } */
3.条件查询(使用条件操作符)
可以使用MongoDB提供的查询条件操作符来进行查询,比如"$lt", "$lte", "$gt", "$gte"分别代表 "<", "<=", ">", ">="。
/* 只查询age <= 20的人 */ > db.user.find({"age": {"$lte" : 20}}) { "_id" : ObjectId("4da070040d03918e09fe7dac"), "name" : "huangz", "age" : 20, "favorite fruit" : "pear" } { "_id" : ObjectId("4da070180d03918e09fe7dad"), "name" : "jack", "age" : 20, "favorite fruit" : "apple" }
"$ne"操作符代表"!=",不等关系。
/* 只查询age != 20的人 */ > db.user.find({"age": {"$ne" : 20}}) { "_id" : ObjectId("4da0702d0d03918e09fe7dae"), "name" : "peter", "age" : 30, "favorite fruit" : "banana" }
"$in"操作符表示,只要符合条件数组中的任何一个,就能被选中。
/* 选中喜欢吃苹果apple或者香蕉banana的人 */ > db.user.find({"favorite fruit": {"$in" : ["apple", "banana"]}}) { "_id" : ObjectId("4da070180d03918e09fe7dad"), "name" : "jack", "age" : 20, "favorite fruit" : "apple" } { "_id" : ObjectId("4da0702d0d03918e09fe7dae"), "name" : "peter", "age" : 30, "favorite fruit" : "banana" }
"$nin"操作符表示,只要不符合条件数组的任何一个,就能被选中。
/* 选中不喜欢吃苹果或香蕉的任何一种的人 */ > db.user.find({"favorite fruit": {"$nin" : ["apple", "banana"]}}) { "_id" : ObjectId("4da070040d03918e09fe7dac"), "name" : "huangz", "age" : 20, "favorite fruit" : "pear" }
"$all"操作符表示,必须符合条件数组中的每一个,才能被选中。
/* 所有文档 */ > db.post.find() { "_id" : ObjectId("4da079ee78d367d0af2ded04"), "title" : "today, a new database release", "content" : "long long ago...", "tags" : [ "nosql", "mongodb", "database" ] } { "_id" : ObjectId("4da07aee78d367d0af2ded06"), "title" : "MySQL sucks", "content" : "i use MySQL 10 year ago, is a long time...", "tags" : [ "database", "mysql" ] } /* 只选中tags项符合["nosql", "mongodb", "database"]的文档(注意条件的顺序这里不重要) */ > db.post.find({"tags": {$all: ["nosql", "mongodb", "database"]}}) { "_id" : ObjectId("4da079ee78d367d0af2ded04"), "title" : "today, a new database release", "content" : "long long ago...", "tags" : [ "nosql", "mongodb", "database" ] }
"$not"操作符表示,对操作取反。
/* 只选中age不小于或等于20的人 */ > db.user.find({"age": {"$not": {"$lte": 20}}}) { "_id" : ObjectId("4da0702d0d03918e09fe7dae"), "name" : "peter", "age" : 30, "favorite fruit" : "banana" }
"$or"操作符表示,至少符合条件数组的其中一个,就能被选中。
/* 下面包含or的语句本该返回2个文档,但在我的测试中,1.8.1版本一个结果也不返回。。。真诡异 */ > db.user.find() { "_id" : ObjectId("4da070040d03918e09fe7dac"), "name" : "huangz", "age" : 20, "favorite fruit" : "pear" } { "_id" : ObjectId("4da070180d03918e09fe7dad"), "name" : "jack", "age" : 20, "favorite fruit" : "apple" } { "_id" : ObjectId("4da0702d0d03918e09fe7dae"), "name" : "peter", "age" : 30, "favorite fruit" : "banana" } /* WTF */ > db.user.find({"$or": [{"age": 20}, {"age": 21}]}) > /* 本该是 { "_id" : ObjectId("4da070040d03918e09fe7dac"), "name" : "huangz", "age" : 20, "favorite fruit" : "pear" } { "_id" : ObjectId("4da070180d03918e09fe7dad"), "name" : "jack", "age" : 20, "favorite fruit" : "apple" } */
"$and"操作符表示。。。嗯。。。MongoDB没有$and,你在写查询的时候提供多个条件就是了。
> db.user.find({"name":"huangz", "age": 20}) { "_id" : ObjectId("4da070040d03918e09fe7dac"), "name" : "huangz", "age" : 20, "favorite fruit" : "pear" }
/* 所有文档,分别有size为2和3的文档各一个 */ > db.post.find() { "_id" : ObjectId("4da079ee78d367d0af2ded04"), "title" : "today, a new database release", "content" : "long long ago...", "tags" : [ "nosql", "mongodb", "database" ] } { "_id" : ObjectId("4da07aee78d367d0af2ded06"), "title" : "MySQL sucks", "content" : "i use MySQL 10 year ago, is a long time...", "tags" : [ "database", "mysql" ] } /* 查找所有tags的size为3的文档 */ > db.post.find({"tags": {"$size": 3}}) { "_id" : ObjectId("4da079ee78d367d0af2ded04"), "title" : "today, a new database release", "content" : "long long ago...", "tags" : [ "nosql", "mongodb", "database" ] }
/* 返回所有post集合文档, 其中只返回大小为2的tags数组 */ > db.post.find({}, {"tags": {"$slice": 2}}) { "_id" : ObjectId("4da079ee78d367d0af2ded04"), "tags" : [ "nosql", "mongodb", "database" ] } { "_id" : ObjectId("4da07aee78d367d0af2ded06"), "tags" : [ "database", "mysql" ] } /* $slice操作怎么又错了,悲剧阿。。。 */
// Document 1 { "foo" : [ { "shape" : "square", "color" : "purple", "thick" : false }, { "shape" : "circle", "color" : "red", "thick" : true } ] } // Document 2 { "foo" : [ { "shape" : "square", "color" : "red", "thick" : true }, { "shape" : "circle", "color" : "purple", "thick" : false } ] } /* 失败查询 */ db.foo.find({"foo.shape": "square", "foo.color": "purple"}) db.foo.find({foo: {"shape": "square", "color": "purple"} } ) /* 正确方法 */ db.foo.find({foo: {"$elemMatch": {shape: "square", color: "purple"}}})
/* 翻页程序 */ var record_per_page = 50 var pages = db.post.find().sort({"date": -1}).limit(record_per_page) latest = loop_and_print_pages(pages) var pages = db.post.find({"date": {"$gt": latest.date}}).sort({"date": 1}).limit(record_per_page) latest = loop_and_print_pages(pages)
>db.user.find().skip(Math.floor(Math.random()*db.user.find().count())).limit(1) { "_id" : ObjectId("4da070040d03918e09fe7dac"), "name" : "huangz", "age" : 20, "favorite fruit" : "pear" } >db.user.find().skip(Math.floor(Math.random()*db.user.find().count())).limit(1) { "_id" : ObjectId("4da0702d0d03918e09fe7dae"), "name" : "peter", "age" : 30, "favorite fruit" : "banana" } >db.user.find().skip(Math.floor(Math.random()*db.user.find().count())).limit(1) { "_id" : ObjectId("4da070180d03918e09fe7dad"), "name" : "jack", "age" : 20, "favorite fruit" : "apple" } >db.user.find().skip(Math.floor(Math.random()*db.user.find().count())).limit(1) { "_id" : ObjectId("4da070180d03918e09fe7dad"), "name" : "jack", "age" : 20, "favorite fruit" : "apple" }
> db.user.find({"$query": {}, "$snapshot": true}) { "_id" : ObjectId("4da070040d03918e09fe7dac"), "name" : "huangz", "age" : 20, "favorite fruit" : "pear" } { "_id" : ObjectId("4da070180d03918e09fe7dad"), "name" : "jack", "age" : 20, "favorite fruit" : "apple" } { "_id" : ObjectId("4da0702d0d03918e09fe7dae"), "name" : "peter", "age" : 30, "favorite fruit" : "banana" }
/* 比如你经常要按升序查询名字,可以给名字(name)项加上索引 */ > db.user.ensureIndex({"name":1}) > db.user.find().sort({"name":1}) { "_id" : ObjectId("4da070040d03918e09fe7dac"), "name" : "huangz", "age" : 20, "favorite fruit" : "pear" } { "_id" : ObjectId("4da070180d03918e09fe7dad"), "name" : "jack", "age" : 20, "favorite fruit" : "apple" } { "_id" : ObjectId("4da0702d0d03918e09fe7dae"), "name" : "peter", "age" : 30, "favorite fruit" : "banana" }
/* 使用了索引的查询 */ > db.user.find({"name": "huangz", "age": 20}).explain() { "cursor" : "BtreeCursor name_1_age_1", // 索引的类型,索引成功一般为BasicCursor,name_1_age_1显示索引的名字 "indexBounds" : [ // 索引查询的条件限制 [ { "name" : "huangz", "age" : 20 }, { "name" : "huangz", "age" : 20 } ] ], "nscanned" : 1, // 扫描项目数量,项目可以是Object也可以是索引关键字 "nscannedObjects" : 1, // 扫描过的对象数量 "n" : 1, // 实际返回文档数量 "millis" : 0 // 查询使用时间 } /* 没有使用索引的查询 */ > db.post.find({"title": "feak title"}).explain() { "cursor" : "BasicCursor", "indexBounds" : [ ], "nscanned" : 2, "nscannedObjects" : 2, "n" : 0, "millis" : 0 }
/* 删除name项的升序索引 */ db.user.dropIndexes({"name": 1})
/* 比如你以下两个查询都可以被索引 */ db.user.ensureIndex({"name": 1, "age": 1}) db.user.find({"name": "huangz", "age": 20}) db.user.find({"age": 20, "name": "huangz"}) /* 以下两个就不一样了,只有第一个查询被索引,第二个查询没有用到索引 */ db.user.find().sort({"name": 1, "age": 1}) db.user.find().sort({"age": 1, "name": 1})
> db.user.find().count() 3 > db.user.find({"name":"huangz"}).count() 1
/* 用distinct计算出,集合里只有20岁和30岁的人。 */ > db.user.find() { "_id" : ObjectId("4da070040d03918e09fe7dac"), "name" : "huangz", "age" : 20, "favorite fruit" : "pear" } { "_id" : ObjectId("4da070180d03918e09fe7dad"), "name" : "jack", "age" : 20, "favorite fruit" : "apple" } { "_id" : ObjectId("4da0702d0d03918e09fe7dae"), "name" : "peter", "age" : 30, "favorite fruit" : "banana" } > db.user.distinct("age") [ 20, 30 ]
/* group的一般参数如下: ns: 要处理的集合名称 key: 需要group by处理的项 reduce: reduce函数,对集合内的每个文档使用一次,接受两个函数,一个是目前迭代的文档,另一个是聚合计数器。 initial: 聚合的初始值 keyf: 可选,用函数对key中的值进行修改然后作为key参数传入到group。 cond: 可选,相当于文档的filter,符合条件的文档才会被group处理。 finalize: 可选,每个group处理之后的结果都传入到这个函数,可以用于修改最终group结果。 参数有点复杂,但整体还是很清晰的。 最好的例子还是参考mongodb主页的例子: http://www.mongodb.org/display/DOCS/Aggregation */ db.test.group( { cond: {"invoked_at.d": {$gte: "2009-11", $lt: "2009-12"}} , key: {http_action: true} , initial: {count: 0, total_time:0} , reduce: function(doc, out){ out.count++; out.total_time+=doc.response_time } , finalize: function(out){ out.avg_time = out.total_time / out.count } } );
/* MapReduce参数 db.runCommand( { mapreduce : <collection>, map : <mapfunction>, reduce : <reducefunction> [, query : <query filter object>] [, sort : <sort the query. useful for optimization>] [, limit : <number of objects to return from collection>] [, out : <see output options below>] [, keeptemp: <true|false>] // 如果为true,则计算的结果作为数据保存到out参数指定的地方 [, finalize : <finalizefunction>] // 处理所有计算结果的函数 [, scope : <object where fields go into javascript global scope >] // 指定计算时候可以访问的外部变量 [, verbose : true] // 提供计算时候的统计数据 } ); 关于out选项,当使用版本<=1.7.3时,可以指定变量或者集合名字作为参数。 如果使用版本>=1.7.4,参数名可以是以下: 1.collectionName: 结果覆盖同名集合 2.{replace: collectionName}: 同上 3.{merge: collectionName}: 将结果和集合里的数据合并,如果有同名的key,新的数据会覆盖旧的数据。 4.{reduce: collectionName}: 如果计算结果和集合里的数据有相同key的情况出现,将调用指定的reduce function。 5.{inline: 1}: 所有计算结果保存在内存当中。 详细参考MongoDB网站: http://www.mongodb.org/display/DOCS/MapReduce */ > db.things.insert( { _id : 1, tags : ['dog', 'cat'] } ); > db.things.insert( { _id : 2, tags : ['cat'] } ); > db.things.insert( { _id : 3, tags : ['mouse', 'cat', 'dog'] } ); > db.things.insert( { _id : 4, tags : [] } ); > // map function > m = function(){ ... this.tags.forEach( ... function(z){ ... emit( z , { count : 1 } ); ... } ... ); ...}; > // reduce function > r = function( key , values ){ ... var total = 0; ... for ( var i=0; i<values.length; i++ ) ... total += values[i].count; ... return { count : total }; ...}; > res = db.things.mapReduce(m,r); > res {"timeMillis.emit" : 9 , "result" : "mr.things.1254430454.3" , "numObjects" : 4 , "timeMillis" : 9 , "errmsg" : "" , "ok" : 0} > db[res.result].find() {"_id" : "cat" , "value" : {"count" : 3}} {"_id" : "dog" , "value" : {"count" : 2}} {"_id" : "mouse" , "value" : {"count" : 1}} > db[res.result].drop()
/* 创建一个名为"fifo"的capped collection, 最大占用空间为100 000byte, 最大文档数量为100个 */ > db.createCollection("fifo", {capped: true, size: 100000, max:100}) { "ok" : 1 } > db.fifo.stats() { "ns" : "test.fifo", "count" : 0, "size" : 0, "storageSize" : 100096, "numExtents" : 1, "nindexes" : 0, "lastExtentSize" : 100096, "paddingFactor" : 1, "flags" : 0, "totalIndexSize" : 0, "indexSizes" : { // capped collection的索引总是空的,因为它不允许使用索引 }, "capped" : 1, // 说明这是个capped collection "max" : 100, // 最大文档数量 "ok" : 1 } /* 注意只是指定一个集合的size,而不指定capped:true参数,那只是创建了一个限制大小的普通集合,不是capped collection。 */ > db.createCollection("collection_with_preallocating_space", {size: 100000}) { "ok" : 1 } > db.collection_with_preallocating_space.stats() { "ns" : "test.collection_with_preallocating_space", "count" : 0, "size" : 0, "storageSize" : 131072, "numExtents" : 1, "nindexes" : 1, "lastExtentSize" : 131072, "paddingFactor" : 1, "flags" : 1, "totalIndexSize" : 8192, "indexSizes" : { "_id_" : 8192 }, "ok" : 1 }
/* 对一个CC结构进行插入 */ > db.fifo.insert({"name":"huangz"}) > db.fifo.insert({"name":"jack"}) > db.fifo.insert({"name":"peter"}) /* 默认的插入序 */ > db.fifo.find() { "_id" : ObjectId("4da26e03fd2b46bafddb950f"), "name" : "huangz" } { "_id" : ObjectId("4da26e0cfd2b46bafddb9510"), "name" : "jack" } { "_id" : ObjectId("4da26e16fd2b46bafddb9511"), "name" : "peter" } > db.fifo.find().sort({"$natural": 1}) { "_id" : ObjectId("4da26e03fd2b46bafddb950f"), "name" : "huangz" } { "_id" : ObjectId("4da26e0cfd2b46bafddb9510"), "name" : "jack" } { "_id" : ObjectId("4da26e16fd2b46bafddb9511"), "name" : "peter" } /* 逆插入序 */ > db.fifo.find().sort({"$natural": -1}) { "_id" : ObjectId("4da26e16fd2b46bafddb9511"), "name" : "peter" } { "_id" : ObjectId("4da26e0cfd2b46bafddb9510"), "name" : "jack" } { "_id" : ObjectId("4da26e03fd2b46bafddb950f"), "name" : "huangz" }
CC结构的详细文档参见: Capped Collection
2.DBRef
DBRef文档,它不同于嵌套文档,不直接保存文档本身,它只是保存对另外文档的引用,类似关系数据库中的外键引用。
当数据修改时,嵌套数据要更新文档,但DBRef的文档总是最新的。
/* 创建一个ref到user的profile文档 */ > var huangz = db.user.find({"name":"huangz"}) > db.phone.insert({"number": 15820123123, "profile": {"$ref": "user", "$id": huangz._id}}) > db.phone.find() { "_id" : ObjectId("4da1e59bfadbe09a1a9a324f"), "number" : 15820123123, "profile" : { "$ref" : "user", "$id" : null } } // 引用ref文档的方法各个驱动都稍有不同,请参见各自的文档
DBRef的详细文档参见: Database Reference
3.GridFS
MongoDB内建了一个称为GridFS的存储机制,可以用于保存比普通MongoDB文档更大的文件(最大为2GB)。
/* MongoDB的Python驱动PyMongo手册的例子 */ >>> from pymongo import Connection >>> import gridfs >>> >>> db = Connection().gridfs_example >>> fs = gridfs.GridFS(db) >>> a = fs.put("hello world") // 保存 >>> fs.get(a).read() // 读取 'hello world'
GridFS的相信文档参见: GridFS