概念
NoSQL 的功能,类型,含义,优势
MongoDB 介绍
MongoDB 将数据存储在类似 JSON 的文档中,并且文档中每个 json 串结构可能有所不同。相关信息存储在一起,通过 MongoDB 查询语言进行快速查询访问。 MongoDB 使用动态模式,这意味着您可以在不首先定义结构的情况下创建记录,例如字段或其值的类型。您可以通过添加新字段或删除现有记录来更改记录的结构(我们称之为文档)。该数据模型可以让您轻松地代表层次关系,存储数组和其他更复杂的结构。集合中的文档不需要具有相同的一组字段,数据的非规范化是常见的。MongoDB 还设计了高可用性和可扩展性,并提供了即用型复制和自动分片功能。
MongoDB 与 MySQL 对比
概念
MySQL | MongoDB |
---|---|
表 | 集合 |
行 | 文档 |
列 | 字段 |
joins | 嵌入文档或者链接 |
特色对比
MySQL | MongoDB | |
---|---|---|
丰富的数据模型 | 否 | 是 |
动态 Schema | 否 | 是 |
易于编程 | 否 | 是 |
复杂事务 | 是 | 否 |
自动分片 | 否 | 是 |
为什么使用 MongoDB
- MongoDB 能更快地构建应用程序,处理高度多样化的数据类型,并更有效地管理应用程序。
- 简化了开发,因为 MongoDB 文档自然映射到现代的面向对象编程语言。使用 MongoDB 可以避免将代码中的对象转换为关系表的复杂对象关系映射(ORM)层。
- MongoDB 的灵活数据模型意味着数据库模式可以随业务需求而发展。而关系型数据库的表很难扩展。
- 随着在数据量和吞吐量方面的增长,MongoDB 可轻松扩展,无需停机,无需更改应用程序。相比之下,要扩展 MySQL 的规模往往需要大量的定制工程。
什么时候用 MySQL
虽然大多数现代应用程序需要一个灵活的可扩展系统,如 MongoDB,但是有一些关系数据库(如 MySQL)将更适合使用的情况:需要复杂的多行事务的应用程序。
一个具体的例子是订票系统背后的预订引擎,它通常涉及复杂的事务。
虽然核心预订引擎可能在 MySQL 上运行,但是与用户互动的应用程序部分如提供内容、与社交网络集成、管理会话等使用 MongoDB 将会更好。
数据库操作
数据库
db // 当前数据库
show dbs // 显示所有数据库
show databases // 显示所有数据库
use DB_NAME // 切换到某个数据库
:::info
在数据库未创建的情况下,直接 use DB_NAME
,再调用 db.COLLECTION.insert()
插入数据会自动创建数据库。
:::
集合
查看所有集合:
show collections
show tables
db.getCollectionNames()
创建集合:
db.createCollection('COLLECTION_NAME')
:::info 在集合未创建的情况下,直接插入数据也可自动创建集合。 :::
删除集合:
db.COLLECTION_NAME.drop()
重命名:
db.COLLECTION_NAME.renameCollection('NEW_NAME')
文档
插入文档:
db.COLLECTION_NAME.insertOne(DOCUMENT)
db.COLLECTION_NAME.insertMany([DOCUMENT, DOCUMENT, ...])
db.COLLECTION_NAME.insert(...) // 参数既可以是一个文档,也可以是一个文档数组
:::warning
插入文档时如果没有指定 _id
字段,MongoDB 会自动添加一个 _id
字段作为主键,类型为 ObjectId
:::
查询文档:
db.COLLECTION_NAME.find(QUERY, PROJECTION) // 返回所有
db.COLLECTION_NAME.findOne(QUERY, PROJECTION) // 返回第一个
:::info
- 这里的
PROJECTION
写法和下面 聚合 中的$project
相似。 - 某字段是数组的情况:如,数据库中某条数据的
tag
字段的值是["tag1", "tag2", "tag3"]
,那么QUERY
写成{tag: "tag1"}
或者{tag: "tag2"}
或者{tag: "tag3"}
都能将这条数据查出来。 ::: :::warning 注:没有findMany()
。 :::
更新文档:
db.COLLECTION_NAME.updateOne(QUERY, UPDATE) // 更新指定字段,只更新查到的第一个文档
db.COLLECTION_NAME.updateMany(QUERY, UPDATE) // 更新指定字段,更新查到的所有文档
db.COLLECTION_NAME.update(QUERY, UPDATE) // 同 updateMany()
// 样例:db.user.update({ username: "admin" }, { $set: { desc: "admin user" } })
// 样例解释:相当于 SQL 中的 `UPDATE user SET desc='admin user' WHERE username='admin'
db.COLLECTION_NAME.save(DOCUMENT) // 通过传入的文档来替换已有文档,_id 主键存在就更新,不存在就插入
:::warning
注:motor
没有 update
和 save
方法,只能使用 update_one
和 update_many
方法。
:::
删除文档:
db.COLLECTION_NAME.remove(QUERY)
:::warning
注:motor
中的删除是 delete_many
,没有 remove
。
:::
计数:
db.COLLECTION_NAME.count(QUERY)
查询语法
条件操作符:
db.COLLECTION_NAME.find({age: {$gt: 18}}) // 大于:$gt
// 其他还有:小于($lt)、大于等于($gte)、小于等于($lte)
db.COLLECTION_NAME.find({age: {$gt: 18, $lt: 22}}) // 多个条件操作符
查询结果交、并集:
db.COLLECTION_NAME.find({$or: [QUERY1, QUERY2]}, PROJECTION) // 执行多个查询语句,并取并集
db.COLLECTION_NAME.find({$and: [QUERY1, QUERY2]}, PROJECTION) // 执行多个查询语句,并取交集
正则表达式:
db.COLLECTION_NAME.find({name: {$regex: 'ma'}}) // 返回 name 包含 'ma' 的所有文档
// 注意:在编程语言中,正则表达式 'ma' 匹配时只会返回 'ma';
// 而在 MongoDB 中会返回 name 中包含 'ma' 的所有文档,即 'ma' 和 '.*ma.*' 的作用是一样的
db.COLLECTION_NAME.find({name: {$regex: '^ma', $options: 'gi'}})
// $options 可以指定一些选项,如 'i' 表示忽略大小写。这与 JS 中是一样的
db.COLLECTION_NAME.find({name: /^ma/gi})
// 正则表达式的简化写法,与 JS 中写法相同
查询修饰符
.skip(NUMBER) // 跳过 NUMBER 个文档
.limit(NUMBER) // 限制文档数量
.sort({age: -1}) // 按 age 降序排序。1 表示升序,-1 表示降序
游标
关于游标
当使用 db.COLLECTION_NAME.find()
在集合中搜索文档时,结果将返回指向文档集合的指针,该指针称为游标。
默认情况下,返回查询结果时,游标将自动进行迭代输出。
若想自己迭代,可以这样写:
let cursor = db.COLLECTION_NAME.find(QUERY, PROJECTION)
while (cursor.hasNext()) {
print(to_json(cursor.next()))
}
cursor.xxx()
.pretty() // 格式化显示
.forEach(printjson) // 查询结果以 json 格式展示。(目前来看和 .pretty() 效果类似)
.forEach(<function>) // 遍历查询结果,对每个文档调用定义好的函数
// 完全支持 JS 中编写函数的方法,也支持箭头函数。
// 打印用 print() 不能用 console.log()
// 上面的 printjson 其实就是一个预定义好的函数
.hasNext() // 是否还有下一个文档
.next() // 返回下一个文档
聚合
联合查询的一个示例:
db.user.aggregate([ // 聚合操作。和管道类似,列表中每个操作的输出作为下一个操作的输入
{
$lookup: { // 用于多集合关联查询。相当于 SQL 的 **左连接**
localField: "_id", // 当前集合的字段
from: "user_roles", // 目标集合
foreignField: "user_id",// 目标集合的字段
as: "temp" // 新字段命名
}
}, {
$project: { // 过滤某些字段,0 表示过滤,1 表示显示
_id: 0,
username: 1,
temp: 1
}
}, {
$unwind: { // 将数组展开
path: "$temp",
preserveNullAndEmptyArrays: true // 保留空数组
}
}, {
$lookup: {
localField: "temp.role_id", // 内嵌文档的字段要用 . 访问
from: "role",
foreignField: "_id",
as: "temp2"
}
}, {
$unwind: {
path: "$temp2",
preserveNullAndEmptyArrays: true
}
}, {
$project: {
username: 1,
role: "$temp2.name" // 这里相当于给字段起别名,可以用来将内嵌文档中的字段提出来
}
}
]);
索引
每个集合的 _id
字段默认就有索引,而且 无法删除。
索引的默认命名规则样例:column1_1_column2_-1
,解释:这是一个联合索引,建立在 column1
和 column2
两个字段上,其中 column1
升序,column2
降序。注:_id
索引的名字不太一样,是 _id_
。
建立索引:
db.COLLECTION_NAME.ensureIndex({
column1: 1, // 1 表示升序
column2: -1 // -1 表示降序
});
删除索引:
db.COLLECTION_NAME.dropIndex("column1_1_column2_-1"); // 删除单个索引
db.COLLECTION_NAME.dropIndexes(); // 删除除 _id 索引之外的所有索引
获取索引信息:
db.COLLECTION_NAME.getIndexes();
数据库管理
备份与恢复
备份:
$ mongodump [-h localhost:27017] [-d test] [-o /data]
// -h 参数指定 mongod 服务。默认为 localhost:27017
// -d 参数指定要备份的数据库。若未指定该参数则备份所有数据库
// -o 参数指定要备份到哪儿。默认为当前目录
:::info
备份目录的结构:
dump
├── admin
│ ├── system.version.bson
│ └── system.version.metadata.json
└── test
├── roles.bson
├── roles.metadata.json
├── user.bson
├── user.metadata.json
├── user_roles.bson
└── user_roles.metadata.json
:::
恢复:
$ mongorestore [-h localhost:27017] [-d test] ./test
安全认证
内置角色
角色 | 权限描述 |
---|---|
read | 可以读取指定数据库中任何数据 |
readWrite | 可以读写指定数据库中任何数据,包括创建、重命名、删除集合 |
userAdmin | 可以在指定数据库中创建、修改、删除用户(除 config 和 local 数据库外) |
dbAdmin | 可以读取指定数据库以及对数据库进行清理、修改、压缩、获取统计信息、执行检查等操作。 |
readAnyDatabase | 可以读取所有数据库中任何数据(除 config 和 local 数据库外) |
readWriteAnyDatabase | 可以读写所有数据库中任何数据(除 config 和 local 数据库外) |
userAdminAnyDatabase | 可以在任何数据库创建、修改、删除用户(除 config 和 local 数据库外) |
dbAdminAnyDatabase | 可以读取任何数据库以及对数据库进行清理、修改、压缩、获取统计信息、执行检查等操作(除 config 和 local 数据库外) |
backup | 备份数据最小的权限 |
restore | 从备份文件中还原恢复数据(除 system.profile 集合)的权限 |
clusterAdmin | 可以对整个集群或数据库系统进行管理操作 |
root | 超级账号,最大权限 |
:::warning MongoDB 可以自定义角色,但不推荐使用,内置角色已经够用了。 :::
用户管理
创建用户:
db.createUser({
user: "user1", // 用户名
pwd: "user1pwd", // 密码
roles: [ // 角色列表。可以是空数组,即没有任何权限
{ role: "read", db: "test" } // 赋予该用户对 test 数据库的读权限
],
customData:{ // 一些自定义的信息
description:"user1",
email: "xxx@example.com"
}
});
删除用户:
db.dropUser('user1');
修改密码:
db.changeUserPassword("username", "xxx")
修改用户信息:
db.runCommand({
updateUser: "username",
pwd: "xxx",
customData: { title:"xxx" }
});
查看当前数据库中的用户:
show users;
:::warning 注:
- 在哪个数据库创建用户,用户信息就会保存在哪个数据库,
db.auth
就要在哪个数据库用。但也仅仅如此,并不是说在 test 数据库创建的用户只能有 test 数据库的权限,有哪个数据库的权限取决于创建用户时 roles -> db 字段中指定的数据库名。 - xxxAnyDatabase 一类的角色必须在 admin 数据库中创建。 :::
服务器开启身份认证
MongoDB 默认没有开启身份认证,即任何人都可以做任何操作。
使用以下任意一种方式可以开启身份认证:
- 修改配置文件,将
auth=true
这一行取消注释。 - 启动 mongod 服务时添加
--auth
参数。
客户端连接后,使用 db.auth('user', 'pwd')
来验证身份,之后就可以进行该用户可以进行的操作了。
其他
数据模型设计
使用数据的时候,一个数据项常常和另外的一个或多个数据项产生关系,比如一个“人”对象,有一个名字,可能有多个电话号码,以及多个子女,等等。
在 SQL 数据库中,关系被分为一个个表,在表中,每个数据项以主键标识,而一个表的主键又作为另一个表的外键,在两个表之间引用。当遇上多对多关系的时候,还需要一个额外的关联表,将多对多关系转化成两个一对多关系。
而在 MongoDB 中,表示关系有两种办法:
- 嵌套。即一个文档中包裹子文档;
引用链接。又可以分为两种:
手动引用。这种方式其实就完全相当于 SQL 数据库中设置外键的方式。
DBRefs。DBRefs 是一种约定,约定一种引用链接的书写格式,而不是特定的引用类型。除了
_id
字段中的值之外,它们还包括集合的名称,在某些情况下还包括数据库的名称。
如 Python 的 pymongo 库会提供自动解析 DBRefs 的功能,这样就不用手动解析了,这就是 DBRefs 的作用。否则 DBRefs 其实跟手动引用差不多。
下面的文章中介绍了如何使用 pymongo 自动解引用:
当数据项之间的关系是 1 对 n,且 n 的那一方数据量较少时,可以使用嵌套的方式。如一个人有多个手机号。
如果是 n 对 n 的关系,或者 1 对 n 中 n 的那一方数据量较多,最好使用手动引用的方式。
如果很多个集合之间相互引用,可以考虑使用 DBRefs 方式,并用程序自动解析,便于编程。
BSON 和 JSON
BSON:Binary JSON
MongoDB 使用 BSON 结构存储数据
Python 中的 bson 库
安装 pymongo 时会自动安装一个 bson 库,使用该 bson 库,不要自己手动安装 bson 库,用不了,可能是版本的问题。
bson 库就是用来操作 BSON 格式的库。
其他一些小点
- 在 admin 数据库中执行
db.shutdownServer()
可以关闭 mongodb 服务。 ObjectId
:BSON 中的一种数据类型,_id
字段都是该类型。