
深入学习mongdb(零) mongodb特性、文档、集合、数据库、用户创建和身份验证
一、mongodb的特性
1.非关系型数据库
MongoDB是一个面向文档(document-oriented)的非关系型数据库,而不是关系型数据库。
与关系型数据库相比,面向文档的数据库不再有“行”(row)的概念,取而代之的是更为灵活的“文档”(document)模型。通过在文档中嵌入文档和数组,面向文档的方法能够仅使用一条记录来表现复杂的层次关系,这与使用现代面向对象语言的开发者对数据的看法一致。在mongodb中,文档相当于mysql的行,集合(collection)相当于mysql的表。
mongodb文档的键(key)和值(value)不再是固定的类型和大小。由于没有固定的模式。这意味着一个集合的不同的文档可以有不同的字段(不同数量或不同字段名),以及字段的类型没有限制(文档1的name字段可以存字符串,文档2的name字段可以存数组,不规定某字段必须存某个类型)。
由于是非关系型数据库,因此mongodb不支持join连接查询。
2.易于扩展
由于需要存储的数据量不断增长,开发者面临一个困难:应该如何扩展数据库?实质上,这是纵向扩展(scale up)和横向扩展(scale
out)之间的选择。纵向扩展就是使用计算能力更强的机器,而横向扩展就是通过分区将数据分散到更多机器上。
MongoDB的设计采用横向扩展。MongoDB能自动处理跨集群的数据和负载,自动重新分配文档,以及将用户请求路由到正确的机器上。如果一个集群需要更大的容量,只需要向集群添加新服务器即可。
相比于mysql而言,mongodb更适合做分布式存储。
MongoDB并不具备一些在关系型数据库中很普遍的功能,如连接(join)和复杂的多行事务(multirow
transaction)。省略这些功能是出于架构上的考虑(mongodb的分片和集群特性),因为在分布式系统中这两个功能难以高效地实现。
MongoDB非常强大并试图保留关系型数据库的很多特性,但它并不追求具备关系型数据库的所有功能。
二、mongodb的基础概念
文档 是MongoDB中数据的基本单元,类似于关系型数据库中的行。
集合 (collection)可以看作是一个拥有动态模式(dynamic schema)的表。
数据库 (database),每一个数据库都拥有自己的集合。
每一个文档都有一个特殊的键"_id" , 这个键在文档所属的集合中是唯一的。
MongoDB自带了一个简单但功能强大的JavaScript shell
,可用于管理MongoDB的实例或数据操作。在mongodb命令行中我们可以使用原生的js语法对mongodb执行增删改查的命令。
MongoDB不但区分类型,而且区分大小写,且一个文档不能有重复的键。
集合是动态模式的。这意味着一个集合里面的多个文档的字段可以不同。例如,下面两个文档可以存储在同一个集合里面:
{"greeting":"Hello, world!", "name":"zbp"}{"foo":5}
一个集合中可以创建一个子集合,如blogs.posts就是blogs集合下的posts子集合。但是集合和子集合在结构上没有关联关系,他们只是通过命名来帮助开发者知道两个集合在逻辑上是父集合和子集合的关系。例如blogs和blogs.posts是两个独立的集合,删除blogs之后,blogs.posts依旧存在。创建blogs.posts这个集合的时候可以不需要先创建blogs集合。
mongodb的数据库中,以下几个库是系统原有的:
admin:存储mongodb的root超级管理员用户信息。如果将一个用户添加到admin数据库,这个用户将自动获得所有数据库的权限。
local:一台服务器上的所有本地集合都可以存储在这个数据库中。这个数据库永远都不可以复制。
config:用于分片设置时,分片信息会存储在config数据库中。
命名空间
数据库和集合通过分隔符"."组合在一起形成一个完整的命名空间。例如 cms.blogs.posts表示cms这个库的blogs集合的posts子集合。
三、mongodb shell以及简单操作
启动mongodb服务命令
mongod
如果希望mongod在后台运行可以执行(Linux环境下,默认使用/data/db作为数据目录)
nohup mongod &
在windows环境下,如果自定义安装目录,那么在启动mongodb服务的时候需要制定数据存储的路径,否则启动服务会报错。
mongod -dbpath D:\mongodb\data\db
mongod
在没有参数的情况下会使用默认数据目录/data/db(Windows系统中为C:\data\db)。如果数据目录不存在或者没有写权限,服务器会启动失败。默认情况下,MongoDB监听27017端口。如果端口被占用,启动将失败。通常,这是由于已经有一个MongoDB实例在运行了。
mongod
还会启动一个非常基本的HTTP服务器,监听数字比主端口号高1000的端口,也就是28017端口。这意味着,通过浏览器访问http://localhost:28017
,能获取数据库的管理信息。
中止mongod 的运行,只须在运行着服务器的shell中按下Ctrl-C。
打开mongodb客户端连接mongodb服务:
mongo
指定ip和端口连接mongodb服务:
mongo 127.0.0.1:27018
mongodb客户端是一个基于js语法的shell。启动mongodb会自动连接到test数据库。
查看当前使用数据库
db // db是一个存储当前使用数据库的全局变量
切换到zbp这个库
use zbp
在某行连续三次按下回车键可取消未输入完成的命令,并退回到>-提示符。
mongodb shell中可以存储的几种数据结构(基本上都是js的数据结构):
null - 用于表示空值或者不存在的字段;
布尔型;
数值 - 不区分整型或浮点型;
字符串;
日期 - new Date(),日期对象;
数组;
内嵌文档 - 即json对象;
对象id - 对象id是一个12字节的ID,是文档的唯一标识;
二进制数据;
创建一个文档并插入一条数据:
use zbp // 使用zbp这个库(这个如果这个库不存在也可以使用)db // zbpdb.createCollection("blogs.posts") // 创建blogs.posts这个集合。此时并不会创建blogs集合。show collections // 显示当前库的所有集合。只有blogs.postsposts = { // 将一个json对象复制给posts变量 "title" : "mongodb介绍", "category_id" : 10, "tags" : [ "t1", "t2", "t3" ], "comment" : { "Bob" : "nice", "Peter" : "not so good", "Tim" : "that's ok" }, "create_time" : ISODate("2021-02-22T01:44:46.582Z"), "is_crawled" : false}
db.blogs.posts.insert(posts) // 往blogs.posts集合中插入一条数据
db.blogs.posts.find() // 查看posts这个表的文档数据(默认返回20条文档),得到结果如下//{ "_id" : ObjectId("603310997b3be5dde108f378"), "title" : "mongodb介绍", "category_id" : 10, "tags" : [ "t1", "t2", "t3" ], "comment" : { "Bob" : "nice", "Peter" : "not so good", "Tim" : "that's ok" }, "create_time" : ISODate("2021-02-22T01:44:46.582Z"), "is_crawled" : false }
db.blogs.posts.find().pretty() // 可以将查询结果格式化
db.blogs.posts.drop() // 删除blogs.posts这个集合
_id和ObjectId
MongoDB中存储的文档必须有一个唯一的"_id" 键。这个键的值可以是任何类型的,默认是个ObjectId
对象。这个_id是集合的主键,mongodb没有使用自增的数字id作为主键的原因是由于mongodb的分布式特性,设计MongoDB的初衷就是用作分布式数据库,而在多个服务器上同步唯一的自动增加主键值既费力又费时。因此使用ObjectId而不是自增数字为默认的_id主键。
ObjectId
使用12字节的存储空间,是一个由24个十六进制数字组成的字符串(每个字节8个位,一个十六进制数字占4个位,因此一个字节可以存储两个十六进制数字)。其结构如下:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11时间戳 | 机器 | PID | 计数器
ObjectId 的前4个字节是从标准纪元开始的时间戳,单位为秒。由于时间戳在前,这意味着ObjectId
大致会按照插入的时间顺序排列。但是这个是没有保证的,仅仅是“大致”。
接下来的3字节是所在主机的唯一标识符。通常是机器主机名的散列值(hash)。这样就可以确保不同主机生成不同的 ObjectId ,不产生冲突。
接下来的2字节是进程id(PID)。这样确保在同一台机器上并发的多个进程产生的ObjectId
是唯一的。前9字节保证了同一秒钟不同机器不同进程产生的ObjectId 是唯一的。最后3字节是一个自动增加的计数器,确保相同进程同一秒产生的ObjectId
也是不一样的。一秒钟最多允许每个进程 拥有256^3 (16 777 216)个不同的ObjectId
如果插入文档时没有"_id"
键,系统会自动帮你创建一个。自动生成一个_id这件事是由mongodb的客户端来完成的,这样做是为了减轻mongodb服务端的负担。
四、身份验证
我们可以在mongodb的任何数据库中通过createUser()或者addUser()方法(该方法已被废弃)添加用户。
admin(管理员)和local(本地)是两个特殊的数据库,它们当中的用户可对任何数据库进行操作。这两个数据库中的用户可被看作是超级用户。
管理员用户可对任何 数据库进行读写,同时能夠执行某些只有管理员才能执行的命令,如listDatabases 和shutdown
。已开启安全检查的数据库在被启动前,应至少添加一个管理员用户。
现在我们在admin这个库和zbp这个库分别添加一个用户user_admin 和 user_zbp
user_zbp = { "user" : "user_zbp", "pwd" : "123456", "roles" : [ { "role" : "root", "db" : "admin" } ]}
user_admin = { "user" : "user_admin", "pwd" : "123456", "roles" : [ { "role" : "root", "db" : "admin" } ]}
use admindb.createUser(user_admin) // 创建root角色。root角色默认拥有所有权限
use zbpdb.createUser(user_zbp)
关闭服务(在命令行执行db.shutdownServer()或者再Linux shell执行kill命令皆可)。
再次启动服务,并指定需要权限认证:
mongod -dbpath D:\mongodb\data\db -auth
这样启动服务后,如果在客户端直接使用mongo不加任何参数的连接,虽然能连接成功,但是没有权限操作数库。
我们需要指定用户名和密码:
mongo -u user_admin -p 123456
此时我们就可以操作数据库和集合。
如果我们使用 user_zbp 登录
mongo -u user_zbp -p 123456
会发现报错说验证失败。原因是user_zbp是在zbp这个库添加的用户,只有在admin或local这两个库添加的用户才是全局生效的。
如果我们想使用user_zbp这个用户登录可以这样:
// --authenticationDatabase表示使用test库创建的user_zbp用户登录mongo -u user_zbp -p 123456 --authenticationDatabase zbp
或者
mongo // 先直接连接use test // 切换到test库db.auth("user_zbp", "123456") // 登录
添加用户权限的原理:
无论是在哪个数据库执行的createUser()所添加的用户,这些用户信息都被记录到了admin数据库的system.users这个集合中。
如果想查看用户信息,可以这样:
use admin db.system.users.find().pretty()
删除一个或多个用户就跟删除集合中的文档一样:
//删除用户名为user_zbp的用户db.system.users.remove({"user" : "user_zbp"})
如果我们使用类似于 Compass 这样的客户端连接工具进行连接的话,可以使用下面的连接字符串进行连接。
mongodb://user_admin:123456@localhost:27017