参考资料:
《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
2807

被折叠的 条评论
为什么被折叠?



