[MongoDB1]基础八股-架构-索引-电商场景-命令-管道操作符

92 阅读6分钟
A.1Why we choose MongoDB

1.灵活的数据模型: MongoDB使用一种名为BSON(Binary JSON)的文档存储格式,它允许存储复杂的数据类型,无需预先定义表结构,使得数据模型非常灵活。

2.高性能: MongoDB支持高性能的数据持久化,它通过内部机制,如内存映射、索引和副本集,来优化读写操作。

3.高可用性: MongoDB的副本集(Replica Sets)功能可以提供数据冗余和自动故障转移,确保了数据的高可用性。

4.水平可扩展: MongoDB支持分片(Sharding),这使得数据库可以轻松地通过添加更多的服务器来水平扩展,以支持大数据量的存储和高吞吐量的操作。

5.丰富的查询语言: MongoDB提供了丰富的查询接口,支持动态查询、数据聚合、文本搜索和地理空间查询等。

6.部署的易用性MongoDB设计简单,部署和维护相对容易,它不需要复杂的配置,支持自动修复和简单的备份操作。

7.支持多语言客户端:MongoDB有官方的驱动程序支持多种编程语言,如Java、Python、PHP、C#、C++、Node.js等,方便开发者使用。

8.副本集的容错性副本集可以容忍多个节点的故障,并且能够自动恢复,提高了系统的容错能力。

9.无模式(Schema-less) MongoDB的无模式特性意味着同一个集合中的文档不需要有相同的字段,这对于处理不断变化的数据结构非常有利。

10.网格文件系统MongoDB通过GridFS可以存储和检索超过16MB的文件,适合处理大文件存储。

A.2MongoDB的文件架构
  • 数据文件(.ns和.data) :

    • .ns 文件:存储命名空间信息,即集合(collection)和索引(index)的元数据。
    • .data 文件:存储集合中的实际数据。
  • 日志文件(.log) :

    • 日志文件记录了MongoDB的操作和系统事件,对于调试和审计非常有用。
  • 配置文件(mongod.conf) :

    • 配置文件包含了MongoDB实例的配置选项,如端口号、数据目录路径、日志路径等。
  • 诊断文件(.diaglog) :

    • 如果开启了诊断日志,MongoDB会生成.diaglog文件,用于记录数据库的操作细节。
  • 索引文件(.0、.1、.ns等) :

    • 索引文件用于存储集合的索引数据,它们通常以数字命名。
  • journal文件(.journal) :

    • 如果启用了journaling(日志记录),MongoDB会使用journal文件来确保数据操作的持久性。在系统崩溃时,journal文件可以用来恢复数据。
  • 锁文件(.lock) :

    • 锁文件用于防止同一数据目录被多个MongoDB实例同时使用。
  • 元数据文件(.metadata) :

    • 存储有关 WiredTiger 存储引擎的元数据。
  • WiredTiger存储引擎文件(WiredTiger.wt等) :

    • 对于使用WiredTiger存储引擎的MongoDB实例,这些文件用于存储数据和索引。
A.3MongoDB的调试工作

调试MongoDB:

调试MongoDB通常涉及以下几个步骤:

  • 检查日志文件:

    • MongoDB的日志文件通常位于/var/log/mongodb/mongod.log(在Linux系统上)。查看日志文件可以帮助诊断问题。
  • 使用MongoDB shell:

    • 启动MongoDB shell (mongo) 并执行各种命令来检查数据库状态,例如:

      db.stats() // 查看数据库统计信息
      db.serverStatus() // 查看服务器状态
      
  • 使用诊断命令:

    • 使用db.currentOp()来查看当前正在进行的操作。
    • 使用db.killOp()来终止特定的操作。
  • 配置诊断参数:

    • 在配置文件中设置diagnosticLoggingverbose参数来增加日志的详细程度。
  • 使用第三方工具:

    • 使用如MongoDB Compass等工具来可视化数据库操作和性能分析。

MongoDB CRUD操作:

以下是MongoDB的CRUD(创建、读取、更新、删除)操作的示例:

  • 创建(Create) :

    db.collection.insertOne({ name: "Alice", age: 25 }) // 插入单个文档
    db.collection.insertMany([{ name: "Bob", age: 30 }, { name: "Charlie", age: 35 }]) // 插入多个文档
    
  • 读取(Read) :

    db.collection.find({}) // 查询所有文档
    db.collection.find({ age: { $gt: 30 } }) // 查询年龄大于30的文档
    db.collection.findOne({ name: "Alice" }) // 查询第一个名为Alice的文档
    
  • 更新(Update) :

    db.collection.updateOne({ name: "Alice" }, { $set: { age: 26 } }) // 更新第一个名为Alice的文档的年龄
    db.collection.updateMany({ age: { $lt: 30 } }, { $inc: { age: 1 } }) // 将所有年龄小于30的文档年龄加1
    
  • 删除(Delete) :

    db.collection.deleteOne({ name: "Alice" }) // 删除第一个名为Alice的文档
    db.collection.deleteMany({ age: { $gte: 35 } }) // 删除所有年龄大于等于35的文档
    

    在进行这些操作时,可以使用各种查询操作符来精确控制数据的操作。需要注意的是,CRUD操作可能需要适当的权限才能执行。

A.4MongoDB的索引相关

索引的类型:

  1. 单字段索引(Single Field) :在文档的单个字段上创建索引。

  2. 复合索引(Compound Index) :在文档的多个字段上创建索引。

  3. 多键索引(Multikey Index) :用于索引数组类型的字段,可以对数组中的每个元素创建索引。

  4. 文本索引(Text Index) :用于文本搜索。

  5. 哈希索引(Hashed Index) :对字段值的哈希进行索引,主要用于分片。 索引命令:

  6. 创建索引

    db.collection.createIndex({ <field1>: <type>, <field2>: <type>, ... })
    

    例如,为name字段创建升序索引:

    db.users.createIndex({ "name": 1 })
    
  7. 创建复合索引

    db.collection.createIndex({ <field1>: <type>, <field2>: <type>, ... })
    

    例如,为nameage字段创建复合索引:

    db.users.createIndex({ "name": 1, "age": -1 })
    
  8. 列出集合的所有索引

    db.collection.getIndexes()
    
  9. 删除索引: 根据索引名删除:

    db.collection.dropIndex(<index-name>)
    

    删除所有索引(除了_id索引):

    db.collection.dropIndexes()
    
  10. 解释查询计划: 使用explain来获取查询的执行计划,这有助于理解是否使用了索引:

    db.collection.find({ <query> }).explain("executionStats")
    

    索引的注意事项:

  • 性能影响:索引可以提高查询速度,但也会降低插入、更新和删除操作的速度,因为索引本身也需要维护。
  • 存储空间:索引需要额外的存储空间。
  • 索引选择性:选择那些能够有效区分文档的字段作为索引,以提高查询效率。
  • 索引大小:复合索引的条目数量受限于索引键的总大小,所有索引键的大小总和不能超过1024字节。 使用索引时,应根据实际的应用场景和查询模式来创建合适的索引,以达到最优的性能。

在MongoDB中,当你创建一个索引时,MongoDB会自动为该索引生成一个名称。默认情况下,这个名称是由索引的字段名和排序方向组成的。对于你提供的例子 db.users.createIndex({ "name": 1, "age": -1 }),生成的索引名大致会是这样的格式:

name_1_age_-1

要找到具体的索引名,你可以使用 getIndexes() 方法列出集合中的所有索引,并查看每个索引的 name 字段。 以下是列出所有索引并找到特定索引名的步骤:

db.users.getIndexes()

这会返回一个数组,其中包含所有索引的详细信息,包括每个索引的名称。你可以从中找到与你创建的复合索引对应的名称。 例如,输出可能会是这样的:

[
{
 // 第一个索引对象
 "v" : 2, // 索引的版本号。v:2表示这是MongoDB 2.2及以后版本的索引格式。
 "key" : {
   "_id" : 1 // 索引的键。这里表示_id字段上的索引,且排序方向为升序(1表示升序,-1表示降序)。
 },
 "name" : "_id_", // 索引的名称。对于_id字段,MongoDB默认创建一个名为_id_的索引。
 "ns" : "yourDatabaseName.users" // 索引所在的命名空间。格式通常是数据库名.集合名。
},
{
 // 第二个索引对象
 "v" : 2, // 同上,索引版本号。
 "unique" : false, // 布尔值,表示索引是否是唯一的。这里为false,意味着索引中的值可以重复。
 "key" : {
   "name" : 1, // 索引的键。这里表示name字段上的索引,且排序方向为升序。
   "age" : -1 // 索引的键。这里表示age字段上的索引,且排序方向为降序。
 },
 "name" : "name_1_age_-1", // 索引的名称。MongoDB根据索引的字段名和排序方向自动生成。
 "ns" : "yourDatabaseName.users" // 同上,索引所在的命名空间。
}
]

在上面的输出中,"name" : "name_1_age_-1" 就是你需要用来删除索引的索引名。 现在,你可以使用这个索引名来删除索引:

db.users.dropIndex("name_1_age_-1")

这将删除名为 "name_1_age_-1" 的索引。如果你不确定确切的索引名,一定要先通过 getIndexes() 方法检查,以免删除错误的索引。

A.5基于电商业务整合MongoDB命令

要将shell命令写入一个文件并执行它,你可以按照以下步骤操作:

步骤 1: 创建一个脚本文件

使用文本编辑器(如 nano, vim, gedit 等)创建一个新的文件。例如,我们可以创建一个名为 script.sh 的文件。

nano script.sh

步骤 2: 写入shell命令

在打开的编辑器中,输入你的shell命令。例如:

#!/bin/bash
# 这是注释,说明这个脚本将使用bash来执行
# MongoDB 示例命令
mongo <<EOF #<<EOF 是一个Here文档的标记,它告诉shell接下来的文本块直到另一个 EOF 标记为止都应该被当作标准输入传递给 mongo 命令。
use yourDatabase;
db.collection.insertOne({ field: "value" });
db.collection.find();
EOF#Here文档结束: 这标记了Here文档的结束。所有在 mongo <<EOF 和这个 EOF 标记之间的文本都将作为标准输入传递给 mongo 命令。

确保在文件的第一行包含了 #!/bin/bash(或者适合你脚本的其他解释器路径),这被称为shebang,它告诉系统应该使用哪个解释器来执行这个脚本。

步骤 3: 保存并关闭文件

nano 中,你可以通过按 Ctrl + X,然后按 Y 来保存文件,最后按 Enter 确认文件名并退出编辑器。

步骤 4: 使脚本可执行

你需要给脚本文件设置执行权限:

chmod +x script.sh
#chmod:change mode的意思
#+x +是添加 x是execute permission也就是添加执行权限
#script.sh 是一个shell脚本文件

步骤 5: 执行脚本

现在,你可以通过以下命令来执行你的脚本:

./script.sh

确保你位于包含 script.sh 文件的目录中,或者提供脚本的完整路径。

注意事项:

  • 如果你的脚本需要以root权限运行,你可能需要使用 sudo 来执行它:sudo ./script.sh
  • 如果你的脚本中包含对其他用户不安全的命令,或者你需要以其他用户身份运行,确保你知道你在做什么。
  • 如果你在Windows系统上编写脚本,你可能需要创建一个批处理文件(.bat)或PowerShell脚本文件(.ps1),并且使用不同的命令和语法。 以上就是将shell命令写入文件并执行的基本步骤。

在电商业务中,MongoDB可以用来存储和查询各种类型的数据,例如用户信息、商品信息、订单信息等。以下是一些与电商业务相关的MongoDB命令和知识点的讲解:

1. 创建集合(Collection)

首先,你可能需要为不同的数据类型创建集合:

db.createCollection("users")       # 创建用户集合
db.createCollection("products")    # 创建商品集合
db.createCollection("orders")      # 创建订单集合

2.插入文档(Insert Documents)

插入数据到集合中:

# 插入用户
db.users.insertOne({
  username: "user123",
  email: "user123@example.com",
  password: "hashed_password",
  createdAt: new Date()
})
# 插入商品
db.products.insertOne({
  name: "Product Name",
  category: "Category",
  price: 99.99,
  stock: 100,
  description: "Product Description"
})
# 插入订单
db.orders.insertOne({
  userId: ObjectId("507f191e810c19729de860ea"), # 假设userId是用户的ObjectId
  productId: ObjectId("507f1f77bcf86cd799439011"),
  quantity: 2,
  status: "pending",
  createdAt: new Date()
})
  1. 创建索引(Create Indexes)

为了提高查询性能,可以在常用查询的字段上创建索引:

# 在用户集合的username字段上创建唯一索引
db.users.createIndex({ username: 1 }, { unique: true })
# 在商品集合的name字段上创建文本索引,以便进行全文搜索
db.products.createIndex({ name: "text" })
# 在订单集合的userId和status字段上创建复合索引
db.orders.createIndex({ userId: 1, status: 1 })
  1. 查询数据(Query Data)

执行各种查询操作:

# 查询特定用户的所有订单
db.orders.find({ userId: ObjectId("507f191e810c19729de860ea") })
# 查询类别为"Electronics"的所有商品
db.products.find({ category: "Electronics" })
# 查询价格低于50元的所有商品
db.products.find({ price: { $lt: 50 } })
# 使用文本索引进行全文搜索
db.products.find({ $text: { $search: "Product Name" } })
  1. 更新数据(Update Data)

更新集合中的文档:

# 更新商品库存
db.products.updateOne(
  { _id: ObjectId("507f1f77bcf86cd799439011") },
  { $inc: { stock: -2 } } # 假设卖出了2个商品,库存减少2
)
# 更新订单状态
db.orders.updateOne(
  { _id: ObjectId("507f1f77bcf86cd799439012") },
  { $set: { status: "shipped" } }
)
  1. 删除数据(Delete Data)

从集合中删除文档:

# 删除特定订单
db.orders.deleteOne({ _id: ObjectId("507f1f77bcf86cd799439012") })
# 删除所有已完成的订单
db.orders.deleteMany({ status: "completed" })

注意事项:

  • 使用 ObjectId 来引用其他集合中的文档。
  • 对于敏感信息(如密码),应当存储其哈希值,而不是明文。
  • 索引可以显著提高查询性能,但会占用额外的存储空间,并且会稍微减慢写操作的速度。
  • 使用 $text 索引进行全文搜索时,确保字段已经创建了文本索引。 这些命令和知识点是MongoDB在电商业务中常用的基础操作。在实际应用中,你可能还需要处理更复杂的查询、聚合操作、事务处理等。
A.6聚合管道操作符

MongoDB的聚合操作是一种用于处理数据并返回计算结果的方法,类似于SQL中的GROUP BY语句。聚合操作可以对集合中的数据进行分组、转换、计算等操作,最终输出一个结果集。 在MongoDB中,聚合操作主要通过聚合管道(Aggregation Pipeline)来实现。聚合管道是一系列的数据处理阶段,每个阶段对输入的文档序列执行特定的操作,并将结果输出到下一阶段。下面是一些常用的聚合管道操作符和它们的功能: 聚合管道操作符:

  1. $match

    • 用于过滤文档,只输出符合条件的文档。
    • 类似于SQL中的WHERE子句。
  2. $group

    • 用于将集合中的文档分组,可以对分组后的数据进行聚合。
    • 类似于SQL中的GROUP BY。
  3. $project

    • 用于重塑每个文档的结构,选择、添加或删除字段。
    • 可以用来创建计算字段。
  4. $sort

    • 用于对输入的文档进行排序。
  5. $limit

    • 用于限制聚合管道返回的文档数。
  6. $skip

    • 用于在聚合管道中跳过指定数量的文档。
  7. $unwind

    • 用于将数组类型的字段拆分成多个文档。
  8. $out

    • 将聚合管道的结果输出到一个新的集合中。 重塑文档示例: 假设我们有一个名为 orders 的集合,其中包含以下文档:
{
  "_id": ObjectId("507f191e810c19729de860ea"),
  "customer": "Alice",
  "orderDate": ISODate("2023-01-01T00:00:00Z"),
  "items": [
    { "name": "T-shirt", "price": 20, "quantity": 2 },
    { "name": "Jeans", "price": 40, "quantity": 1 }
  ]
}

以下是一个聚合管道的示例,它将重塑文档以计算每个订单的总价:

db.orders.aggregate([
  {
    $project: {
      customer: 1,
      orderDate: 1,
      total: { $sum: "$items.price" }
    }
  }
]);

在这个例子中,$project 阶段用于添加一个新的字段 total,它是通过计算数组 items 中每个项目的 price 字段的总和得到的。 聚合管道操作步骤:

  1. match:首先,你可以使用match:首先,你可以使用 `match` 来过滤出特定条件的文档。
  2. group:然后,使用group:然后,使用 `group` 来对文档进行分组,并计算每个分组的总和、平均值等。
  3. project:接下来,使用project:接下来,使用 `project` 来重塑文档,选择需要的字段,并可以添加计算字段。
  4. sort:使用sort:使用 `sort` 来对结果进行排序。
  5. limitlimit 和 skip:最后,可以使用 $limit$skip 来限制结果的数量或跳过一些文档。 聚合管道是非常强大的,可以执行复杂的操作来处理和分析数据。在实际应用中,这些阶段可以组合使用,以完成复杂的数据处理任务。

以下是一个基于电商场景的MongoDB聚合管道操作示例,该示例尽可能使用了各种聚合管道操作符。假设我们有一个名为orders的集合,该集合包含了电商平台的订单数据。

> db.orders.aggregate([
>  {
>      $match: {
>          status: "completed"  // 筛选出已完成状态的订单
>      }
>  },
>  {
>      $group: {
>          _id: "$customerId",  // 按客户ID分组
>          totalAmount: { $sum: "$amount" },  // 计算每个客户的总消费金额
>          averageAmount: { $avg: "$amount" },  // 计算每个客户的平均消费金额
>          orderCount: { $sum: 1 }  // 统计每个客户的订单数量
>      }
>  },
>  {
>      $sort: {
>          totalAmount: -1  // 按总消费金额降序排序
>      }
>  },
>  {
>      $limit: 10  // 取消费金额最高的前10个客户
>  },
>  {
>      $lookup: {
>          from: "customers",  // 从customers集合中查找客户信息
>          localField: "_id",
>          foreignField: "_id",
>          as: "customerInfo"
>      }
>  },
>  {
>      $unwind: "$customerInfo"  // 展开customerInfo数组
>  },
>  {
>      $project: { //控制输出文档的结构
>          _id: 0,  // 不显示_id字段
>          customerId: "$_id",
>          customerName: "$customerInfo.name",
>          totalAmount: 1,
>          averageAmount: 1,
>          orderCount: 1
>      }
>  },
>  {
>      $out: "top_customers"  // 将结果输出到新的集合top_customers
>  },
>  {
>      $facet: { //在同个聚合管道阶段中同时进行多个分组操作。输出多个分组的结果
>          stats: [
>              { $count: "total" }  // 统计总客户数
>          ],
>          categories: [
>              {
>                  $unwind: "$customerInfo.categories"  // 展开客户类别
>              },
>              {
>                  $group: {
>                      _id: "$customerInfo.categories",
>                      count: { $sum: 1 }  // 统计每个类别的客户数量
>                  }
>              }
>          ]
>      }
>  },
>  {
>      $bucket: {//将输入文档根据指定的表达式和边界划分为桶 对每个桶内文档引用聚合 
>          groupBy: "$averageAmount",  // 按平均消费金额分组
>          boundaries: [0, 100, 200, 300, 400, 500, 600],  // 分组边界
>          default: "Other",  // 默认分组
>          output: {
>              count: { $sum: 1 },
>              averageAmount: { $avg: "$averageAmount" }
>          }
>      }
>  }
> ]);

这个聚合管道操作包含了以下步骤:

  1. $match:筛选出已完成状态的订单。
  2. $group:按客户ID分组,并计算总消费金额、平均消费金额和订单数量。
  3. $sort:按总消费金额降序排序。
  4. $limit:取消费金额最高的前10个客户。
  5. $lookup:从customers集合中查找客户信息。
  6. $unwind:展开customerInfo数组。
  7. $project:选择要显示的字段。
  8. $out:将结果输出到新的集合。
  9. $facet:对数据进行分组统计,包括总客户数和每个类别的客户数量。
  10. $bucket:按平均消费金额分组,并计算每个分组的客户数量和平均消费金额。