前面我们谈了很多关于在MongoDB中创建、读取、更新、删除文档。除了这些,MongoDB还支持大量的高级操作,这些操作都是通过命令来实现的。实际上,除了CRUD,MongoDB的其他功能都是直接或间接通过命令来完成的。我们在前面也多次使用了命令操作,如前面的mapreduce,就是通过命令来触发运行的!

【命令的工作原理】

MongoDB中的命令其实是作为一种“特殊类型的查询”来实现的!这些查询是针对$cmd集合来执行(通过调用findOne函数)。runCmmand命令就是接受一个文档(注意该参数文档的键区分大小写!),来执行等价的查询操作。我们举个例子:通过执行命令和等价查询来删除一个集合

> show collections;
system.indexes
test
> db.runCommand({"drop" : "test"});
{
        "nIndexesWas" : 1,
        "msg" : "indexes dropped for collection",
        "ns" : "mylearndb.test",
        "ok" : 1
}
> show collections;
system.indexes
> db.test.insert({"x" : 1});
> show collections;
system.indexes
test
> db.$cmd.findOne({"drop" : "test"});
{
        "nIndexesWas" : 1,
        "msg" : "indexes dropped for collection",
        "ns" : "mylearndb.test",
        "ok" : 1
}
> show collections;
system.indexes
>

上面我们是通过执行命令和其对应的等价查询的方式删除同一个集合。当MongoDB服务器得到查询$cmd集合的请求时,会启动一套特殊的逻辑来处理!而不像别的集合那样来执行。几乎所有MongoDB的驱动程序都支持类似runCommand的执行命令的方式,也基本都支持其对应的等价查询这种执行方式。

在Shell中通过db.listCommands(),来看当前MongoDB支持的所有命令(同样可通过运行命令db.runCommand({"listCommands" : `1})来查询所有命令)。我们说几个常用的命令:

1》 db.runCommand({"buildInfo" : 1}):返回MongoDB服务器的版本号和服务器OS的相关信息。

2》 db.runCommand({"collStats" : 集合名}):返回该集合的统计信息,包括数据大小,已分配存储空间大小,索引的大小等。

3》 db.runCommand({"distinct" : 集合名, "key" : 键, "query" : 查询文档}):返回特定文档所有符合查询文档指定条件的文档的指定键的所有不同的值。

4》 db.runCommand({"dropDatabase" : 1}):清空当前数据库的信息,包括删除所有的集合和索引。

5》 db.runCommand({"isMaster" : 1}):检查本服务器是主服务器还是从服务器。

6》 db.runCommand({"ping" : 1}):检查服务器链接是否正常。即便服务器上锁,该命令也会立即返回。

7》 db.runCommand({"repaireDatabase" : 1}):对当前数据库进行修复并压缩,如果数据库特别大,这个命令会非常耗时。

8》 db.runCommand({"serverStatus" : 1}):查看这台服务器的管理统计信息。

我们前面提到了,某些命令必须在admin数据库(从权限角度看,是root数据库)下运行,我们再看两个这样的命令:

9》 db.runCommand({"renameCollection" : 集合名, "to":集合名}):对集合重命名,注意两个集合名都要是完整的集合命名空间,如foo.bar, 表示数据库foo下的集合bar。

10》db.runCommand({"listDatabases" : 1}):列出服务器上所有的数据库!

【固定集合】

前面提到的所有集合,都是动态创建的(向一个不存在的集合插入文档的同时会创建该集合)。这种集合会随着数据的增多而自动扩容。MongoDB同时还支持另外一种集合---固定集合。这种集合的特征就是,需要提前创建,并且大小固定!对于大小固定,我们可以想象其就像一个环形队列,当集合空间用完后,再插入的元素就会覆盖最初始的头部的元素!

在shell中,我们通过createCollection来显示创建一个固定集合:

> db.createCollection("myFixedColl", {"capped" : true, "size" : 100000,
... "max" : 100} );
{ "ok" : 1 }
>

上面,我们创建了名称为myFixedColl的固定集合,其固定大小为100000字节,最多放置文档数目为100个。这里需要强调,可以不指定max,但必须指定size,当同时指定了size和max,当size没有达到上限时,以max来控制文档数量,当size达到上限时,以size为准!

我们还可以使用命令convertToCapped,将一个普通集合转化为一个固定集合:

> db.runCommand({"convertToCapped" : "testnew", "size" : 10000});
{ "ok" : 1 }
>

上述命令将一个普通集合testnew转化为一个大小为10000字节的固定集合。

【固定集合的特性和使用限制】

固定集合本质上和普通集合不同,也为其赋予了很多特性。首先,默认情况下,固定集合没有索引,列"_id"上也不存在索引。其插入速度极快,直接在集合的尾部插入即可,有必要的话会进行自动覆盖,文档在集合中的存储顺序就是其插入顺序。对于查询,固定集合会按照插入顺序返回文档列表,速度也十分了得!

使用固定集合也有一些限制,如不能删除文档(自动淘汰不算),更新文档不能导致文档移动(即更新不能将原始文档尺寸一定范围,这个范围就是预留的文档补白大小),否则更新会失败!

【自然排序】

相对于其他集合的排序,固定集合有种特殊的排序方式:自然排序。自然顺序就是文档在磁盘上的存储顺序!因为固定集合的存储顺序就是插入文档的顺序,因此自然顺序就是插入顺序!

> db.myFixedColl.insert({"name" : "jimmy", "age" : 40, "job" : "programmer"});
> db.myFixedColl.insert({"name" : "tom", "age" : 60, "job" : "manager"});
> db.myFixedColl.insert({"name" : "tim", "age" : 50, "job" : "jgs"});
> db.myFixedColl.find();
{ "_id" : ObjectId("5037324500275ae6127c6ada"), "name" : "jimmy", "age" : 40, "j
ob" : "programmer" }
{ "_id" : ObjectId("5037325400275ae6127c6adb"), "name" : "tom", "age" : 60, "job
" : "manager" }
{ "_id" : ObjectId("5037326400275ae6127c6adc"), "name" : "tim", "age" : 50, "job
" : "jgs" }
> db.myFixedColl.find().sort({"$natural" : 1});
{ "_id" : ObjectId("5037324500275ae6127c6ada"), "name" : "jimmy", "age" : 40, "j
ob" : "programmer" }
{ "_id" : ObjectId("5037325400275ae6127c6adb"), "name" : "tom", "age" : 60, "job
" : "manager" }
{ "_id" : ObjectId("5037326400275ae6127c6adc"), "name" : "tim", "age" : 50, "job
" : "jgs" }
> db.myFixedColl.find().sort({"$natural" : -1});
{ "_id" : ObjectId("5037326400275ae6127c6adc"), "name" : "tim", "age" : 50, "job
" : "jgs" }
{ "_id" : ObjectId("5037325400275ae6127c6adb"), "name" : "tom", "age" : 60, "job
" : "manager" }
{ "_id" : ObjectId("5037324500275ae6127c6ada"), "name" : "jimmy", "age" : 40, "j
ob" : "programmer" }
>

上述我们向一个固定集合中插入3条数据,直接调用find(),会按照插入顺序返回文档。对游标按自然正序({"$natural":1})处理后,返回的还是插入顺序,对游标按自然倒序({"$natural":-1})处理后,返回的就是插入的倒序。对于普通集合,因为其存储顺序与插入顺序没有必然的联系,所以自然排序在普通集合上使用没有多大意义!

【尾部游标】

尾部游标是一种特殊的持久游标,这些游标在没有结果后不会被销毁,会在持续地获取结果输出。一旦有文档插入文档中,就会立即获得!只有固定集合支持尾部游标!Shell中不支持尾部游标,后面我们在特定语言的驱动下使用MongoDB再进行演示。

固定集合在实际中适用于想要自动淘汰过期内容的场景。如果有这方面的需求,不要忘记了这种特殊的集合!