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

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

一、文档

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

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

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

  • 键不能含有\0(空字符)。这个字符用于表示键的结尾。
  • .$具有特殊意义,只能在特定环境下使用。
  • MongoDB不但区分类型,而且区分大小写。下面两个文旦是不同的:
{"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字符串。

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

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

三、 数据库

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

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

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

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

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

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

四、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

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

{"x" : null}
  • 布尔值
    布尔类型有两个值truefalse
{"x" : true}
  • 数值
    shell默认使用64位浮点数值。
{"x" : 3.14}
{"x" : 3}

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

{"x" : NumberInt("3")}
{"x" : NumberLong("3")}
  • 字符串
    UTF-8字符串都可表示为字符串类型的数据
{"x" : "foobar"}
  • 日期
    日期被存储为自新纪元来经过的毫秒数,不存储时区:
{"x" : new Date()}
  • 正则表达式
    查询时,使用正则表达式作为限定条件,语法也与JS的正则表达式语法相同:
{"x" : /foobar/i}
  • 数组
    数据列表或数据集可以表示为数组
{"x" : ["a", "b", "c"]}
  • 内嵌文档
    文档可嵌套其他文档,被嵌套的文档作为父文档的值
{"x" : {"foo" : "bar"}}
  • 对象id
    对象id是一个12字节的ID,是文档的唯一标识。
{"x" : ObjectId()}

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

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

  • 代码
    查询和文档中可以包括任意JS代码

{"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,不产生冲突。为了确保在同一台机器上并发的多个进程产生的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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容