- 文档是MongoDB中数据的基本单元,非常类似于关系型数据管理系统的行,但更具表现力
- 类似地,集合(collection)可以看做是一个拥有动态模式(dynamic schema)的表
- MongoDB的一个实例可以拥有多个相互独立的数据库(database),每一个数据库都拥有自己的集合。
- 每一个文档都有一个特殊的键,"_id",这个键在文档所属的集合中是唯一的。
- MongoDB自带了一个简单但功能强大的JavaScript shell,可用于管理MongoDB的实例或数据操作
2.1 文档
文档是MongoDB的核心概念。文档就是键值对的一个有序集。比如映射(map)、散列(hash)或字典(dictionary)。例如,在JavaScript里面,文档被表示为对象:
{"greeting":"Hello,world!"}
{"greeting":"Hello,world!","foo":3}
文档中的数据类型可以是不同的数据类型。
文档的键是字符创。除了少数例外情况,键可以使用任意UTF-8字符。
- 键不能含有\0(空字符)。这个字符用于表示键的结尾
- .和$具有特殊意义,只能在特定环境下使用。通常,这两个字符是保留的;如果使用不当的话,驱动程序有提示
MongoDB区分大小写:
{"foo":3}和{"foo":"3"}不同
{"Foo":3}和{"foo":3}不同
MongoDB文档不能有重复的键。
文档中的键/值对是有序的:{"x":1,"y":2}与{"y":2,"x":1}是不同的。
2.2 集合
集合就是一组文档。如果将MongoDB中的一个文档比喻为关系型数据库中的一行,那么一个集合就相当于一张表。
2.2.1 动态模式
集合是动态模式的。这意味着一个集合里面的文档可以是各式各样的。
{"greeting":"Hello,world!"}和{"foo":5}可以存储在同一个集合中。
既然没有必要区分不同类型文档的模式,为什么还要使用多个集合呢?这里有几个重要的原因:
- 如果把各种各样的文档不加区分地放在同一个集合里,无论对开发者还是对管理员来说都将是噩梦。
- 在一个集合里查询特定类型的文档在速度上也很不划算,分开查询多个集合要快得多
- 把同种类型的文档放在一个集合里,数据会更加集中。
- 创建索引时,需要使用文档的附加结构(特别是创建唯一索引时)
2.2.2 命名
集合使用名称进行标识。集合名可以是满足下列条件的任意UTF-8字符串
- 集合名不能是空字符串("")。
- 集合名不能包含\0字符(空字符),这个字符表示集合名的结束
- 集合名不能以"system."开头,这是为系统集合保留的前缀。例如,system.users这个集合保存着数据库的用户信息,而system.namespaces集合保存着所有数据库集合的信息。
- 用户创建的集合不能在集合名中包含保留字符"$"。
子集合:组织集合的一种惯例是使用"."分隔不同命名空间的子集和。如blog.posts和blog.authors,这里的blog集合(这个集合甚至不需要存在)跟它的子集合没有任何关系。
2.3 数据库
在MongoDB中,多个文档组成集合,而多个集合可以组成数据库。一个MongoDB实例可以承载多个数据库,每个数据库拥有0个或者多个集合。每个数据库都有独立的权限,即便是在磁盘上,不同的数据库也放置在不同的文件中。
数据库名可以是满足以下条件的任意UTF-8字符串:
- 不能是空字符串("")。
- 不得含有/,\,.,",*,<,>,:,|,?,$(一个空格),\0(空字符)。基本上,只能使用ASCII中的字母和数字。
- 数据库名区分大小写,
- 数据库名最多为64字节
另外,有一些数据库名是保留的,可以直接访问这些有特殊语义的数据库。这些数据库如下所示。
- admin
从身份验证的角度来讲,这是"root"数据库。
- local
这个数据库永远都不可能复制,且一台服务器上的所有本地集合都可以存储在这个数据库中。
- config
MongoDB用于分片设置是时,分片信息会存储在config数据库中。
2.4 启动MongoDB
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.4.0.tgz
/** 目录/opt **/
cd /opt
tar zxvf mongodb-linux-x86_64-3.4.0.tgz
/** 重命名 **/
mv mongodb-linux-x86_64-3.4.0.tgz mongodb
/** 进入mongodb目录 **/
cd mongodb
/** 创建db和日志目录 **/
mkdir /opt/mongodb/data
mkdir /opt/mongodb/data/db
mkdir /opt/mongodb/data/logs
/** logs目录下创建mongodb.log文件 **/
touch mongodb.log
/** 在mongodb/data目中创建mongodb.conf **/
cd data
vi mongodb.conf
/** 加入相关配置 **/
#端口号
port = 27017
#数据目录
dbpath = /opt/mongodb/data/db
#日志目录
logpath = /opt/mongodb/data/logs/mongodb.log
#设置后台运行
fork = true
#日志输出方式
logappend = true
#开启认证
#auth = true
cd .. /* 返回上一层目录:mongodb文件夹*/
./bin/mongod --config /opt/mongodb/data/mongodb.conf
vim /etc/profile
# mongo
export PATH=$PATH:/opt/mongodb/bin
export PATH=$PATH:/opt/mongodb/bin
mongodb启动监听27017端口,同时还会启动一个非常基本的HTTP服务器,监听数字比主端口号高1000的端口,也就是28017.
2.5 MongoDB shell简介
> x=200
200
> x/5;
40
> Math.sin(Math.PI/2);
1
> new Date("2010/1/1");
ISODate("2009-12-31T16:00:00Z")
> "Hello,World!".replace("World","MongoDB");
Hello,MongoDB!
> function factorial(n){
... if(n<=1) return 1;
... return n*factorial(n-1);
... }
> factorial(5);
120
查看db当前指向那个数据库,可以使用db命令:
> db
test
选择数据库
> use foobar
switched to db foobar
> db
foobar
2.5.3 shell中的基本操作
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("2018-03-18T23:10:33.590Z")
}
> db.blog.insert(post)
WriteResult({ "nInserted" : 1 })
> db.blog.find()
{ "_id" : ObjectId("5aaef21b031a94c0f439fbf5"), "title" : "My Blog Post", "content" : "Here's my blog post.", "date" : ISODate("2018-03-18T23:10:33.590Z") }
2.读取
find和findOne方法可以用于查询集合里的文档。若只想查看一个文档,可用findOne:
> db.blog.findOne()
{
"_id" : ObjectId("5aaef21b031a94c0f439fbf5"),
"title" : "My Blog Post",
"content" : "Here's my blog post.",
"date" : ISODate("2018-03-18T23:10:33.590Z")
}
find和findOne可以接受一个查询文档作为限定条件。
3.更新
使用update修改博客文章。update接收(至少)两个参数:第一个时限定条件(用于匹配待更新的文档),第二个是新的文档。
> post.comments=[]
[ ]
> db.blog.update({title:"My Blog Post"},post)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("5aaef21b031a94c0f439fbf5"), "title" : "My Blog Post", "content" : "Here's my blog post.", "date" : ISODate("2018-03-18T23:10:33.590Z"), "comments" : [ ] }
4.删除
使用remove方法可将文档从数据库中永久删除。如果没有指定任何参数,它会将集合内的文档全部删除。它可以接收一个作为限定条件的文档作为参数。
> db.blog.remove({title:"My Blog Post"})
WriteResult({ "nRemoved" : 1 })
现在,集合又是空的了。
2.6数据类型
2.6.1 基本数据类型
- null
null用于表示空值或者不存在的字段:{"x":null}
- 布尔型
布尔类型有两个值true和false:{"x":false}
- 数值
shell默认使用64为浮点型数值。因此,以下数值在shell中是很"正常"的 {"x":3.14}或{"x":3}
对于整型值,可使用NumberInt类(4字节)或NumberLong类(8字符){"x":NumberInt("3)} {"x":NumberLong("3")}
- 字符串
UTF-8字符串都可表示为字符串类型的数据:{"x":"foobar"}
- 日期
日期被存储为自新纪元一来的毫秒数,不存储时区:{"x":new Date()}
- 正则表达式
查询时,使用正则表达式作为限定条件,语法也与JavaScript的正则表达式语法相同:{"x":/foobar/i}
- 数组
数据列表或数据集可以表示为数组:{"x":["a","b","c"]}
- 内嵌文档
文档可嵌套其他文档,被嵌套的文档作为父文档的值:{"x":{"foo":"bar"}}
- 对象id
对象id是一个12字节的ID,是文档的唯一标识。{"x":ObjectId()}
- 二进制数据
- 代码
查询和文档中可以包括任意JavaScript代码:{"x":function(){/*...*/}}
2.6.2 日期
关于JavaScript日期类的完整解释,以及构造函数的参数格式,参见ECMAScript规范http://www.ecmascript.org
2.6.3 数组
数组是一组值,它既能作为有序对象(如列表、栈或队列),也能作为无序对象(如数据集)来操作。
{"things":["pie",3.14]} "things"这个键的值是一个数组
2.6.4 内嵌文档
{
"name":"John Doe",
"address":{
"street":"123 Park Street",
"city":"Anythown",
"state":"NY"
}
}
2.6.5 _id和ObjectId
MongoDB中存储的文档必须要有一个"_id"键。这个键可以是任何类型的,默认是个ObjectId对象。
1.ObjectId
ObjectId是"_id"的默认类型。他设计成轻量型的,不同机器都能使用全局唯一的同种方法方便地生成它,ObjectId使用12字节的存储空间,是一个有24个十六进制数字组成的字符串。如果快速地创建多个ObjectId,会发现每次最后几位数字的变化。另外中间的几位数字也会变化。这是ObjectId的创建方式导致的。ObjectId的12字节按照如下方式生成:
- 时间戳,与随后的5字节组合起来,提供了秒级别的唯一性
- 由于时间戳在前,这意味着ObjectId大致会按照插入的顺序排列。
- 这4个字节也隐含了文档创建的时间。绝大多数驱动程序都会提供一个方法,用于从ObjectId获取这些信息。
接下来的3字节是所在主机的唯一标识符。通常是机器主机名的散列值(hash)。这样就可以确保不同主机生成不同的ObjectId,不产生冲突。
为了确保在同一机器上并发的多个进程产生的ObjectId是唯一的,接下来的两字节来自产生ObjectId的进程的进程标识符(PID)。
2.自动生成_id
如果插入文档没有"_id",系统会自动帮你创建一个。MongoDB的哲学:能交给客户端程序来做的事情就不要交给服务器来做。
2.7 使用MongoDB shell
也可以:
2.7.1 shell小贴士
例如,查看update函数的工作机制,
2.7.2 使用shell脚本执行
[root@localhost code]# cat defineConnectTo.js
/**
* 连接到指定的数据库,并且将db指向这个连接
*/
var connectTo = function(port, dbname) {
if (!port) {
port = 27017;
}
if(!dbname) {
dbname = "test";
}
db = connect("localhost:"+port+"/"+dbname);
return db;
};
如果在shell中加载这个脚本,connectTo函数就可以使用了。
> typeof connectTo
undefined
> load('defineConnectTo.js')
true
> typeof connectTo
function
默认情况下,shell会在运行shell时所处的目录中查找脚本(可以使用run("pwd")命令查看)。如果脚本不再当前目录中,可以为shell指定一个相对路径或者绝对路径。例如,如果脚本放置在~/my-scripts目录中,可以使用load("/home/myUser/my-scripts/defineConnectTo.js")命令来加载defineConnectTo.js。注意,load函数无法解析~符号。
2.7.3 创建.mongorc.js文件
如果某些脚本会被频繁加载,可以将它们添加到mongorc.js文件中。这个文件会在启动shell时自动执行。
例如,我们希望启动成功时让shell显示一句欢迎语。为此,我们在用户主目录下创建一个名为.mongorc.js的文件,向其中添加如下内容:
// mongorc.js
var compliment = ["attractive", "intelligent", "like Batman"];
var index = Math.floor(Math.random()*3);
print("Hello,you're looking particularly"+compliment[index]+"today!");
[root@localhost code]# mongo
MongoDB shell version v3.4.0
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.4.0
Hello,you're looking particularlyattractivetoday!
为了实用,可以使用这个脚本创建一些自己需要的全局变量,或者是为太长的名字创建一个简短的别名,也可以重写内置的函数。.mongorc.js最常见的用途之一是移除那些比较"危险"的shell辅助函数。
var no = function() {
print("Not on my watch.");
};
// 禁止删除数据库
db.dropDatabase = DB.prototype.dropDatabase = no;
// 禁止删除集合
DBCollection.prototype.drop = no;
// 禁止删除索引
DBCollection.prototype.dropIndex = no;
如果在启动shell时指定--norc参数,就可以禁止加载.mongorc.js。
2.7.4 定制shell提示
将prompt变量设为一个字符串或者函数,就可以重写默认的shell提示。
另一个方便的提示是显示当前使用的数据库:
2.7.5 编辑复合变量
shell的多行支持是非常有限的:不可以编辑之前的行。为了方便地调用编辑器,可以在shell中设置EDITOR变量(也可以在环境变量中设置):
> EDITOR="/usr/bin/emacs"
现在,如果想要编辑一个变量,可以使用"edit 变量名"这个命令,比如:
> var wap = db.books.findOne({title:"War and Peace"})
edit wap
在.mongorc.js文件中添加一行内容,EDITOR="编辑器路径";,以后不必单独设置EDITOR变量了。
2.7.6 集合命令注意事项
可以使用db.collectionName获取一个集合的内容,但是,如果集合名称中包含保留字或者无效的JavaScript属性名称,db.collectionName就不能正常工作了。
假设要访问version集合,不能直接使用db.version,因为db.version是db的一个方法
> db.version
function () {
return this.serverBuildInfo().version;
}
为了访问version集合,必须使用getCollection函数:
> db.getCollection("version");
test.version
如果集合名称中包含无效的JavaScript属性名称(比如foo-bar-baz和123abc),也可以使用这个函数来访问相应的集合。
还有一种方法可以访问以无效属性名称命名的集合,那就是使用数组访问语法;在JavaScript中,x.y等同于x['y']。
如果需要对blog的每一个子集合进行操作,可以使用如下方式进行迭代:
而不必这样:
注意,不能使用db.blog.i,这样会被解释为test.blog.i,而不是test.blog.posts。必须使用db.blog[i]语法才能将i解释为相应的变量。
可以使用这种方式来访问那些名字怪异的集合:
> var name = "@#&!"
> db[name].find()
直接使用db.@#&!进行查询时非法的,但是可以使用db[name]。