MongoDB权威指南(6)- Advanced Topics
1.数据库命令
mongoDB提供了范围广泛的数据库命令,覆盖了除create,read,update,delete之外的所有功能。比如我们前边用到的getLastError命令,用来查看update时受影响的数量。
命令是如何工作的
这里有个你比较熟悉的例子:drop,如果从shell里删除一个collection,我们运行db.test.drop().实际上,在内部执行的是drop命令,跟下边用runCommand执行的操作是一样的
> db.runCommand({"drop" : "test"}); { "nIndexesWas" : 1, "msg" : "indexes dropped for collection", "ns" : "test.test", "ok" : true }
作为结果返回的document叫做命令响应,包括了一些信息,如命令执行是否成功等。
实际上,mongoDB的命令被实现为一种对叫$cmd的collection的特殊查询,runCommand只是使用参数进行了一次查询,所以我们的drop也可以这样写
db.$cmd.findOne({"drop" : "test"});
当mongoDB服务器接到一个对$cmd的查询时,使用一种特殊的逻辑来处理。几乎所有的驱动都提供了runCommand方法来执行命令,实际上这些命令都可以通过执行查询的方式来完成。
命令参考
到目前mongoDB支持75个以上的数据库命令,而且这些命令越来越多,你可以通过两种方式查看支持的命令列表,一是在shell里执行db.listCommands(),二是在浏览器里访问mongoDB的管理站点http://localhost:28017/_commands
下边列出了一些最常用的命令:
- buildInfo: {"buildInfo" : 1}, 返回mongoDB服务器版本和宿主操作系统的信息
- collStats: {"collStats" : collection},给出指定collection的统计信息,包括数据大小,分配的存储控件,索引大小等
- distinct: {"distinct" : collection, "key": key, "query": query} 返回在指定的collection里符合query条件的所有key的值
- drop: {"drop" : collection}, 删除collection的说有数据
- dropDatabase: {"dropDatabase" : 1}, 删除当前数据库的所有数据
- dropIndexes: {"dropIndexes" : collection, "index" : name}, 删除collection上名字为name的索引
- findAndModify:参见第3章
- getLastError:{"getLastError" : 1[, "w" : w[, "wtimeout" : timeout]]}, 检查此连接上最后操作的错误或状态信息,可以指定一个选项,此命令将会阻塞直到w个salves复制了最后的那个操作或者时间超时(毫秒)
- isMaster: {"isMaster" : 1}, 检查此服务器是master还是slave
- listCommands: {"listCommands" : 1}, 列出此服务器上所有可用命令
- listDatabases: {"listDatabases" : 1},列出服务器上所有数据库
- ping: {"ping" : 1},检查服务器是否正在运行,即使服务器处于锁定状体此命令也会立即返回
- renameCollection: {"renameCollection" : a, "to" : b}, 将collection的名字从a改为b
- repairDatabase:{"repairDatabase" : 1}, 修复并压缩当前数据库
- serverStatus:{"serverStatus" : 1}, 获取此服务器的管理统计信息
2.Capped Collections
mongoDB还支持一种特殊的collection叫Capped Collections,这种collection是事先创建并且是大小固定的。固定大小就带来了一个问题,如果collection满了之后对其执行插入该如何处理。Capped Collection的工作方式像一个循环队列,如果空间用完了,那么最旧的数据会被删除掉,新数据会取代旧数据的位置。这就是说,随着新document的插入,最旧的document会自动消亡。
某些操作在Capped Collection上是不允许执行的。不允许执行document删除,也不允许执行会导致document移动的更新(使document变大的更新),这样就可以保证Capped Collections里的document是按照插入顺序存储的。
特性和用例
Capped Collection的这些特点和限制会产生有趣的特性。首先是插入很快,执行插入的时候不再需要分配空间,服务器就不用查询空闲列表,插入的document直接放在collection的尾部。缺省情况下,也不会更新索引,所以一次插入本质上就是一次内存拷贝。另外一个特性是,按插入顺序查询很快,document是按照插入顺序存储的,只需要按它们在磁盘上的顺序遍历。最后,旧数据的自动消亡也是个有用的特性。包括了上述3个特性的Capped Collection对某些用例特别适用,如日志,实际上Capped Collection原本就是设计来存储内部复制日志的。另外还有个好的用例,缓存小规模的document。
创建Capped Collections
> db.createCollection("my_collection", {capped: true, size: 100000}); { "ok" : true }
另外还可以指定document的数量上限,如下
> db.createCollection("my_collection", {capped: true, size: 100000, max: 100}); { "ok" : true }
将一个普通collection转化为Capped Collection
> db.runCommand({convertToCapped: "test", size: 10000}); { "ok" : true }
自然排序
自然排序是Capped Collection上的一种特殊排序,就是按照磁盘上的存储顺序排序。当然也可以按反存储顺序排序,指定参数-1.
> db.my_collection.find().sort({"$natural" : -1})
循环游标(Tailable Cursors)
循环游标只能用于Capped Collection上,循环游标是一种特殊的持久性的游标,在结果集耗尽之后并不会关闭,当有了新document插入到collection之后,它可以继续取出。shell不支持循环游标。
3.GridFS: 存储文件
GridFS是mongoDB里的存储二进制大文件的机制。使用GridFS最简单的方式就是使用mongofiles工具,这个工具在分发包里边有。下边演示了如何使用mongofiles上传下载列出文件。
$ echo "Hello, world" > foo.txt $ ./mongofiles put foo.txt connected to: 127.0.0.1 added file: { _id: ObjectId('4c0d2a6c3052c25545139b88'), filename: "foo.txt", length: 13, chunkSize: 262144, uploadDate: new Date(1275931244818), md5: "a7966bf58e23583c9a5a4059383ff850" } done! $ ./mongofiles list connected to: 127.0.0.1 foo.txt 13 $ rm foo.txt $ ./mongofiles get foo.txt connected to: 127.0.0.1 done write to: foo.txt $ cat foo.txt Hello, world
ps:如果你使用的是windows控制台,需要把rm命令换成del,把cat命令换成type
mongofiles还支持search和delete。
GridFS是一个基于普通document之上的文件存储规范,mongoDB服务器对GridFS请求几乎没有做任何特殊处理,所有的工作都是客户端驱动和工具完成的。
GridFS的基本思想是将大文件分割成多个块,每个块存储为一个document,除了存储文件的每个块外,还有个单独的document把这些块组织在一起,并且包含了文件的元数据信息。
GridFS中的块有自己的collection,缺省是使用fs.chunks,文件元数据存储在另外一个collection,缺省为fs.files。
4.服务器端脚本
db.eval函数允许你在服务器端执行javascript代码,将javascript字符串传递给服务器,然后返回结果。
db.eval可以用来模拟事务,db.eval会锁定数据库,执行javascript,然后解锁数据库。虽然没有回滚,这也能够让你保证一组操作是按顺序执行。
代码可以包装进一个function发送也可以不包装,下边两行是等价的
> db.eval("return 1;") 1 > db.eval("function() { return 1; }") 1
如果要传递参数的话,就必须将代码封装进函数了
> db.eval("function(x,y,z) { return x + y + z; }", [num1, num2, num3])
Stored Javascript
mongoDB里的每个数据库都有个特殊的collection叫system.js,用于存储javascript变量,这些变量可以在任意的javascript上下文中使用,包括"$where"子句、db.eval调用、MapReduce任务等。通过简单的插入就可以添加javascript变量了。
> db.system.js.insert({"_id" : "x", "value" : 1}) > db.system.js.insert({"_id" : "y", "value" : 2}) > db.system.js.insert({"_id" : "z", "value" : 3})
这样就在全局范围定义了x、y、z三个变量,如果你想求它们的和
> db.eval("return x+y+z;") 6
system.js同样可以存储javascript代码,例如,你想创建一个日志打印函数
> db.system.js.insert({"_id" : "log", "value" : ... function(msg, level) { ... var levels = ["DEBUG", "WARN", "ERROR", "FATAL"]; ... level = level ? level : 0; // check if level is defined ... var now = new Date(); ... print(now + " " + levels[level] + msg); ... }})
那么你就可以在任何javascript上下文中调用此函数了
> db.eval("x = 1; log('x is '+x); x = 2; log('x is greater than 1', 1);");
数据库日志中就会包含下边的内容
Fri Jun 11 2010 11:12:39 GMT-0400 (EST) DEBUG x is 1
Fri Jun 11 2010 11:12:40 GMT-0400 (EST) WARN x is greater than 1
5.数据库引用(Database References/DBRef)
一个DBRef就是一个嵌入的document,和其他的嵌入document一样,但是必须有一些特殊的key。如下
{"$ref" : collection, "$id" : id_value}
这个DBRef引用了collection和id_value,使用这两个值就可以唯一确定一个document。如果要引用其他数据库的document,还需要第三个key,即"$db"
{"$ref" : collection, "$id" : id_value, "$db" : database}
Note:DBRef里边的key的顺序是有影响的,第一个必须是"$ref",然后是"$id",然后是可选的"$db"。
由于mongoDB是非关系型的(无join),document之间的引用(外键)通常是客户端通过额外的查询来完成的。mongoDB里有两种常用的做法,一是简单的手动引用,另外就是DBRef标准,很多驱动都是支持DBRef的。
简单的手动引用
一般来说,手动写代码来处理引用的办法用起来还是不错的,我们只需要把另外的document的_id的值存起来,然后进行查询,如:
> // grab a random blog post: > p = db.postings.findOne(); { "_id" : ObjectId("4b866f08234ae01d21d89604"), "author" : "jim", "title" : "Brewing Methods" } > // get more info on author of post p > a = db.users.findOne( { _id : p.author } ) { "_id" : "jim", "email" : "jim@gmail.com" } > // inverse: given an author, find all blog posts for the author > db.postings.find( {author : a._id } )
DBRef
各个驱动对DBRef的实现不太一样,不是所有的驱动都把DBRef当成普通的嵌入document,有些提供了一个特殊类型来自动转换。C#中使用一个叫DBRef的类,构造函数有两个参数,collection的名字和_id,然后就可以用FollowReference方法来获取引用的document。
在javascript(shell)中,如下例:
> x = { name : 'Biology' } { "name" : "Biology" } > db.courses.save(x) > x { "name" : "Biology", "_id" : ObjectId("4b0552b0f0da7d1eb6f126a1") } > stu = { name : 'Joe', classes : [ new DBRef('courses', x._id) ] } // or we could write: // stu = { name : 'Joe', classes : [ {$ref:'courses',$id:x._id} ] } > db.students.save(stu) > stu { "name" : "Joe", "classes" : [ { "$ref" : "courses", "$id" : ObjectId("4b0552b0f0da7d1eb6f126a1") } ], "_id" : ObjectId("4b0552e4f0da7d1eb6f126a2") } > stu.classes[0] { "$ref" : "courses", "$id" : ObjectId("4b0552b0f0da7d1eb6f126a1") } > stu.classes[0].fetch() { "_id" : ObjectId("4b0552b0f0da7d1eb6f126a1"), "name" : "Biology" } >