我爱编程

2、MongoDB基础知识(1)(MongoDB笔记)

2017-05-10  本文已影响153人  yjaal

这里是阅读了《MongoDB权威指南》后做的相关笔记。

一、文档

文档是MongoDB的核心概念。文档就是键值对的一个有序集合。在JS中,文档被表示为对象:

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

文档的键是字符串。除了少数例外情况,键可以使用任意UTF-8字符。

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

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

{"foo" : 3}
{"Foo" : 3}
{"x" : 1, "y" : 2}
{"y" : 2, "x" : 1}

二、集合

集合就是一组文档。

2.1 动态模式

集合是动态的,也就是说,一个集合里的文档可以是各式各样的。如下面的文档可以同时存在于同一个集合中。

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

2.2 命名

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

子集合
组织集合的一种管理是使用"."分隔不同命名空间的子集合。如,一个具有博客功能的应用可能包含两个集合,分别是blog.posts、blog.authors。这是为了使组织结构更清晰,这里的blog集合跟其子集合没有任何关系。

三、 数据库

MongoDB中,多个文档年组成集合,而多个集合可以组成数据库。数据库通过名称来标识,这点与集合类似。数据库名可以是满足以下条件的任意UTF-8字符串。

注意:数据库最终会变成文件系统里的文件,而数据库名就是相应的文件名。另外有一些数据库名是保留的,可以直接访问这些有特殊语义的数据库。

四、MongoDB简介

4.1 shell 中的基本操作

4.1.1 数据库查看

db //表示查看当前使用的是哪个数据库
show dbs //查看所有的数据库
use test //更换数据库为test

4.1.2 创建

1
说明:这里创建一个名为post的局部变量,这是一个JS对象,用于表示文档。注意,换行的时候不能直接使用回车,应先打//对回车进行转义。这个对象是一个有效的MongoDB文档,使用insert方法可以将其保存到blog集合中。
2
说明:如上,将文档保存到集合中之后,可以使用find()方法查看。同时,可以看到曾输入的完整文档,还有一个额外添加的键"_id"。其实这就和主键类似。

4.1.3 读取

这里可以使用方法findfindOne查询集合里的文档。若只想查看一个文档,可用findOne

3
这两个方法可以接受一个查询文档作为限定条件。这样就可以查询符合一定条件的文档。使用find时,shell会自动显示最多二十个匹配的文档,也可以获得更多文档。

4.1.4 更新

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

4
说明:这里先修改post变量,增加了"comments"键。然后执行update操作,其中第一个参数表示限定条件,即匹配标题为"My Blog Post"的文章。

4.1.5 删除

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

5

五、数据类型

5.1 基本数据类型

在概念上,MongoDB的文档与JS中的对象相近,因而可认为它类似于JSONJSON是一种简单的数据表示方式:其规范仅用一段文字就能描述清楚,且仅包含六种数据类型。这其中只有null、布尔、数字、字符串、数组和对象这几种数据类型,所以JSON的表达能力有一定的局限。如JSON没有日期类型。只有一种数字类型,无法区分浮点数和整数,更别说区分32位和64位数字了。基于此原因,MongoDB在保留JSON基本键值对的基础上,添加了其他一些数据类型。

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

{"x" : null}
{"x" : true}
{"x" : 3.14}
{"x" : 3}

对于整型值,可使用NumberInt类(表示四字节带符号整数)或NumberLong类(表示八字节带符号整数),分别举例如下:

{"x" : NumberInt("3")}
{"x" : NumberLong("3")}
{"x" : "foobar"}
{"x" : new Date()}
{"x" : /foobar/i}
{"x" : ["a", "b", "c"]}
{"x" : {"foo" : "bar"}}
{"x" : ObjectId()}

还有一些不那么常用,但可能有需要的类型,包括下面这些。

{"x" : function(){/*...*/}}

5.2 日期

JS中,Date类型可以用作MongoDB的日期类型。创建日期对象时,应使用new Date(...),而非Date(...)。如果将构造函数作为函数进行调用(即不包括new的方式),返回的是日期的字符串表示,而非日期(Date)对象。shell根据本地时区设置显示日期对象。然而,数据库中存储的日期仅为新纪元以来的毫秒数,并未存储对应的时区。(当然,可将时区信息存储为另一个键的值)。

5.3 数组

数组是一组值,它既能作为有序对象(如列表、栈或队列),也能作为无无序对象(如数据集)来操作。在下面的文档中,"things"这个键的值是一个数组:

{"things" : ["pie", 3.14]}

此例表示,数组可包含不同数据类型的元素(在此,是一个字符串和一个浮点数)。实际上,常规的键值对支持的所有值都可以作为数组的值,数组中甚至可以嵌套数组。

文档中的数组有个奇妙的特性,就是MongoDB能理解其结构,并知道如何深入数组内部对其内容进行操作。这样就能使用数组内容对数组进行查询和构建索引了。如之前的例子中,可以查询出"things"数组中包含3.14这个元素的所有文档。还可以使用原子更新对数组内容进行修改,这在后面讲解。

5.4 内嵌文档

文档可以作为键的值,这样的文档就是内嵌文档。如用一个文档来表示一个人,同时还要保存他的地址,可以将地址信息保存在内嵌的“address”文档中:

{
  "name" : "Tom",
  "address" : {
    "street" : "123 Park Street",
    "city" : "Anytown",
    "state" : "NY"
  }
}

同数组一样,MongoDB能够理解内嵌文档的结构, 并能深入其中构建索引,执行查询或更新。在关系型数据库中,这个例子中的文档一般会被拆分成两个表中的两个行("people""address"各一行)。而MongoDB中直接将地址文档嵌入到人员文档中。这样做的坏处就是会导致更多的数据重复。假设"address"是关系数据库中的一个独立的表,我们需要修正地址中的错误。当我们对"people""address"执行连续操作时,使用这个地址的每个人的信息都会得到更新。但是在MongoDB中,则需要对每个人的文档分别修正拼写错误。

5.5 _id 和 ObjectId

MongoDB中存储的文档必须有一个"_id"键。这个键的值可以是任何类型的,默认是个ObjectId对象。在一个集合里面,每个文档都有唯一的"_id",确保集合里面每个文档都能被唯一标识。如果有两个集合的话,两个集合可以都有一个"_id"的值为123(一个数据库中集合名不能重复),但是每个集合里面只能有一个文档的"_id"值为123

5.5.1 ObjectId

ObjectId"_id"的默认类型。它设计成轻量型的,不同的机器都能用全局唯一的同一种方法方便的生成它。这是MongoDB不采用其他比较常规的做法(比如自动增加的主键)的主要原因,因为在多个服务器上同步自动增加主键既费时又费力。

ObjectId使用十二字节的存储空间,是一个由二十四个十六进制数字组成的字符串(每个字节可以存储两个十六进制数字)。如果快速连续创建多个ObjectId,会发现每次只有最护几位数字有变化。另外,中间的几位数字也会变化(要是在创建的过程中停顿几秒)。这是ObjectId的创建方式导致的。其十二字节按照如下方式生成:

6
说明:ObjectId的前四个字节是从标准纪元开始的时间戳,单位为秒。这样会带来一些有用的属性。

用户不必担心多服务器时钟同步的问题,因为时间戳的实际值并不重要,只要它总是不停增加就好了(每秒一次)。接下来的三字节是所在主机的唯一标识。通常是机器主机名的散列值。这样就可以确保不同主机生成不同的ObjectId,不产生冲突。为了确保在同一台机器上并发的多个进程产生的ObjectId是唯一的,接下来的两字节来自产生ObjectId的进程的进程标识符(PID)。

前九字节保证了同一秒钟不同机器不同进程产生的ObjectId是唯一的。最后三字节是一个自动增加的计数器,确保相同进程同一秒产生的ObjectId也是不一样的。一秒钟最多允许每个进程拥有2563个不同的ObjectId

5.5.2 自动生成 _id

在创建文档的时候,如果文档没有插入"_id"键,系统会自动创建一个。

六、使用 MongoDB shell

在之前已经讲过,可以使用命令mongo 127.0.0.1:12345/test连接相关数据库。而同时我们启动MongoDB的时候不连接到任何MongoDB有时也很方便。通过--nodb参数启动shell,启动时就不会连接到任何数据库:mongo --nodb。启动之后,在需要时运行new Mongo(hostname)命令就可以连接到想要的MongoDB了:

> conn = new Mongo("some-host:12345")
connection to some-host:12345
> db = conn.getDB("test")
test

6.1 shell 小贴士

可以使用help命令查看相关帮助,可以通过db.help()查看数据库级别的帮助,使用db.test.help()查看集合级别的帮助。如果想知道一个函数是做什么用的,可以直接在shell输入函数名(函数名后面不要加小括号即可),这样就可以看到相应函数的JS实现代码。

> db.test.update

6.2 使用shell 执行脚本

我们在MongoDB中可以直接执行相关的JS脚本,比如现在给出一个脚本script1.js(这里只是给出了一个脚本文件,其实可以同时给出多个脚本文件)。其内容如下:

print("Hello World");

执行时,首先启动MongoDB,然后使用命令

sudo bin/mongo 127.0.0.1:27017/test script1.js

执行。

7
说明:这里要注意脚本文件所在的目录(使用正确的路径),而且这里还没有连接服务器。这里使用print()函数将内容输出到标准输出,而且可以加入--quiet参数(sudo bin/mongo --quiet 127.0.0.1:27017/test script1.js)可以不打印MongoDB shell version...之类的信息。当然,在连接服务器之后也可以执行脚本文件。如下:
8

在脚本中可以访问db变量,以及其他全局变量。然而,shell辅助函数(如"use db")不可以在文件中使用。这些辅助函数都有对应的JS函数,如表所示:

辅助函数 等价函数
use foo db.getSisterDB("foo")
show dbs db.getMongo().getDBs()
show collections db.getCollectionNames()

可以使用脚本将变量注入到shell。例如,下面的脚本对于本书的复制和分片部分内容非常有用。这个脚本定义了一个connectTo()函数,它连接到指定端口处的一个本地数据库,并且将db指向这个连接。
defineConnectTo.js

var connectTo = function(port, dbname){
    if(!port){
        port = 27017;
    }
    if(!dbname){
        dbname = "test";
    }
    db = connect("localhost:" + port + "/" + dbname);
    return db;
};

如果在shell中加载这个脚本,connectTo函数就可以使用了。

9

除了添加辅助函数,还可以使用脚本将通用的任务和管理活动自动化。默认情况下,shell会在运行shell时所处的目录中查找脚本(可以使用run("pwd")命令查看)。如果脚本不在当前目录中,可以为shell指定一个相对路径或者绝对路径。如可以使用load("/usr/yj-software/my-mongodb/defineConnectTo.js")来加载defineConnectTo.js。注意,load函数无法解析~符号。也可以在shell中使用run()函数来执行命令行程序。可以在函数参数列表中指定程序所需的参数:

10
通常来说,这种使用方式的局限性很大,因为输出格式很奇怪,而且不支持管道。
上一篇下一篇

猜你喜欢

热点阅读