11

基本概念

  • 文档是 MongoDB 中数据的基本单元,非常类似于关系型数据库中的行,但更具有表现力;
  • 集合 Collection 可以看作是一个动态模式(Dynamic Schema)的表;
  • MongoDB 的一个实例可以拥有多个相互独立的数据库 ( Database),每一个数据库都拥有自己的集合;
  • 每一个文档都有一个特殊的键 _id ,这个键在文档所属的集合中是唯一的;
  • MongoDB 自带了一个简单但功能强大的 JavaScript Shell ,可用于管理 MongoDB 的实例或数据操作。

文档

文档是 MongoDB 的核心概念,文档就是键值对的有序集,下面的即是以 JavaScript 语言表现的一份文档:

{
    "greeting" : "Hello, world!"
}

在绝大多数情况下,文档的键是字符串(除了少数例外),键可以使用任意的UTF-8 字符。

  • 键不能含有 \0 (空字符),这个字符用于表示键的结尾;
  • .$ 具有特殊意义,只能在特定环境下使用,通常这两个字符是被保留的,如果使用不当的话,驱动程序会有提示。

MongoDB 不但区分类型,还区分大小写,下面的两个文档是不同的:

{
    "foo" : "bar"
}

与:

{
    "Foo" : "bar"
}

另一个重要的事项是, MongoDB 的文档不能有重复的键,例如:

{
    "greeting" : "Hello, world!",
    "greeting" : "Hello, MongoDB!"
}

文档中的键/值对是有序的:

{
    "x" : 1,
    "y" : 2
}

与:

{
    "y" : 2,
    "x" : 1
}

是不同的。

集合

集合就是一组文档。如果将 MongoDB 中的一个文档比喻为关系型数据库中的一行,那么一个集合就相当于一张表。

动态模式

集合是动态模式的,这意味着一个集合里面的文档可是各式各样的,如何下面两个文档就可以出现一个集合里面:

{
    "greeting" : "Hello, world!"
},
{
    "foo" : 5
}

上面的两个文档,除了值不同外,键也不同,但是不建议像上面那样做,同样的文档,我们一般还是放在一个特定的集合里面更好,不管是速度、效率还是结构上来讲,都要更好。

命名

集合使用名称进行标识,集合名称可以是满足下列条件的任意 UTF-8 字符串:

  • 集合名不能是空字符串("");
  • 集合名称不能包含 \0 字符(空字符),这个字符表示集合名的结束;
  • 集合名不能以 system. 开头,这是为系统集合保留的前缀,例如: system.users 这个集合保存着数据库的用户信息,而 system.namespaces 集合保存着所有数据库集合的信息;
  • 用户创建的集合不能在集合名中包含保留字符 $ ,因为某些系统生成的集合中饮食 $

子集合

组织集合的一种惯例是使用 . 分隔不同命名空间的了集合,比如一个具有博客功能的应用可能包含两个集合,分别是 blog.postsblog.authors

数据库

在 MongoDB 中,多个文档组成集合,而多个集合可以组成数据库,一个 MongoDB 可以承载我个数据库,每个数据库拥有 0 个或者多个集合,每个数据库都有独立的权限,即便是在磁盘上,不同的数据库也放置在不同的文件中。

数据库通过名称来标识,这点与集合类似,数据库名可以是满足以下条件的任意 UTF-8 字符串:

  • 不能是空字符串 ""
  • 不能含有 /\."*<>:|?$(一个空格)、\0(空字符);
  • 数据库名区分大小写,即使在不区分大小写的文件系统中也是如此,简单起见,所有的数据库均为小写;
  • 数据库名最多为64个字节。

在这里我们需要记住一点,数据库名最终会变成文件系统中的文件,而数据库名就是相应的文件名,这是数据库名有如此多限制的原因。

另外,有一些数据库名是保留的,可以直接访问这些有特殊语言的数据库,如下:

  • admin

    从身份验证的角度来讲,这是 root 数据库,如果有一个用户添加到这个数据库,则这个用户将拥有所有数据库的权限;

  • local

    这个数据库永远都不可以复制,且一台服务器上所有的本地集合都可以存储在这个数据训中;

  • config

    MongoDB 用户分片设置时,分片信息会存储在 config 数据库中。

把数据库名称添加到集合名称前,得到集合的完全限定名,即 命名空间 namespace,命名空间的长度不得超过 121 字节,而实际应用中,应该小于100字节。

启动 MongoDB

通常,MongoD 作为网络服务器来运行,客户端可连接到该服务器并执行操作。要安装 MongoDB ,可以从官方网站下载相应的适合你系统的版本(http://www.mongodb.org/downloads)。下载完成之后,解压,并把解压之后得到的文件夹复制或者移到至最终你想安装的目录即可。

我将其安装在了 /Users/pantao/Workspace/MongoDB 这个目录,同时,我将 MongoDBbin 目录加入了PATH变量中:

vi ~/.bash_profile

加入如下一行:

export PATH="/Users/pantao/Workspace/MongoDB/bin:$PATH"

保存之后,运行:

source ~/.bash_profile

这个时候,我就可以直接在任何地方启动 MongoDB 或者进行相关的数据库操作了,使用 mongod 命名启动服务器:

pantaodeMacBook-Pro:MongoDB pantao$ mongod
2015-04-21T10:00:06.302+0800 I STORAGE  [initandlisten] exception in initAndListen: 29 Data directory /data/db not found., terminating
2015-04-21T10:00:06.302+0800 I CONTROL  [initandlisten] dbexit:  rc: 100

出现上面这个错误是因为 /data/db 目录不存在,若启动时,不指定任何参数, MongoDB 会默认使用 /data/db 目录存储数据,我们可以使用 --dbpath 来指定其它的路径,比如我使用的是下面这样的命令启动的:

mongod --dbpath /Users/pantao/Workspace/MongoDB/db

上面这个是我的工作目录,直接将 MongoDB 的程序和数据库放在一起,我方便学习管理。

MongoDB Shell

MongoDB 自带有 JavaScript Shell ,可以Shell 中使用命令行与 MongoDB 实例交互,Shell非常有用,通过它可以执行管理操作,检查运行实例,亦或是做其它尝试。

运行 shell

使用 mongo 命令启动 shell

pantaodeMacBook-Pro:MongoDB pantao$ mongo
MongoDB shell version: 3.0.2
connecting to: test
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
    http://docs.mongodb.org/
Questions? Try the support group
    http://groups.google.com/group/mongodb-user
Server has startup warnings: 
2015-04-21T10:03:18.997+0800 I CONTROL  [initandlisten] 
2015-04-21T10:03:18.997+0800 I CONTROL  [initandlisten] ** WARNING: soft rlimits too low. Number of files is 256, should be at least 1000
> 

启动时, shell 会打印出当前 shell 的版本号,连接到了哪个库以及一些帮助信息等,这是一个功能完备的 JavaScript 解释器,可以运行任意 JavaScript 程序,比如使用 JavaScript 标准库或者定义以及调用 JavaScript 函数等。

MongoDB 客户端

shell 是一个独立的 MongoDB客户端,启动时, shell 会连到 MongoDB 服务器的 test 数据库,并将数据库连接赋值给合局变量 db ,这个变量是通过 shell访问 MongoDB 的主要入口点,可以使用 db 查看当前指向哪个数据库:

> db
test

除了JavaScript语法外,MongoDB 还提供了一些语法糖,以帮助我们更好的管理数据库,比如:

> use foobar
switched to db foobar

这个时候我们可以看到数据库已经切换到 foobar 数据库了:

> db
foobar

Shell 中的基本操作

shell 中查看查看或操作数据,会用到4个基本操作:创建读取更新删除,即 CRUD 操作;

创建

通过 db.createCollection() 函数可以先创建一个集合:

> db.createCollection("blog")
{ "ok" : 1 }

insert 可以将一个文档添加到集合中:

> post = {"title": "这是一篇文章", "content": "这是文章的内容。","date" : new Date()}
{
    "title" : "这是一篇文章",
    "content" : "这是文章的内容。",
    "date" : ISODate("2015-04-21T02:22:52.899Z")
}

这是一个有效的 MongoDB 文档,所以可以用 insert 方法将其保存到集合中。

> db.blog.insert(post)
WriteResult({ "nInserted" : 1 })

接着可以使用 find 方法查找这篇文章:

> db.blog.find()
{ "_id" : ObjectId("5535b574b705494e688e218a"), "title" : "这是一篇文章", "content" : "这是文章的内容。", "date" : ISODate("2015-04-21T02:22:52.899Z") }

可以看到我们的数据都已经完整的保存下来了,同时,MongoDB 还为我们自动生成了一个 _id 参数。

读取

findfindOne 方法可以用于查询集合里的文档:

> db.blog.findOne()
{
    "_id" : ObjectId("5535b574b705494e688e218a"),
    "title" : "这是一篇文章",
    "content" : "这是文章的内容。",
    "date" : ISODate("2015-04-21T02:22:52.899Z")
}

findfindOne 可以接受一个查询文档作为限定条件,使用 find 时,shell 会自动显示最多 20 个匹配的文档,也可以获取更多文档。

更新

使用 update 修改博客文章,它至少接受两个参数,第一个是限定条件,第二个是新文档,比如我们现在要给 post 加上评论列表:

> post.comments = []
[ ]

然后,用新版本的 post 替换标题为 《这是一篇文章》的文章:

> db.blog.update({"title":"这是一篇文章"},post)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.findOne()
{
    "_id" : ObjectId("5535b574b705494e688e218a"),
    "title" : "这是一篇文章",
    "content" : "这是文章的内容。",
    "date" : ISODate("2015-04-21T02:22:52.899Z"),
    "comments" : [ ]
}

可以看到,comments 已经更新到原来的那个 post 中去了。

删除

使用 remove 可以删除集合中的文档,若没有任何限定参数,它将删除集合中的所有数据,也可以像下面这样,删除标题为《这是一篇文章》的文章:

> db.blog.remove({"title":"这是一篇文章"})
WriteResult({ "nRemoved" : 1 })
> db.blog.find()
> 

数据类型

基本的数据类型

MongoDB 的文档与 JavaScript 中的对象相近,因而可以认为类似于 JavaScript 中的 JSONJSON 是一种简单的数据表示方式,仅有 6种数据类型,分别为 nullboolnumberstringarrayobject

MongoDB在保留 JSON 基本键/值对特性的基础上,添加了其它的一些数据类型:

null

用于表示空值或者不存在的字段:

{
    "x" : null
}

bool 布尔型

只有两个值 truefalse

{
    "x" : true,
    "y" : false
}

number 数值

shell 默认使用 64位浮点型数值,对于整型值,可使用 NumberInt 类或 NumberLong 类:

{
    "pi" : 3.14,
    "x" : 3,
    "ni" : NumberInt("3"),
    "nl" : NumberLong("3")
}

string 字符串

UTF-8 类型的字符串都可以表示为字符串类型的数据:

{
    "x" : "this is a string.",
    "y" : "这是一个NB的字符串"
}

date 日期

日期被存储 为自新纪元以来经过的毫秒数,不存储时区

{
    "x" : new Date()
}

regular expression 正则表达式

在进行查询时,我们可以直接使用正则表达式作为值,语法与 javascript 的相同:

{
    "x" : /foobar/i
}

array 数组

数据列表或数据集可以表达为数组:

{
    "a" : ["x", "y" , "z"]
}

object id 对象ID

对象ID是一个 12 字节的ID,它是文档的唯一标识:

{
    "x" : ObjectId()
}

object id 以以下方式生成:

|0|1|2|3|4|5|6|7|8|9|10|11|
|  时间戳  | 机器|PID|   计数器   |
  • 时间戳在前,对于索引效率提高有一定的作用;同时它也带了一定的时间信息,一些驱动可以从 ObjectId 获取这些信息;
  • 时间戳与机器码还有 PID 组合在一起,提供了秒级别的唯一性,机器码是机器主机名的散列值(hash);
  • PID确保了同一台主机不同进程产生的 ObjectId 的唯一性,接下来的字节
  • 最后的计数器,确保同一台机器同一个进程在同一秒内产生的 ObjectId 是不一样的,一秒钟最多允许每个进程拥有 2563 个不同的ObjectId。
自动生成 _id

若插入文档时,没有提供 _id

object 内嵌文档

文档可以嵌套其它文档,被嵌套的文档作为父文档的值:

{
    "o" : {
        "name" : "child object"
    }
}

binary data 二进制数据

任意字节的字符串,它不直直接在 shell 中使用,如果要将非 utf-8 字符保存到数据库中,二进制数据是唯一的实现方式。

javascript 代码

查询和文档中可以包括做生意 JavaScript 代码:

{
    "script" : function() { /* code goes here */ }
}

timestamps 时间戳

不同于 Date 类型的时间戳值,它是一个 64位长度的值,它是:

  • 前面的 32位为 time_t 值,(Unix 时间戳)
  • 后32位是一个在给定的时间内的自增值

使用 MongoDB shell

除了像前面那样使用 shell 连接数据库外,我们还可以在连接数据时指定服务器地址、端口号以及数据名等参数,比如:

mongo mongo-db.xingzhewujiang.com:30000/ahaInsight

启动时,可以让 mongo shell 不连接任何的 mongod ,可以通过 --nodb 参数:

mongo --nodb

启动之后,我们可以在需要时执行 new Mongo(hostname) 命令就可以连接到想要连接的 mongod 了:

> conn = new Mongo("host.name:30000")
connection to host.name:30000
> db = conn.getDB("dbname")
dbname

在使用 shell 的过程中,还可以随时使用 help 命令查看帮助:

help
    db.help()                    help on db methods
    db.mycoll.help()             help on collection methods
    sh.help()                    sharding helpers
    rs.help()                    replica set helpers
    help admin                   administrative help
    help connect                 connecting to a db help
    help keys                    key shortcuts
    help misc                    misc things to know
    help mr                      mapreduce

    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
    show logs                    show the accessible logger names
    show log [name]              prints out the last segment of log in memory, 'global' is default
    use <db_name>                set current database
    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
    DBQuery.shellBatchSize = x   set default number of items to display on shell
    exit                         quit the mongo shell

使用 shell 执行脚本

除了交互式的使用 shell 外,我们还可以将命令保存在一个文件中,比如 script.js ,然后使用 mongo 命令直接执行它们,一次可以传入多个文件名,mongo shell 会依次执行传入的脚本,然后退出:

mongo script.js script1.js

如果希望指定主机和端口来运行上面的脚本,还可以这样做:

mongo --quiet host.name:30000/dbname script.js script1.js

--quiet 可以让 mongo shell不打印 MongoDB shell version... 这样的提示信息。

在交互式的命令行中,还可以使用 load() 函数加载并运行脚本:

> load("script.js")
i am a string printed by script.js
>

知道这个之后,我们可以把一些能用的函数保存到一个文件里面,然后再将他们加载进 shell 交互界面里面来:

创建一个名为 connectTo.js 的文件,内容如下:

javascript/**
 * 链接到指定的数据库,然后将 db 指向这个链接
 */
var connectTo = function(port, dbname) {
    if (!port) {
        port = 27017;
    }

    if (!dbname) {
        dbname = "test"
    }

    db = connect("localhost:" + port + "/" + dbname)
    return db
}

然后我们进入 shell

bash> typeof connectTo
undefined
> load("connectTo.js")
true
> typeof connectTo
function
> 

我们可以使用脚本让能用的管理和任务自动化,比如,我们想在每一次 shell 启用时,都加载上面定义的那个函数,这个时候我们可以用到一个名为 .mongorc.js 的文件。

在自己的家目录中(不同的系统都不一样),新建一个名为 .mongorc.js 的文件,然后写入下面这些内容:

print("你好,我来自 .mongorc.js 文件")

然后,重新进入 shell

mongo
MongoDB shell version: 3.0.2
connecting to: test
你好,我来自 .mongorc.js 文件
> 

删除危险的函数

该文件一般用得最多的就是用于删除一些比较危险的 shell 辅助函数,比如删除数据库、索引等,比如下面这样的:

javascriptvar no = function() {
    print("Not no my watch.");
}

// 禁止删除数据库
db.dropDatabase = DB.prototype.dropDatabase = no;

// 禁止删除集合
DBCollection.prototype.drop = no;

// 禁止删除索引
DBCollection.prototype.dropIndex = no;

定制提示信息

shell 默认的提示是一个 > 符号,我们可以对该符号进行定制,比如最简单的是,在每一个提示符前面加上一个当前时间,这样我们就可以很容易大概的知道,一些需要长时间执行的操作到底用了多久时间了:

javascriptprompt = function() {
    return (new Date()) + "> ";
}

再一次进入 shell

bashmongo
MongoDB shell version: 3.0.2
connecting to: test
Tue Apr 21 2015 13:06:40 GMT+0800 (CST)>
Tue Apr 21 2015 13:06:43 GMT+0800 (CST)>db
test

另一个方便的提示是显示当前正在使用的数据库:

javascriptprompt = function() {
    if (typeof db == 'undefined') {
        return '(nodb)>';
    }

    // 检查最后使用的数据库操作
    try {
        db.runCommand({getLastError:1});
    }
    catch (e) {
        print(e);
    }
    return db + "> ";
}

再一次进入 shell

bashmongo
MongoDB shell version: 3.0.2
connecting to: test
test> 

更好的编辑工具

shell 对多行编辑很有限,若编辑了多行,突然发现前面有一行有错误,这是不能修改的,但是它提供了一种方法,让你可以在 shell 中很访问的使用外部的第三方编辑器,保存并退出编辑器之后,会对你修改的值进行重新解析,并重新加载回 shell 中,同样使用 .mongorc.js,在其中加入下面这一行:

EDITOR = "/usr/bin/vim"

然后进入 shell:编辑一个复杂一点儿的变量:

bashmongo
MongoDB shell version: 3.0.2
connecting to: test
> var post = {
... title: "这是一篇文章的内容",
... content: "这是一篇文章的标题",
... author: "潘韬"
... }
> post
{ "title" : "这是一篇文章的内容", "content" : "这是一篇文章的标题", "author" : "潘韬" }
> edit post
> post
{ "title" : "这是一篇文章的标题", "content" : "这是一篇文章的内容", "author" : "潘韬" }
> 

一些变态的集合名称的使用

绝大多数时候,我们可以使用 db.collectionName 这种方式访问一个数据库中的集合,但是也有例外,比如 db.version 就不能访问到名称为 version 的集合,因为 versiondb 的一个方法名,可以用来显示当前的数据库版本信息。那么我们就得使用其它的方法了,比如:

> db.getCollection("version");
test.version

有些时候,我们的集合里面还可能使用了很多变态的奇怪的名称,比如 &$*%#,使用 db.&$*%# 是非法的,但是我们却可以使用 db.getCollection("&$*%#") 来获取该集合,同样的,我们还有一种更加直接的方法,就是类似于JavaScript的数组访问语法,像下面这样:

db["&$*%#"].find()

大胡子民工潘半仙
4.8k 声望758 粉丝