概念

NoSQL 的功能,类型,含义,优势

MongoDB 介绍

MongoDB 将数据存储在类似 JSON 的文档中,并且文档中每个 json 串结构可能有所不同。相关信息存储在一起,通过 MongoDB 查询语言进行快速查询访问。 MongoDB 使用动态模式,这意味着您可以在不首先定义结构的情况下创建记录,例如字段或其值的类型。您可以通过添加新字段或删除现有记录来更改记录的结构(我们称之为文档)。该数据模型可以让您轻松地代表层次关系,存储数组和其他更复杂的结构。集合中的文档不需要具有相同的一组字段,数据的非规范化是常见的。MongoDB 还设计了高可用性可扩展性,并提供了即用型复制和自动分片功能

MongoDB 与 MySQL 对比

概念

MySQL MongoDB
集合
文档
字段
joins 嵌入文档或者链接

特色对比


MySQL MongoDB
丰富的数据模型
动态 Schema
易于编程
复杂事务
自动分片

为什么使用 MongoDB

  1. MongoDB 能更快地构建应用程序,处理高度多样化的数据类型,并更有效地管理应用程序。
  2. 简化了开发,因为 MongoDB 文档自然映射到现代的面向对象编程语言。使用 MongoDB 可以避免将代码中的对象转换为关系表的复杂对象关系映射(ORM)层。
  3. MongoDB 的灵活数据模型意味着数据库模式可以随业务需求而发展。而关系型数据库的表很难扩展。
  4. 随着在数据量和吞吐量方面的增长,MongoDB 可轻松扩展,无需停机,无需更改应用程序。相比之下,要扩展 MySQL 的规模往往需要大量的定制工程。

什么时候用 MySQL

虽然大多数现代应用程序需要一个灵活的可扩展系统,如 MongoDB,但是有一些关系数据库(如 MySQL)将更适合使用的情况:需要复杂的多行事务的应用程序

一个具体的例子是订票系统背后的预订引擎,它通常涉及复杂的事务。
虽然核心预订引擎可能在 MySQL 上运行,但是与用户互动的应用程序部分如提供内容、与社交网络集成、管理会话等使用 MongoDB 将会更好。

数据库操作

数据库

  1. db // 当前数据库
  2. show dbs // 显示所有数据库
  3. show databases // 显示所有数据库
  4. use DB_NAME // 切换到某个数据库

:::info 在数据库未创建的情况下,直接 use DB_NAME,再调用 db.COLLECTION.insert()插入数据会自动创建数据库。 :::

集合

查看所有集合:

  1. show collections
  2. show tables
  3. db.getCollectionNames()

创建集合:

  1. db.createCollection('COLLECTION_NAME')

:::info 在集合未创建的情况下,直接插入数据也可自动创建集合。 :::

删除集合:

  1. db.COLLECTION_NAME.drop()

重命名:

  1. db.COLLECTION_NAME.renameCollection('NEW_NAME')

文档

插入文档:

  1. db.COLLECTION_NAME.insertOne(DOCUMENT)
  2. db.COLLECTION_NAME.insertMany([DOCUMENT, DOCUMENT, ...])
  3. db.COLLECTION_NAME.insert(...) // 参数既可以是一个文档,也可以是一个文档数组

:::warning 插入文档时如果没有指定 _id 字段,MongoDB 会自动添加一个 _id 字段作为主键,类型为 ObjectId :::

查询文档:

  1. db.COLLECTION_NAME.find(QUERY, PROJECTION) // 返回所有
  2. db.COLLECTION_NAME.findOne(QUERY, PROJECTION) // 返回第一个

:::info

  1. 这里的 PROJECTION 写法和下面 聚合 中的 $project 相似。
  2. 某字段是数组的情况:如,数据库中某条数据的 tag 字段的值是 ["tag1", "tag2", "tag3"],那么 QUERY 写成 {tag: "tag1"} 或者 {tag: "tag2"} 或者 {tag: "tag3"} 都能将这条数据查出来。 ::: :::warning 注:没有 findMany()。 :::

更新文档:

  1. db.COLLECTION_NAME.updateOne(QUERY, UPDATE) // 更新指定字段,只更新查到的第一个文档
  2. db.COLLECTION_NAME.updateMany(QUERY, UPDATE) // 更新指定字段,更新查到的所有文档
  3. db.COLLECTION_NAME.update(QUERY, UPDATE) // 同 updateMany()
  4. // 样例:db.user.update({ username: "admin" }, { $set: { desc: "admin user" } })
  5. // 样例解释:相当于 SQL 中的 `UPDATE user SET desc='admin user' WHERE username='admin'
  6. db.COLLECTION_NAME.save(DOCUMENT) // 通过传入的文档来替换已有文档,_id 主键存在就更新,不存在就插入

:::warning 注:motor 没有 updatesave 方法,只能使用 update_oneupdate_many 方法。 :::

删除文档:

  1. db.COLLECTION_NAME.remove(QUERY)

:::warning 注:motor 中的删除是 delete_many,没有 remove。 :::

计数:

  1. db.COLLECTION_NAME.count(QUERY)

查询语法

条件操作符:

  1. db.COLLECTION_NAME.find({age: {$gt: 18}}) // 大于:$gt
  2. // 其他还有:小于($lt)、大于等于($gte)、小于等于($lte)
  3. db.COLLECTION_NAME.find({age: {$gt: 18, $lt: 22}}) // 多个条件操作符

查询结果交、并集:

  1. db.COLLECTION_NAME.find({$or: [QUERY1, QUERY2]}, PROJECTION) // 执行多个查询语句,并取并集
  2. db.COLLECTION_NAME.find({$and: [QUERY1, QUERY2]}, PROJECTION) // 执行多个查询语句,并取交集

正则表达式:

  1. db.COLLECTION_NAME.find({name: {$regex: 'ma'}}) // 返回 name 包含 'ma' 的所有文档
  2. // 注意:在编程语言中,正则表达式 'ma' 匹配时只会返回 'ma';
  3. // 而在 MongoDB 中会返回 name 中包含 'ma' 的所有文档,即 'ma' 和 '.*ma.*' 的作用是一样的
  4. db.COLLECTION_NAME.find({name: {$regex: '^ma', $options: 'gi'}})
  5. // $options 可以指定一些选项,如 'i' 表示忽略大小写。这与 JS 中是一样的
  6. db.COLLECTION_NAME.find({name: /^ma/gi})
  7. // 正则表达式的简化写法,与 JS 中写法相同

查询修饰符

  1. .skip(NUMBER) // 跳过 NUMBER 个文档
  2. .limit(NUMBER) // 限制文档数量
  3. .sort({age: -1}) // 按 age 降序排序。1 表示升序,-1 表示降序

游标

关于游标

当使用 db.COLLECTION_NAME.find() 在集合中搜索文档时,结果将返回指向文档集合的指针,该指针称为游标。
默认情况下,返回查询结果时,游标将自动进行迭代输出。

若想自己迭代,可以这样写:

  1. let cursor = db.COLLECTION_NAME.find(QUERY, PROJECTION)
  2. while (cursor.hasNext()) {
  3. print(to_json(cursor.next()))
  4. }

cursor.xxx()

  1. .pretty() // 格式化显示
  2. .forEach(printjson) // 查询结果以 json 格式展示。(目前来看和 .pretty() 效果类似)
  3. .forEach(<function>) // 遍历查询结果,对每个文档调用定义好的函数
  4. // 完全支持 JS 中编写函数的方法,也支持箭头函数。
  5. // 打印用 print() 不能用 console.log()
  6. // 上面的 printjson 其实就是一个预定义好的函数
  7. .hasNext() // 是否还有下一个文档
  8. .next() // 返回下一个文档

聚合

联合查询的一个示例:

  1. db.user.aggregate([ // 聚合操作。和管道类似,列表中每个操作的输出作为下一个操作的输入
  2. {
  3. $lookup: { // 用于多集合关联查询。相当于 SQL 的 **左连接**
  4. localField: "_id", // 当前集合的字段
  5. from: "user_roles", // 目标集合
  6. foreignField: "user_id",// 目标集合的字段
  7. as: "temp" // 新字段命名
  8. }
  9. }, {
  10. $project: { // 过滤某些字段,0 表示过滤,1 表示显示
  11. _id: 0,
  12. username: 1,
  13. temp: 1
  14. }
  15. }, {
  16. $unwind: { // 将数组展开
  17. path: "$temp",
  18. preserveNullAndEmptyArrays: true // 保留空数组
  19. }
  20. }, {
  21. $lookup: {
  22. localField: "temp.role_id", // 内嵌文档的字段要用 . 访问
  23. from: "role",
  24. foreignField: "_id",
  25. as: "temp2"
  26. }
  27. }, {
  28. $unwind: {
  29. path: "$temp2",
  30. preserveNullAndEmptyArrays: true
  31. }
  32. }, {
  33. $project: {
  34. username: 1,
  35. role: "$temp2.name" // 这里相当于给字段起别名,可以用来将内嵌文档中的字段提出来
  36. }
  37. }
  38. ]);

索引

每个集合的 _id 字段默认就有索引,而且 无法删除

索引的默认命名规则样例:column1_1_column2_-1,解释:这是一个联合索引,建立在 column1column2 两个字段上,其中 column1 升序,column2 降序。注:_id 索引的名字不太一样,是 _id_

建立索引:

  1. db.COLLECTION_NAME.ensureIndex({
  2. column1: 1, // 1 表示升序
  3. column2: -1 // -1 表示降序
  4. });

删除索引:

  1. db.COLLECTION_NAME.dropIndex("column1_1_column2_-1"); // 删除单个索引
  2. db.COLLECTION_NAME.dropIndexes(); // 删除除 _id 索引之外的所有索引

获取索引信息:

  1. db.COLLECTION_NAME.getIndexes();

数据库管理

备份与恢复

备份:

  1. $ mongodump [-h localhost:27017] [-d test] [-o /data]
  2. // -h 参数指定 mongod 服务。默认为 localhost:27017
  3. // -d 参数指定要备份的数据库。若未指定该参数则备份所有数据库
  4. // -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 :::

恢复:

  1. $ 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 可以自定义角色,但不推荐使用,内置角色已经够用了。 :::

用户管理

创建用户:

  1. db.createUser({
  2. user: "user1", // 用户名
  3. pwd: "user1pwd", // 密码
  4. roles: [ // 角色列表。可以是空数组,即没有任何权限
  5. { role: "read", db: "test" } // 赋予该用户对 test 数据库的读权限
  6. ],
  7. customData:{ // 一些自定义的信息
  8. description:"user1",
  9. email: "xxx@example.com"
  10. }
  11. });

删除用户:

  1. db.dropUser('user1');

修改密码:

  1. db.changeUserPassword("username", "xxx")

修改用户信息:

  1. db.runCommand({
  2. updateUser: "username",
  3. pwd: "xxx",
  4. customData: { title:"xxx" }
  5. });

查看当前数据库中的用户:

  1. show users;

:::warning 注:

  1. 在哪个数据库创建用户,用户信息就会保存在哪个数据库,db.auth 就要在哪个数据库用。但也仅仅如此,并不是说在 test 数据库创建的用户只能有 test 数据库的权限,有哪个数据库的权限取决于创建用户时 roles -> db 字段中指定的数据库名。
  2. xxxAnyDatabase 一类的角色必须在 admin 数据库中创建。 :::

服务器开启身份认证

MongoDB 默认没有开启身份认证,即任何人都可以做任何操作。

使用以下任意一种方式可以开启身份认证:

  1. 修改配置文件,将 auth=true 这一行取消注释。
  2. 启动 mongod 服务时添加 --auth 参数。

客户端连接后,使用 db.auth('user', 'pwd') 来验证身份,之后就可以进行该用户可以进行的操作了。

其他

数据模型设计

使用数据的时候,一个数据项常常和另外的一个或多个数据项产生关系,比如一个“人”对象,有一个名字,可能有多个电话号码,以及多个子女,等等。

在 SQL 数据库中,关系被分为一个个表,在表中,每个数据项以主键标识,而一个表的主键又作为另一个表的外键,在两个表之间引用。当遇上多对多关系的时候,还需要一个额外的关联表,将多对多关系转化成两个一对多关系。

而在 MongoDB 中,表示关系有两种办法:

  1. 嵌套。即一个文档中包裹子文档;
  2. 引用链接。又可以分为两种:

    • 手动引用。这种方式其实就完全相当于 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 格式的库。

其他一些小点

  1. 在 admin 数据库中执行 db.shutdownServer() 可以关闭 mongodb 服务。
  2. ObjectId:BSON 中的一种数据类型,_id 字段都是该类型。