1
上一篇文章:MongoDB指南---1、MongoDB简介
下一篇文章:MongoDB指南---3、MongoDB基础知识-数据类型

MongoDB非常强大但很容易上手。本章会介绍一些MongoDB的基本概念。
文档是MongoDB中数据的基本单元,非常类似于关系型数据库管理系统中的行,但更具表现力。
类似地,集合(collection)可以看作是一个拥有动态模式(dynamic schema)的表。
MongoDB的一个实例可以拥有多个相互独立的数据库(database),每一个数据库都拥有自己的集合。
每一个文档都有一个特殊的键"_id", 这个键在文档所属的集合中是唯一的。
MongoDB自带了一个简单但功能强大的JavaScript shell,可用于管理MongoDB的实例或数据操作。

2.1 文档

文档是MongoDB的核心概念。文档就是键值对的一个有序集。每种编程语言表示文档的方法不太一样,但大多数编程语言都有一些相通的数据结构,比如映射(map)、散列(hash)或字典(dictionary)。例如,在JavaScript 里面,文档被表示为对象:

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

这个文档只有一个键"greeting",其对应的值为"Hello,world!"。大多数文档会比这个简单的例子复杂得多,通常会包含多个键/值对:

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

从上面的例子可以看出,文档中的值可以是多种不同的数据类型(甚至可以是一个完整的内嵌文档,详见2.6.4节)。在这个例子中,"greeting"的值是一个字符串,而"foo"的值是一个整数。
文档的键是字符串。除了少数例外情况,键可以使用任意UTF-8字符。
键不能含有0(空字符)。这个字符用于表示键的结尾。
.和$具有特殊意义,只能在特定环境下使用(后面的章节会详细说明)。通常,这两个字符是被保留的;如果使用不当的话,驱动程序会有提示。
MongoDB不但区分类型,而且区分大小写。例如,下面的两个文档是不同的:

{"foo" : 3}
{"foo" : "3"}

下面两个文档也是不同的:

{"foo" : 3}
{"Foo" : 3}

还有一个非常重要的事项需要注意,MongoDB的文档不能有重复的键。例如,下面的文档是非法的:

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

文档中的键/值对是有序的:{"x" : 1, "y":2}与{"y": 2, "x": 1}是不同的。通常,字段顺序并不重要,无须让数据库模式依赖特定的字段顺序(MongoDB会对字段重新排序)。在某些特殊情况下,字段顺序变得非常重要,本书将就此给出提示。
一些编程语言对文档的默认表示根本就不包含顺序问题(如:Python中的字典、Perl和Ruby 1.8中的散列)。通常,这些语言的驱动具有某些特殊的机制,可以在必要时指定文档的顺序。

2.2 集合

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

2.2.1 动态模式

集合是动态模式的。这意味着一个集合里面的文档可以是各式各样的。例如,下面两个文档可以存储在同一个集合里面:

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

需要注意的是,上面的文档不光值的类型不同(一个是字符串,一个是整数),它们的键也完全不同。因为集合里面可以放置任何文档,随之而来的一个问题是:还有必要使用多个集合吗?这的确值得思考:既然没有必要区分不同类型文档的模式,为什么还要使用多个集合呢?这里有几个重要的原因。
如果把各种各样的文档不加区分地放在同一个集合里,无论对开发者还是对管理员来说都将是噩梦。开发者要么确保每次查询只返回特定类型的文档,要么让执行查询的应用程序来处理所有不同类型的文档。如果查询博客文章时还要剔除含有作者数据的文档,这会带来很大困扰。
在一个集合里查询特定类型的文档在速度上也很不划算,分开查询多个集合要快得多。例如,假设集合里面一个名为"type"的字段用于指明文档是skim、whole还是chunky monkey。那么,如果从一个集合中查询这三种类型的文档,速度会很慢。但如果将这三种不同类型的文档拆分为三个不同的集合,每次只需要查询相应的集合,速度快得多。
把同种类型的文档放在一个集合里,数据会更加集中。从一个只包含博客文章的集合里查询几篇文章,或者从同时包含文章数据和作者数据的集合里查出几篇文章,相比之下,前者需要的磁盘寻道操作更少。
创建索引时,需要使用文档的附加结构(特别是创建唯一索引时)。索引是按照集合来定义的。在一个集合中只放入一种类型的文档,可以更有效地对集合进行索引。
上面这些重要原因促使我们创建一个模式,把相关类型的文档组织在一起,尽管MongoDB对此并没有强制要求。

2.2.2 命名

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

  • 集合名不能是空字符串("")。
  • 集合名不能包含0字符(空字符),这个字符表示集合名的结束。
  • 集合名不能以“system.”开头,这是为系统集合保留的前缀。例如,system.users这个集 合保存着数据库的用户信息,而system.namespaces集合保存着所有数据库集合的信息。
  • 用户创建的集合不能在集合名中包含保留字符'$'。因为某些系统生成的集合中包含$,很多驱动程序确实支持在集合名里包含该字符。除非你要访问这种系统创建的集合,否则不应该在集合名中包含$。

子集合

组织集合的一种惯例是使用“.”分隔不同命名空间的子集合。例如,一个具有博客功能的应用可能包含两个集合,分别是blog.posts和blog.authors。这是为了使组织结构更清晰,这里的blog集合(这个集合甚至不需要存在)跟它的子集合没有任何关系。
虽然子集合没有任何特别的属性,但它们却非常有用,因而很多MongoDB工具都使用了子集合。

  • GridFS(一种用于存储大文件的协议)使用子集合来存储文件的元数据,这样就可以与文件内容块很好地隔离开来。(第6章会详细介绍GridFS。)
  • 大多数驱动程序都提供了一些语法糖,用于访问指定集合的子集合。例如,在数据库shell中,db.blog代表blog集合,而db.blog.posts代表blog.posts集合。

在MongoDB中,使用子集合来组织数据非常高效,值得推荐。

2.3 数据库

在MongoDB中,多个文档组成集合,而多个集合可以组成数据库。一个MongoDB实例可以承载多个数据库,每个数据库拥有0个或者多个集合。每个数据库都有独立的权限,即便是在磁盘上,不同的数据库也放置在不同的文件中。按照经验,我们将有关一个应用程序的所有数据都存储在同一个数据库中。要想在同一个MongoDB服务器上存放多个应用程序或者用户的数据,就需要使用不同的数据库。
数据库通过名称来标识,这点与集合类似。数据库名可以是满足以下条件的任意UTF-8字符串。

  • 不能是空字符串("")。
  • 不得含有/、、.、"、*、<、>、:、|、?、$(一个空格)、0(空字符)。基本上,只能使用ASCII中的字母和数字。
  • 数据库名区分大小写,即便是在不区分大小写的文件系统中也是如此。简单起见,数据库名应全部小写。
  • 数据库名最多为64字节。

要记住一点,数据库最终会变成文件系统里的文件,而数据库名就是相应的文件名,这是数据库名有如此多限制的原因。
另外,有一些数据库名是保留的,可以直接访问这些有特殊语义的数据库。这些数据库如下所示。

  1. admin

从身份验证的角度来讲,这是“root”数据库。如果将一个用户添加到admin数据库,这个用户将自动获得所有数据库的权限。再者,一些特定的服务器端命令也只能从admin数据库运行,如列出所有数据库或关闭服务器。

  1. local

这个数据库永远都不可以复制,且一台服务器上的所有本地集合都可以存储在这个数据库中。(第9章会详细介绍复制及本地数据库。)

  1. config

MongoDB用于分片设置时(参见第13章),分片信息会存储在config数据库中。

把数据库名添加到集合名前,得到集合的完全限定名,即命名空间(namespace)。例如,如果要使用cms数据库中的blog.posts集合,这个集合的命名空间就是cms.blog.posts。命名空间的长度不得超过121字节,且在实际使用中应小于100字节。(参考附录B,了解MongoDB中集合的命名空间及内部表示的更多信息。)

2.4 启动MongoDB

通常,MongoDB作为网络服务器来运行,客户端可连接到该服务器并执行操作。下载MongoDB(http://www.mongodb.org/downloads)并解压,运行mongod命令,启动数据库服务器:

$ mongod
mongod --help for help and startup options
Thu Oct 11 12:36:48 [initandlisten] MongoDB starting : pid=2425 port=27017
    dbpath=/data/db/ 64-bit host=spock
Thu Oct 11 12:36:48 [initandlisten] db version v2.4.0, pdfile version 4.5
Thu Oct 11 12:36:48 [initandlisten] git version:
    3aaea5262d761e0bb6bfef5351cfbfca7af06ec2
Thu Oct 11 12:36:48 [initandlisten] build info: Darwin spock 11.2.0 Darwin Kernel
    Version 11.2.0: Tue Aug 9 20:54:00 PDT 2011;
    root:xnu-1699.24.8~1/RELEASE_X86_64 x86_64 BOOST_LIB_VERSION=1_48
Thu Oct 11 12:36:48 [initandlisten] options: {}
Thu Oct 11 12:36:48 [initandlisten] journal dir=/data/db/journal
Thu Oct 11 12:36:48 [initandlisten] recover : no journal files present, no
    recovery needed
Thu Oct 11 12:36:48 [websvr] admin web console waiting for connections on
    port 28017
Thu Oct 11 12:36:48 [initandlisten] waiting for connections on port 27017

在Windows系统中,执行这个命令:

$ mongod.exe

mongod在没有参数的情况下会使用默认数据目录/data/db(Windows系统中为C:datadb)。如果数据目录不存在或者不可写,服务器会启动失败。因此,在启动MongoDB前,先创建数据目录(如mkdir -p /data/db/),以确保对该目录有写权限,这点非常重要。

sudo mkdir -p /data/db/
sudo chmod -R 777 /data/db

启动时,服务器会打印版本和系统信息,然后等待连接。默认情况下,MongoDB监听27017端口。如果端口被占用,启动将失败。通常,这是由于已经有一个MongoDB实例在运行了。
mongod还会启动一个非常基本的HTTP服务器,监听数字比主端口号高1000的端口,也就是28017端口。这意味着,通过浏览器访问http://localhost:28017,能获取数据库的管理信息。
中止mongod的运行,只须在运行着服务器的shell中按下Ctrl-C。

2.5 MongoDB shell简介

MongoDB自带JavaScript shell,可在shell中使用命令行与MongoDB实例交互。shell非常有用,通过它可以执行管理操作,检查运行实例,亦或做其他尝试。对MongoDB来说,mongo shell是至关重要的工具,其应用之广泛将体现在本书接下来的部分中。

2.5.1 运行shell

运行mongo启动shell:

$ mongo
MongoDB shell version: 2.4.0
connecting to: test

启动时,shell将自动连接MongoDB服务器,须确保mongod已启动。
shell是一个功能完备的JavaScript解释器,可运行任意JavaScript程序。为说明这一点,我们运行几个简单的数学运算:

> x = 200
200
> x / 5;
40

另外,可充分利用JavaScript的标准库:

> Math.sin(Math.PI / 2);
1
> new Date("2010/1/1");
"Fri Jan 01 2010 00:00:00 GMT-0500 (EST)"
> "Hello, World!".replace("World", "MongoDB");
Hello, MongoDB!
再者,可定义和调用JavaScript函数:
> function factorial (n) {
... if (n <= 1) return 1;
... return n * factorial(n - 1);
... }
> factorial(5);
120

需要注意,可使用多行命令。shell会检测输入的JavaScript语句是否完整,如没写完可在下一行接着写。在某行连续三次按下回车键可取消未输入完成的命令,并退回到>-提示符。

2.5.2 MongoDB客户端

能运行任意JavaScript程序听上去很酷,不过shell的真正强大之处在于,它是一个独立的MongoDB客户端。启动时,shell会连到MongoDB服务器的test数据库,并将数据库连接赋值给全局变量db。这个变量是通过shell访问MongoDB的主要入口点。
如果想要查看db当前指向哪个数据库,可以使用db命令:

> db
test

为了方便习惯使用SQL shell的用户,shell还包含一些非JavaScript语法的扩展。这些扩展并不提供额外的功能,而是一些非常棒的语法糖。例如,最重要的操作之一为选择数据库:

> use foobar
switched to db foobar

现在,如果查看db变量,会发现其正指向foobar数据库:

> db
foobar

因为这是一个JavaScript shell,所以键入一个变量会将此变量的值转换为字符串(即数据库名)并打印出来。
通过db变量,可访问其中的集合。例如,通过db.baz可返回当前数据库的baz集合。因为通过shell可访问集合,这意味着,几乎所有数据库操作都可以通过shell完成。

2.5.3 shell中的基本操作

在shell中查看或操作数据会用到4个基本操作:创建、读取、更新和删除(即通常所说的CRUD操作)。

1. 创建

insert函数可将一个文档添加到集合中。举一个存储博客文章的例子。首先,创建一个名为post的局部变量,这是一个JavaScript对象,用于表示我们的文档。它会有几个键:"title"、"content"和"date"(发布日期)。

> post = {"title" : "My Blog Post",
... "content" : "Here's my blog post.",
... "date" : new Date()}
{
    "title" : "My Blog Post",
    "content" : "Here's my blog post.",
    "date" : ISODate("2012-08-24T21:12:09.982Z")
}

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

> db.blog.insert(post)

这篇文章已被存到数据库中。要查看它可用调用集合的find方法:

> db.blog.find()
{
    "_id" : ObjectId("5037ee4a1084eb3ffeef7228"),
    "title" : "My Blog Post",
    "content" : "Here's my blog post.",
    "date" : ISODate("2012-08-24T21:12:09.982Z")
}

可以看到,我们曾输入的键/值对都已被完整地记录。此外,还有一个额外添加的键"_id"。"_id"突然出现的原因将在本章末尾解释。

2. 读取

find和findOne方法可以用于查询集合里的文档。若只想查看一个文档,可用findOne:

> db.blog.findOne()
{
    "_id" : ObjectId("5037ee4a1084eb3ffeef7228"),
    "title" : "My Blog Post",
    "content" : "Here's my blog post.",
    "date" : ISODate("2012-08-24T21:12:09.982Z")
}

find和findOne可以接受一个查询文档作为限定条件。这样就可以查询符合一定条件的文档。使用find时,shell会自动显示最多20个匹配的文档,也可获取更多文档。第4章会详细介绍查询相关的内容。

3. 更新

使用update修改博客文章。update接受(至少)两个参数:第一个是限定条件(用于匹配待更新的文档),第二个是新的文档。假设我们要为先前写的文章增加评论功能,就需要增加一个新的键,用于保存评论数组。
首先,修改变量post,增加"comments"键:

> post.comments = []
[ ]

然后执行update操作,用新版本的文档替换标题为“My Blog Post”的文章:

> db.blog.update({title : "My Blog Post"}, post)

现在,文档已经有了"comments"键。再用find查看一下,可以看到新的键:

> db.blog.find()
{
    "_id" : ObjectId("5037ee4a1084eb3ffeef7228"),
    "title" : "My Blog Post",
    "content" : "Here's my blog post.",
    "date" : ISODate("2012-08-24T21:12:09.982Z"),
    "comments" : [ ]
}

4. 删除

使用remove方法可将文档从数据库中永久删除。如果没有使用任何参数,它会将集合内的所有文档全部删除。它可以接受一个作为限定条件的文档作为参数。例如,下面的命令会删除刚刚创建的文章:

> db.blog.remove({title : "My Blog Post"})

现在,集合又是空的了。

上一篇文章:MongoDB指南---1、MongoDB简介
下一篇文章:MongoDB指南---3、MongoDB基础知识-数据类型

Mark
662 声望344 粉丝

talk is cheap,show me the code