概念

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 没有 update 和 save 方法,只能使用 update_one 和 update_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,解释:这是一个联合索引,建立在 column1 和 column2 两个字段上,其中 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 字段都是该类型。