MongoDB学习笔记六:进阶指南
【数据库命令】
『命令的工作原理』
MongoDB中的命令其实是作为一种特殊类型的查询来实现的,这些查询针对$cmd集合来执行。runCommand仅仅是接受命令文档,执行等价查询,因此,
> db.runCommand({"drop" : "test"})
这个drop调用实际上是这样的:
db.$cmd.findOne({"drop" : "test"})
当MongoDB服务器得到查询$cmd集合的请求时,会启动一套特殊的逻辑来处理,而不是交给普通的查询代码来执行。几乎所有MongoDB驱动程序都提供与一个类似于runCommand的帮助方法来执行命令,但是如果有必要,总是可以使用一个简单查询的方式来运行命令。
访问有些命令需要有管理员权限,必须在admin数据库里面运行。如果在别的数据库里运行这样的命令,会得到“拒绝访问”的错误。
『命令参考』
要活的所有命令的最新列表,有两种方式:
·在shell中运行db.listCommands(),或者从驱动程序中运行等价的命令listCommands。
·浏览管理员接口http://localhost:28017/_commands
MongoDB中最经常使用的命令:
· buildInfo
{"buildInfo" : 1}
管理专用命令,返回MongoDB服务器的版本号和主机的操作系统
· collStats
{"collStats" : collection}
返回指定集合的统计信息,包括数据大小、已分配的存储空间和索引的大小。
· distinct
{"distinct" : collection, "key" : key, "query" : query}
列出指定集合中满足查询条件的文档的指定键的所有不同值。
· drop
{"drop" : collection}
删除集合的所有数据。
· dropDatabase
{"dropDatabase" : 1}
删除当前数据库的所有数据。
· dropIndexes
删除集合里面名称为name的索引,如果名称为"*",则删除全部索引。
· findAndModify
更新文档,并且返回更新前或者后的文档信息。(这一行自己总结的)
· getLastError
查看对本集合执行的最后一次操作的错误信息或者其他状态信息。
· isMaster
{"isMaster" : 1}
检查本服务器是猪服务器还是从服务器。
· listCommands
{"listCommands" : 1}
返回所有在服务器上运行的命令及相关信息。
· listDatabases
{"listDatabases" : 1}
管理专用命令,列出服务器上所有的数据库。
· ping
{"ping" : 1}
检查服务器链接是否正常。即便服务器上锁了,这条命令也会立刻返回。
· renameCollection
{"renameCollection" : 1, "to" : b}
将集合a重命名为b,其中a和b都必须是完整的集合命名空间(例如"foo.bar"表示foo数据库中的bar集合)
· repairDatabase
{"repairDatabase" : 1}
修复并压缩当前数据库,这个操作可能非常耗时。
· serverStatus
{"serverStatus" : 1}
返回这台服务器的管理统计信息。
【固定集合】——固定大小的集合
固定集合要实现创建,而且大小固定。
固定集合很像环形队列,如果空间不足,最早的文档就会被删除,为新的文档腾出空间。这意味着固定集合在新文档插入的自动淘汰最早的文档。
有些操作不适用于固定集合。不能删除文档(除了前面说的自动淘汰),更新不得导致文档移动(通常更新意味着尺寸增大)。
基于上述两点,可以保证在固定集合中的文档以插入的顺序存储,而且不必维护一个已删除的文档的释放空间列表。
固定集合在默认情况侠没有索引,即便是"_id"上也没有索引。
『属性及用法』
①对固定一盒插入速度极快。因为不需要额外分配空间。
②按照插入顺序输出的查询速度极快。
③固定集合能够在新数据插入时,自动淘汰最早的数据。
插入快速、按照插入顺序查询也快速、自动淘汰 --> 固定集合特别适合像日志这种应用场景。
事实上,MongoDB中设计固定集合的目的就是用来存储内部的复制日志oplog。
『创建固定集合』
不像普通集合,固定集合必须要在使用前显式地创建。使用create命令创建。在shell中,可以使用createCollection来创建:
> db.createCollection("my_collection", {capped: true, size: 100000});
{ "ok" : true }
上面的命令创建了一个固定集合my_collection,大小是100000字节。createCollection也有别的选项。除了指定总的容量,还可以指定文档数量的上限:
> db.createCollection("my_collection", {capped: true, size: 100000, max: 100});
{ "ok" : true }
注:当指定文档数量上限时,必须同时指定大小。淘汰机制只有在容量还没有满时才回一句文档数量来工作。要是容量满了,淘汰机制则会依据容量来工作,就想别的固定集合一样。
还可以通过转换已有的普通几核的方式来创建固定集合。使用convertToCapped命令来完成这个操作。下面的例子中,会把test集合转换成大小为10000自己的固定集合:
> db.runCommand({convertToCapped: "test", size: 10000});
{ "ok" : true }
『自然排序』
使用自然排序按照插入顺序或者反向插入顺序返回文档。
> db.my_collection.find()/sort({"$natural": -1})
使用{"$natural" : 1}表示与默认顺序相同。
『尾部游标』
尾部游标是一种特殊的持久游标,这类游标不会在没有结果后销毁。游标收到tailf命令的启发,类似地会尽可能持续地获取结果输出。因为这类游标在没有结果后也不销毁,所以一旦有新文档添加到集合里面就会被取回并输出。尾部游标只能用在固定集合上。
Mongo shell不支持尾部游标,但是可以看看PHP中的例子:
$cursor = $collection->find()->tailable(); while (true) {
if (!$cursor->hasNext()) {
if ($cursor->dead()) {
reak;
}
sleep(1);
}
else {
while (cursor->hasNext()) {
do_stuff(cursor->getNext());
}
}
}
游标没有销毁,要么处理结果,要么等着有更多的结果。
【GridFS:存储文件】
『开始使用GridFS:mongofiles』
mongofiles内置在MongoDB发布版中,可以用来在GridFS中上传、下载、列示、查找或删除文件。
执行mongofiles --help可以查看可用选项。
下面将会介绍如何用mongofiles从文件系统向GridFS上传文件,列出GridFS中的所有文件,下载刚上传的文件:
$ 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
$ cat foo.txt
Hello, world
上面的例子中,使用了mongofiles的3个基本操作:put、list和get。put将文件系统中的一个文件添加到GridFS中,list会把所有添加到GridFS中的文件列出来,get则是put的逆操作,它将GridFS中的文件写入到文件系统中。mongofiles还支持另外两个操作:search用来按文件名查找GridFS中的文件,delete则从GridFS中删除一个文件。
『通过MongoDB驱动程序操作GridFS』
例:使用MongoDB的Python驱动程序PyMongo,可以实现上面用mongofiles执行的一系列操作:
>>> from pymongo import Connection
>>> import gridfs
>>> fs = Connection().test
>>> fs = gridfs.GridFS(db)
>>> file_id = fs.put("Hello, world", filename="foo,txt")
>>> fs.list()
[u'foo.txt']
>>> fs.get(file_id).read()
'Hello, world'
『内部原理』
GridFS的一个基本思想就是可以将大文件分成很多块,每块作为一个单独的文档存储,这样就能村大文件了。由于MongoDB支持在文档中存储二进制数据,可以最大限度减小块的存储开销。另外,除了存储文件本身的块,还有一个单独的文档用来存储分块的信息和文件的元数据。
GridFS的块有个单独的集合。默认情况下,块将使用fs.chunk集合,如有需要可以覆盖。这个块集合里面文档的结构是非常简单的:
{
"_id" : ObjectId("..."),
"n" : 0,
"data" : BinData("..."),
"files_id" : ObjectId("...")
}
和别的MongoDB文档一样,快也有自己唯一的"_id"。
"files_id"是包含这个块元数据的文件文档的"_id"。
"n"表示块编号,也就是这个块在源文件中的顺序编号。
"data"包含组成文件块的二进制数据。
文件的元数据放在另一个集合中,默认是fs.files。这里面的每个文档代表GridFS中的一个文件,与文件相关的自定义元数据也可以存在其中。
除了用户自定义的键,GridFS规范还定义了一些键:
_id
文件唯一的id,在块中作为"files_id"键的值存储。
length
文件内容总的字节数。
chunkSize
每块的大小,以字节为单位。默认是256K,必要时可以调整。
uploadDate
文件存入GridFS的时间戳。
md5
文件内容的md5校验和,由服务器端生成。
理解了GridFS规范后,实现一些驱动程序没提供的功能就很容易了。例如,可以使用distinct命令获取GridFS中不重复的文件名列表:
> db.fs.files.distinct("filename")
[ "foo.txt" ]
【服务器端脚本】
在服务器端可以通过db.eval函数来执行JavaScript脚本。也可以把JavaScript脚本保存在数据库中,然后再别的数据库命令中调用。
『db.eval』
利用db.eval可以在MongoDB的服务器端执行任意的JavaScripe脚本。
这个函数先将给定的JavaScript字符串传送给MongoDB(在这里执行),然后返回结果。
db.eval可以用来模拟多文档事务:db.eval锁住数据库,然后执行JavaScript,再解锁。
发送代码有两种选择,或者封装进一个函数,或者不封装。下面两行代码是等价的:
> db.eval("return 1;")
1
> db.eval("function() { return 1; }")
1
只有传递参数的时候,才必须要封装成一个函数。参数通过db.eval的第二个参数传递,要写成一个数组的形式。例如,如果想给一个函数传递username,可以这么做:
> db.eval("function(u) { print('Hello, ' + u + '!'); }", [username])
有必要的话可以传递多个参数。例如,要计算3个数的和,可以这样:
> db.eval("function(x,y,z) { return x + y + z; }", [num1, num2, num3])
num1对应x,num2对应y,num3对应z。
调试db.eval的一个方法是:将调试信息写进数据库日志中,这个可以通过print函数来完成:
> db.eval("print('Hello, world');");
『存储JavaScript』
每个MongoDB的数据库中都有个特殊的集合,叫做system.js,用来存放JavaScript变量。这些变量可以在任何MongoDB的JavaScript上下文中调用,包括"$where"自居,db.eval调用,MapReduce作业。用insert就可以将变量加进system.js中。
> 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代码。这样就可以很方便地自定义一些实用程序。例如,要用JavaScript写一个日志函数,就可以将其存放到system.js中:
> 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
使用存储的JavaScript缺点就是代码会与常规的源代码控制脱离,会搅乱客户端发送来的JavaScript。
最适合使用存储的JavaScript的情况就是程序中有多个地方(也可能是不同的程序,或者不同语言的代码)都要用到一个JavaScript函数。将这样的函数放置在中心位置,要是有更新的话就不必每处都修改。要是JavaScript代码很长又要频繁使用的话,也可以使用存储的JavaScript,这样存一次会节省不少网络传输时间。
『安全性』
若是想打印"Hello, 用户名!"给用户。其中的用户名保存在一个名为username的变量中。可以像下面这样写这段程序:
> func = "fucntion() { printf('Hello, "+username+"!'); }"
如果username是用户定义的,就可能会是这样的字符串"'); db.dropDatabase(); print('",这样代码就变成了下面这样:
> func = "fucntion() { printf('Hello, '); db.dropDatabase(); print('!'); }"
整个数据库都被清干净了!
为了避免这种情况,要先定作用域。例如,在PHP中应该这样写:
$func = new MongoCode("function() { print('Hello, "+username+"!'); }", array("username" => $username));
数据可就会安全地输出如下字符:
Hello, '); db.dropDatabase(); print('!
【数据库引用】
数据库引用,也叫作DBRef。DBRef就像URL,唯一确定一个到文档的引用。它自动加载文档的方式正如网站中URL通过链接自动加载Web页面一样。
『什么是DBref』
DBRef是个内嵌文档,就像MongoDB中的其他内嵌文档一样。但是DBref有些必选键。下面是一个简单的例子:
{"$ref" : collection, "$id" : id_value}
DBRef指向一个集合,还有一个id_value用来在集合里面根据"_id"确定唯一的文档。这两条信息使得DBRef能够唯一标识MongoDB数据库内的任何一个文档。若是想引用另一个数据库中的文档,DBRef中有一个可选键"$db",用这个就可以了:
{"$ref" : collection, "$id" : id_vlue, "$db" : database}
注:DBRef中的键的顺序不能改变,第一个必须是"$ref",接着是"$id",然后是(可选的)"$db"。
『示例模式』
来看一个使用DBRef跨集合引用文档的例子:
本例中含有两个集合users和notes。用户(user)可以创建笔记(note),笔记可以引用用户或者别的笔记。现在有一些用户文档,每一个都有唯一的用户名作为其"_id",以及一个独立行事的"display_name":
{"_id" : "mike", "display_name" : "Mike D"}
{"_id" : "kristina", "display_name" : "Kristine C"}
notes集合稍微复杂一些。每个笔记都含有一个唯一的"_id"。正常情况下,这个"_id"很可能是个ObjectID,但试着利用整数,是为了让例子简明,突出重点。notes还有一个"author",若干个"text",以及一个可选的"references"指向其他笔记或者用户:
{"_id" : 5, "author" : "mike", "text" : "MongoDB is fun!"}
{"_id" : 20, "author" : "kristina", "text" : "... and DBRefs are easy, too",
"references" : [{"$ref" : "users", "$id" : "mike"}, {"$ref" : "notes", "$id" : 5}]}
第二个笔记包含一些对其它文档的引用,每一条都作为一个DBRef存储。应用层的程序会利用这些DBRef得到用户"Mike"和笔记"MongoDB is fun!"这两个文档,而它们都是与Kristina的笔记关联的。去引用是很容易实现的。"$ref"的值就是要查询的集合,然后使用"$id"键的值,获得"_id"的值:
> var note = db.notes.findOne({"_id" : 20})
> note.references.forEach(function(ref) {
printjson(db[ref.$ref].findOne("_id" : ref.$id));
});
{ "_id" : "Mike", "display_name" : "Mike D" }
{ "_id" : 5, "author" : "mike", "text" : "MongoDB is fun!" }
『驱动对DBRef的支持』
不是所有驱动程序都将DBRef作为普通的内嵌文档。一些驱动程序为DBRef提供了特殊的类型,这样就会和普通文档自动相互转换。这主要是为了开发者提供便利,因为这样可以忽略少量细节。例如,使用PyMongo中的DBRef类型可以向下面这样表示上面的例子:
>>> note = {"_id" : 20, "author" : "kristina",
"text" : "... and DBrefs are easy, too",
"references" : [DBRed("user", "mike"), DBRef("notes", 5)]}
当保存时,DBRef实例会自动被转换成等价的内嵌文档。当作为查询结果返回时,逆操作也会自动进行,就又得到了DBRef的实例。
一些驱动程序还添加了别的驱动工具来操作DBRef,比如处理去引用的方法,甚至提供当返回结果包含引用时自动去引用的机制。这些辅助功能随驱动程序的不同而变化,要想知道最新的信息,需要参考具体驱动程序的文档。
『什么时候该使用DBRef』
存储一些对不同几核的文档的引用时,最好使用DBRef。或者想使用驱动程序或者工具中DBRef特有的功能,只能用DBRef了。
否则,最好存储"_id"作为引用来使用,因为这样更精简,也更容易操作。
MongoDB学习笔记六:进阶指南的更多相关文章
- MongoDB学习笔记六—查询下
查询内嵌文档 数据准备 > db.blog.find().pretty() { "_id" : ObjectId("585694e4c5b0525a48a441b5 ...
- MongoDB学习笔记(六) MongoDB索引用法和效率分析
MongoDB中的索引其实类似于关系型数据库,都是为了提高查询和排序的效率的,并且实现原理也基本一致.由于集合中的键(字段)可以是普通数据类型,也可以是子文档.MongoDB可以在各种类型的键上创建索 ...
- MongoDB学习笔记(转)
MongoDB学习笔记(一) MongoDB介绍及安装MongoDB学习笔记(二) 通过samus驱动实现基本数据操作MongoDB学习笔记(三) 在MVC模式下通过Jqgrid表格操作MongoDB ...
- MongoDB学习笔记(六)--复制集+sharding分片 && 总结
复制集+sharding分片 背景 主机 IP 服务及端口 Server A ...
- MongoDB学习笔记(五)--复制集 && sharding分片
主从复制 主从节点开启 主节 ...
- MongoDB学习笔记:快速入门
MongoDB学习笔记:快速入门 一.MongoDB 简介 MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统.在高负载的情况下,添加更多的节点,可以保证服务器性能.M ...
- MongoDB学习笔记系列
回到占占推荐博客索引 该来的总会来的,Ef,Redis,MVC甚至Sqlserver都有了自己的系列,MongoDB没有理由不去整理一下,这个系列都是平时在项目开发时总结出来的,希望可以为各位一些帮助 ...
- PHP操作MongoDB学习笔记
<?php/*** PHP操作MongoDB学习笔记*///*************************//** 连接MongoDB数据库 **////*************** ...
- java之jvm学习笔记六-十二(实践写自己的安全管理器)(jar包的代码认证和签名) (实践对jar包的代码签名) (策略文件)(策略和保护域) (访问控制器) (访问控制器的栈校验机制) (jvm基本结构)
java之jvm学习笔记六(实践写自己的安全管理器) 安全管理器SecurityManager里设计的内容实在是非常的庞大,它的核心方法就是checkPerssiom这个方法里又调用 AccessCo ...
随机推荐
- SSH整合开发的web.xml配置
<?xml version="1.0" encoding="UTF-8"?><web-app version="2.5" ...
- C#版的mongodb最新的官方驱动2.4.0版本
已经升级了mongodb至最新的3.4的版本,我想想也该把驱动升到最新的了,我用的驱动还是1.7的版本呢,之前几次想升级,都是因为升级驱动需要改动的代码太大了,升级的成本很高,所以懒得动,就这么的用了 ...
- CodeSimth - .Net Framework Data Provider 可能没有安装
使用CodeSimth 连接SQLite数据库库 提示错误 codesmith 6以上的版本,说是支持sqlite生成,也有对应的sqliteprovider.dll,但是使用时却说Test fail ...
- firefox浏览器中silverlight无法输入问题
firefox浏览器中silverlight无法输入问题 今天用firefox浏览silverlight网页,想在文本框中输入内容,却没想到silverlight插件意外崩溃了.google一下,发现 ...
- UIKit框架之UIEvent
1.继承链:NSObject 2.事件大致可以分为三种事件:触摸事件.动作事件.遥控事件 3.获取事件的touches (1)- (NSSet<UITouch *> *)allTouche ...
- USACO 刷水
BZOJ 1666 水.. BZOJ 1579 分层图最短路. BZOJ 1782 从一开始若某头牛停在U,那么U的子树的时间都会加一用BIT维护DFS序就行了 BZOJ 1572 贪心+堆 排序后查 ...
- Singleton Design Pattern
The Singleton pattern is one of the simplest design patterns, which restricts the instantiation of a ...
- WPF中未将对象引用设置到对象的实例
前几天,我开始了WPF的基础学习,一上来我就遇到了一个令我头痛的问题,按照书上的例子我写了一段属于自己的代码,一个简单的色调器.满心期待的编译运行,就出现了未将对象引用设置到对象的实例.我在网上查阅了 ...
- GPU渲染和GDI
要实现这样一段逻辑,用GPU画3D图,用GDI画二维图元,怎么样效率高.相传Vista年代,是这样干的: 硬件渲染的东西在GPU上做完 读回CPU端 把GDI这些用软件渲染 两者混合 拷贝到显存显示 ...
- 收拾那掉了一地的session
多个页面有如下多个session,本来可能是如下面这样的 Session["UId"] = 10; Session["UName"] = "test& ...