DBA MongoDB 简单概念
进入MongoDB
启动服务
在MongoDB系列的第一篇文章开始时,我们已经介绍了如何将MongoDB设置为sys服务。
但这是远远不够的,我们需要熟悉它到底是怎么做到的。
启动服务的脚本就是下面这个可执行文件:
/usr/local/mongodb/bin/mongod
在没有指定任何参数的情况下,将会启动27017端口用于本地的shell进行登录。
除此之外,mongod还会启动一个基本的HTTP服务器,端口号为28017,这意味着你可以通过浏览器访问http://localhost:28017来获取数据库的管理信息。
如果使用这种方式进行MongoDB服务的启动,你可以指定一些参数,它实在是太多了,所以我推荐你点我进行跳转。
在启动服务时,MongoDB会去加载配置文件,你可以将参数书写在配置文件的配置项中,这是一种更加常用的方式。
对于启动服务,我更推荐使用sys进行启动:
T > systemctl start mongod.service
shell&JavaScript
当服务启动后,你可以通过MongoDB自带的一个JavaScript shell进行本地登录,这个工具非常强大,也是以后会经常用到的。
使用mongo脚本启动shell,该脚本与mongod的目录位置相同,当然如果你设置了系统环境变量,你可以在全局下使用这个命令:
T > mongo
进入shell之后,你会发现提示符变为了>,它是一个JavaScript的解释器,所以你可以在这里面运行任何的JavaScript程序,甚至利用JavaScript的标准库:
> x = 200
200
> x / 5
40
尝试使用JavaScript的标准库Math:
> Math.sin(Math.PI / 2);
1
> new Date("2020/1/1");
ISODate("2019-12-31T16:00:00Z")
> "Hello, World!".replace("World","MongoDB");
Hello, MongoDB!
>
你也可以定义和调用JavaScript函数(支持多行):
> function func(n){
... if (n < 10) return "小于10";
... return "大于10";
... }
> func(11);
大于10
如果想退出shell,则可以使用exit命令。
shell&MongoDB
shell的真正威力在于他是一个MongoDB的客户端,开启时,会自动链接到MongoDB服务器的test数据库下,并且将该库链接赋值给全局变量db,该变量存储的信息类似于SQL中show databases;语句所打印的结果。
如下所示:
> db
test
你可以使用一些非JavaScript语法的语句,也就是说在输入某些命令时不必指定;为结束符,如同上面的db命令。
需要注意的是,所有的打印结果均为string类型。
获取帮助信息
在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
...
在库级别获取帮助,可以使用db.help()命令。
而在集合这一级别获取帮助,你可以使用任意的一个集合名,如db.coll.help()。
同时,上面的help()帮助文档中也明确的指出,你可以使用sh.help()与rs.help()命令来获取分片和集群相关的帮助。
如果你想获取当前MongoDB的版本信息,可以使用db.version()命令。
在忘记某个命令全拼时,你也可以使用TAB键对命令进行补全,这都是非常实用的操作。
如果对一个函数,不加括号则可以查看到他的源代码:
> db.version
function() {
return this.serverBuildInfo().version;
}
数据库
库的概念
MongoDB的一个库由多个集合组成,一个MongoDB实例可以承载多个数据库。
每个库都有独立的权限控制,在磁盘上,每个库会存放到不同的文件中。
如果想在同一个MongoDB服务器上存放多个应用或者用户数据,就需要使用不同的库。
命名规则
库名应当是utf8字符串构成,以下有一些限制条件,满足即可成功创建库:
- 库名不能是空字符
- 库名不得含有‘’(空格)、.(点)、$(美元符)、/(正斜杠)、\(反斜杠)和\0(空字符)
- 库名应当全部小写
- 库名最多64字节(utf8,一中文最多3字节,一英文一个字节)
内置库
有一些内置的库,注意你的自定义库不要与他们重名。
- admin:从权限角度来看,这是一个root数据库,如果将一个用户添加至该数据库中,该用户可视为管理员
- local:这个库的数据永远不会被复制,可以用来存储仅限于本地单台服务器的任意集合,主从复制不会被作用于该库之上
- config:这个库是在Mongo用于分片设置时使用,config数据库在内部使用,用于保存分片的相关信息
集合
集合概念
集合实际上是一组文档,由多个单独的文档构成,类似于关系型数据库中的表。
对于MongoDB来说,集合是没有任何限制的,这种状态被称之为无模式(无字段、无约束)。
例如,下面两个文档可以存放在同一个集合中:
{"name" : "Jack"}
{"foo" : 5}
上面的两个文档,不光键不同,而且连值的类型都不同,一个是string,一个是int。
集合中可以放置任意文档,那么还有必要使用多个集合吗?
使用多集合的理由如下:
- 方便管理、方便查询
- 如果集合的数据格式不统一,查询速度会大大增加
- 将集合进行合理归类,大幅度减少磁盘寻道时间
- 创建索引时,文档会有附加的结构,将同种类型文档放入一个集合中,可以让索引更有效
命名规则
集合名应当是utf8字符串构成,以下有一些限制条件,满足即可成功创建集合:
- 集合名不能是空字符串
- 集合名不能含有\0字符,这个字符会用来表明集合名字的结尾
- 集合名不能以system.开头,这是系统集合保留的前缀,如system.users这个集合保存数据库用户信息
- 集合名不能含有保留字符$
命名空间
将数据库名字放在集合前,会组成集合的完全限定名,称之为命名空间。
如,在cms数据库中使用blog.posts集合,该集合的命名空间为cms.blog.posts。
命名空间长度不得超过121字节,实际使用中应当少于100字节。
文档
文档概念
文档是MongoDB的核心概念,多个键及其关联的值有序地进行放置在一起就是一个文档。
每种编程语言对文档的表示方法都不一样,如在JavaScript中,它被表示为对象,而在Python中则被表示为字典:
{"name" : "Jack"}
该文档中只有一个键name,当然也可以包含多个键:
{"name" : "Jack", "age" : 18}
键与值
参照上面的示例,可以得出以下几个十分重要的概念:
- 我们在构建一个文档时,应当考虑前一个文档的排列顺序,并且最好将它做到有序,如下这两个文档的键排列就是无序的。
{"name" : "Jack", "age" : 18}
{"age" : 18, "name" : "Jack"}
- 文档中的值,不仅仅可以是字符串,也可以是其他数据类型(甚至是一个嵌入的新文档)
{
"name" : "Jack",
"hobby" : [
"games",
"music"
],
"grades" : {
"MongoDB" : 80,
"MySQL" : 73,
"Es" : 62
}
}
键的命名
键的名字应当是utf8字符串构成,以下有一些限制条件,满足即可成功创建一组键值对:
- 键名不能含有\0字符,这个字符会用来表明键名的结尾
- .和$具有特别的含义,只有在特定环境中才可允许使用,不要轻易使用它们
- 以下划线_开头的键是被保留的,虽然这是一个不太严格的要求
MongoDB中不单区分类型,还区分大小写,如下,这两个文档是不同的:
{"name" : "Jack"}
{"Name" : "Jack"}
还有一个非常重要的事项需要注意,MongoDB中文档不能含有重复的键,如下,这个文档是非法的:
{"name" : "Jack", "name" : "Tom"}
数据类型
基本数据类型
MongoDB的文档类似于JSON,与JavaScript中的对象,Python中的字典等都非常相似。
如果你熟悉一些编程语言,相信你对JSON这种格式已经非常了解了。
对于MongoDB这种数据库来说,JSON还是不够强大,它仅仅只能表明null,布尔,数字,字符串,数组和对象几种类型。
或许对于普通的应用程序来说或许它的表现力已经足够,但JSON没有日期类型这会使处理本来简单的日期问题变得十分繁琐,此外JSON只有一种数字类型而没办法区分浮点数和整数,更不能区分32位数字和64位数字,这对一款数据库来说功能上其实显得有点捉襟见肘。
故此,MongoDB在JSON基本的键值对基础上增加了一些其他的数据类型,如下所示,MongoDB自带的shell所支持的数据类型:
- null:表示空值或则不存在的字段,如
- 布尔:布尔类型有true和false两个值,如
- 32位整数:shell中该类型不可用,JavaScript仅支持64位浮点数,故32位整数会被自动转换
- 64位整数:shell中该类型不可用,相反,shell会使用一个特殊的内嵌文本来显示64位整数
- 64位浮点数:shell中的数字都这种类型,不管看起来有没有小数点
- 字符串:utf8字符串都可以表示为字符串类型的数据
- 符号:shell中不支持这种类型,shell将数据库里的符号类型转换成字符串
- 对象id:对象id是文档中12字节的唯一ID,类似于PRIMARY KEY,是一个objectId()对象
- 日期:日期类型从UNIX纪元开始,存储为时间戳形式,这其实是调用了JavaScript中的方法,如
- 正则表达式:文档中可以包含正则表达式,采用JavaScript的正则表达式语法,如
- 代码:文档中可以包含JavaScript代码,如 {“x” : function() {/*…*/}}
- 二进制数据:二进制数据可以由任意字节组成,不过在shell中无法使用
- 最大值:BSON中包含的一种特殊类型,表示可能的最大值,shell中没有这个类型
- 最小值:BSON中包含的一种特殊类型,表示可能的最小值,shell中没有这个类型
- 未定义:文档中可以使用未定义,需要注意JavaScript中的null和undefined是两种不同的类型(好像新版不支持了)
- 数组:值的集合或者列表可以表示成数组,如
- 内嵌文档:文档中可以包含别的文档,也可以作为值嵌入到父文档中
BSON是JSON的升级版,可以理解为超集,关于BSON的内容后面文章会有写到
数字
JavaScript中只有1中数字类型,而MongoDB中有3种(32位整数、64位整数、64位浮点数)。
在shell中,shell必须绕过JavaScript这种机制,shell中数字都会被MongoDB当做双精度数,这意味着你从数据库中取出的是一个32位整数,而修改文档中存回去后,该整数也会变为浮点数。索引尽量不要在shell下覆盖整个文档。
如果你在使用MongoDB的shell遇到了一些问题,可以参照MongoDB权威指南2.6.2中去寻找答案,我实在是不想写了...
日期
其实在调用日期时,应当调用new Date()而不是调用Date()。
直接调用Date()和调用new Date()其实是有一些差别的,Date()是MongoDB提供的,而new Date()是JavaScript提供的:
> Date()
Wed Mar 10 2021 12:49:19 GMT+0800 (CST)
> new Date()
ISODate("2021-03-10T04:49:26.675Z")
如果不是调用new Date()而是调用Date(),则可能导致与字符串匹配出现问题(仅个人理解,翻译本非常晦涩,看不懂):
数组
MongoDB中的数组同JavaScript中一样,允许任意类型。
如一个嵌套的二维数组,一个文档,一个字符串,一个数字….
MongoDB对于数组可以进行深入理解,你可以在数组中进行增删改查等操作,当然也能够去建立索引。
内嵌文档
MongoDB中的整个文档可以作为另一个集合下文档中键的值。
同数组一样,MongoDB也能对内嵌文档进行深入理解,你可以在内嵌文档中进行增删改查等操作,当然也能够去建立索引。
而关于内嵌文档,则可能会导致一些意外的情况发生。
比如,我有很多个文档中都内嵌了class文档:
{
"name" : "Jack",
"gender" : "male",
"class" : {
"id" : 1
"name" : "三年级一班",
}
}
现在,这个class内嵌文档对于三年级一班来说要将id改为101,则意味着我们需要修改的内嵌文档数据量与外层文档的数量有着直接的关系,而在关系型数据库中,这两个表通常都是拆分开的,你只需要修改class中三年级一班的id即可,显然在MongoDB中如果要进行对于内嵌文档的使用,还是要经过深思熟虑的。
主键
MongoDB对于每一个文档,都有一个名为_id的主键。
如果你没在插入文档时指定该主键,则会自动生成一个12字节的objectId对象,MongoDB中关于主键的性质与MySQL中相同,都是非空且唯一。
为何MongoDB中主键不用自动增长的数字,而是采用objectId()进行生成呢?
这是由于MongoDB的适用场景与MySQL有些许不同,MongoDB更适合做分布式数据库系统,这意味着主键必须不光在一台机器上唯一,而且要在多台机器上唯一。而使用自增长的_id则在同步时显得更加费时又费力。
objectId的组成有以下几个部分:
前4字节为Unix纪元开始的时间戳,单位为秒,这样会带来很多有用的属性:
- 时间戳与后5个字节(机器、进程号)组合起来,提供秒级别的唯一性
- 时间戳在前有一个好处,在下一个文档_id生成时会按照顺序进行排列插入,将其作为索引能提高查询效率
- 前4个字节隐含了该文档的创建时间,绝大多数驱动都会公开一个方法从objectId中获取这一信息
接下来的3字节为所在主机唯一标识符,由机器主机名散列值生成,可以确保不同主机生成的objectId不同
进程标识符(2字节)用来确保同一机器上多个进程产生的objectId是唯一的
计数器标识符(后3字节)是一个自增的计数器,用来确保相同进程同一秒中生成的objectId也不一样,同一秒钟最多允许每个进程产生256**3=16777216个不同的objectId