MongoDB 面试题目

news/2025/2/26 15:10:52

一、基础概念

MongoDB 的特点是什么?

MongoDB是一种NoSQL数据库,具有以下特点:

  1. 文档存储模型

    • MongoDB 使用 BSON(Binary JSON) 格式存储数据,数据以文档的形式组织,类似于JSON对象。
    • 文档可以包含嵌套结构(如数组和对象),非常适合存储复杂、非结构化的数据。
  2. 高性能

    • MongoDB支持索引,能够快速查询数据。
    • 写入性能高,支持内存映射文件,能够高效处理大量数据。【内存映射文件是一种高效的文件访问技术,通过将文件直接映射到内存中,减少了传统文件 I/O 的开销。】
  3. 水平扩展能力

    • MongoDB支持分片(Sharding),可以将数据分布到多个服务器上,实现水平扩展,适合处理大规模数据和高并发场景。
  4. 高可用性

    • MongoDB通过 复制集 提供高可用性。复制集包含多个节点(主节点和从节点),当主节点故障时,系统会自动选举新的主节点,确保服务不中断。
  5. 丰富的查询功能

    • 支持丰富的查询操作,包括范围查询、正则表达式查询、地理空间查询等。
    • 提供 聚合管道,支持复杂的数据分析和处理。
  6. 事务支持

    • 从 MongoDB 4.0 开始,支持多文档事务,能够在分布式环境中保证数据的一致性。

MongoDB 和关系型数据库术语对比

概念MongoDB关系型数据库
数据库层级数据库(Database)数据库(Database)
数据集合集合(Collection)表(Table)
数据单元文档(Document)行(Row)
数据格式BSON(Binary JSON)行数据(Row Data)
数据结构字段(Field)列(Column)
嵌套结构嵌套文档(Embedded Document)关联表(Related Table)
数组支持数组(Array)多值列(Multi-value Column)
唯一标识_id字段主键(Primary Key)
查询语言MongoDB 查询语言SQL(Structured Query Language)
聚合操作聚合管道(Aggregation Pipeline)SQL聚合函数(如GROUP BY)
索引索引(Index)索引(Index)
复合索引复合索引(Compound Index)复合索引(Composite Index)
事务多文档事务(Multi-Document Transactions)事务(Transactions)
数据一致性最终一致性(Eventual Consistency)强一致性(Strong Consistency)
水平扩展分片(Sharding)分区(Partitioning)
高可用性复制集(Replica Set)主从复制(Master-Slave Replication)

BSON 格式的优势有哪些?

1. 二进制编码,高效存储和传输

  • BSON 是二进制编码的 JSON,比文本格式的 JSON 更紧凑,减少了存储空间和网络传输的开销。
  • 二进制格式解析速度更快,适合高性能场景。

2. 支持丰富的数据类型

  • BSON 支持比 JSON 更多的数据类型,如 日期(Date)、二进制数据(BinData)、ObjectId、正则表达式(Regex)等。

3. 支持嵌套和复杂结构

  • BSON可以表示嵌套的文档和数组,适合存储复杂的数据结构。
  • 例如,一个文档中可以包含另一个文档或数组,而无需额外的关联表。

4. 高效查询

  • BSON格式在存储时会记录字段的长度和类型信息,使得查询时可以直接定位数据,无需解析整个文档。

5. 支持索引

  • BSON 格式允许 MongoDB 在文档的特定字段上创建索引,从而加速查询。
  • 例如,可以在嵌套字段或数组字段上创建索引。

MongoDB 支持哪些数据类型?

1. 基本数据类型

  • 字符串(String)
  • 整数(Integer)
  • 双精度浮点数(Double)
  • 布尔值(Boolean
  • 日期(Date)**
    • 用于存储日期和时间,例如:"createdAt": ISODate("2023-10-01T12:00:00Z")
  • 空值(Null)

2. 特殊数据类型

  • ObjectId

    • 用于唯一标识文档的12字节ID,例如:"_id": ObjectId("507f1f77bcf86cd799439011")
  • 二进制数据(BinData)

    • 用于存储二进制数据,例如图片或文件,例如:"file": BinData(0, "SGVsbG8gd29ybGQ=")
  • 正则表达式(Regex)

    • 用于存储正则表达式,例如:"pattern": /^[A-Za-z]+$/
  • JavaScript代码(JavaScript)

    • 用于存储JavaScript代码,例如:"script": "function() { return this.age > 18; }"
  • 时间戳(Timestamp)

    • 用于存储时间戳,通常用于内部操作,例如:"ts": Timestamp(1696156800, 1)

3. 复杂数据类型

  • 数组(Array)**
    • 用于存储一组值,例如:"hobbies": ["reading", "traveling", "coding"]
  • 嵌套文档(Embedded Document)**
    • 用于存储嵌套的文档,例如:
      "address": {
        "city": "New York",
        "zip": "10001"
      }
      
  1. 地理空间数据(Geospatial Data)

二、数据操作

如何创建数据库和集合?

创建数据库

  • 使用 use 命令切换到指定数据库,如果数据库不存在则会自动创建。
    use myDatabase
    

创建集合:

  • 使用 db.createCollection() 方法显式创建集合。
    db.createCollection("myCollection")
    
  • 如果集合不存在,插入文档时会自动创建集合。

使用 insertOne()insertMany() 插入文档?

{ name: “Alice”, age: 25 } 表示一个文档
insertOne()

  • 插入单个文档。
    db.myCollection.insertOne({ name: "Alice", age: 25 })
    
  • 注意事项:
    • 如果文档未指定 _id 字段,MongoDB 会自动生成一个唯一的 ObjectId
    • 如果文档已存在 _id 字段,且 _id 已存在,则会抛出重复键错误。

insertMany()

  • 插入多个文档。
    db.myCollection.insertMany([
      { name: "Bob", age: 30 },
      { name: "Charlie", age: 35 }
    ])
    
  • 注意事项:
    • 如果插入的文档中有重复的 _id,整个操作会失败(默认行为)。
    • 可以通过 ordered: false 选项忽略重复键错误,继续插入其他文档。
      db.myCollection.insertMany([
        { _id: 1, name: "Bob" },
        { _id: 1, name: "Charlie" }
      ], { ordered: false })
      

如何编写多条件查询语句(如年龄>25且性别为男)?

  • 使用 $and 操作符或直接在查询对象中指定多个条件。
    db.myCollection.find({
      age: { $gt: 25 },
      gender: "male"
    })
    

如何使用投影操作符返回部分字段?

  • find() 的第二个参数中指定需要返回的字段(1 表示返回,0 表示不返回)。
    db.myCollection.find(
      { age: { $gt: 25 } },
      { name: 1, age: 1, _id: 0 }
    )
    

如何对查询结果进行排序?

  • 使用 sort() 方法,1 表示升序,-1 表示降序。
    db.myCollection.find().sort({ age: 1 })
    

如何使用聚合管道进行复杂数据处理?

  • 使用 aggregate() 方法,结合多个阶段(如 $match$group$sort 等)处理数据。
db.users.aggregate([
  // 1. 过滤年龄大于 25 的用户
  { $match: { age: { $gt: 25 } } },

  // 2. 只保留 name 和 gender 字段
  { $project: { name: 1, gender: 1, _id: 0 } },

  // 3. 按 gender 分组,计算每组人数
  { $group: { _id: "$gender", total: { $sum: 1 } } },

  // 4. 按 total 字段降序排序
  { $sort: { total: -1 } },

  // 5. 只返回前 5 条结果
  { $limit: 5 }
])

如何更新满足条件的多个文档?

  • 使用 updateMany() 方法。
    db.myCollection.updateMany(
      { age: { $gt: 25 } },
      { $set: { status: "active" } }
    )
    

如何删除满足条件的文档?

  • 使用 deleteMany() 方法。
    db.myCollection.deleteMany({ age: { $lt: 18 } })
    

如何实现文档的部分更新?

  • 使用 $set 操作符更新指定字段。
    db.myCollection.updateOne(
      { name: "Alice" },
      { $set: { age: 26 } }
    )
    

三、索引

索引底层原理?

索引数据通过 B 树来存储,所有节点都有 Data 域,只要找到指定索引就可以进行访问,
单次查询从结构上来看要快于MySql(B+ 树)。

B 树结构:
在这里插入图片描述

B 树的特点:

  • 多路 非二叉树
  • 每个节点 既保存数据 又保存索引
  • 搜索时 相当于二分查找

B 树的分层结构

  • 根节点(Root Node):树的顶层节点。
  • 内部节点(Internal Node):存储索引键和数据指针,用于导航到子节点。
  • 叶子节点(Leaf Node):存储索引键和数据指针,直接指向实际文档(通常是 _id 值或磁盘位置)。

B+ 树结构:
在这里插入图片描述


如何为字段创建索引?

创建单字段索引

为单个字段创建索引,可以是升序(1)或降序(-1)。

语法:

db.collection.createIndex({ field: 1 })

示例:

// 为 users 集合的 age 字段创建升序索引
db.users.createIndex({ age: 1 })

// 为 users 集合的 name 字段创建降序索引
db.users.createIndex({ name: -1 })

创建复合索引

为多个字段创建复合索引,可以指定每个字段的排序方式。

语法:

db.collection.createIndex({ field1: 1, field2: -1 })

示例:

// 为 users 集合的 age 和 gender 字段创建复合索引
db.users.createIndex({ age: 1, gender: -1 })

创建唯一索引

确保字段的值唯一,可以创建唯一索引。

语法:

db.collection.createIndex({ field: 1 }, { unique: true })

示例:

// 为 users 集合的 email 字段创建唯一索引
db.users.createIndex({ email: 1 }, { unique: true })

创建文本索引

支持全文搜索,可以创建文本索引。

语法:

db.collection.createIndex({ field: "text" })

示例:

// 为 articles 集合的 content 字段创建文本索引
db.articles.createIndex({ content: "text" })

创建 TTL 索引

支持自动删除过期的文档,可以创建 TTL 索引。

语法:

db.collection.createIndex({ field: 1 }, { expireAfterSeconds: 3600 })

示例:

// 为 logs 集合的 createdAt 字段创建 TTL 索引,文档在 1 小时后自动删除
db.logs.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 })

查看索引

可以使用 getIndexes() 方法查看集合中的所有索引。

语法:

db.collection.getIndexes()

删除索引

可以使用 dropIndex() 方法删除指定的索引。

语法:

db.collection.dropIndex("index_name")

升序索引和降序索引的区别是什么?

  • 升序索引适合从小到大排序的查询,降序索引适合从大到小排序的查询。
  • 在复合索引中,升序和降序的组合可以优化复杂的排序查询。
db.collection.createIndex({ age: 1, score: -1 })

// 这个索引会先按 age 升序排序,再按 score 降序排序。
// 如果查询的排序方式与索引一致(如 sort({ age: 1, score: -1 })),MongoDB 可以直接利用索引。

如何查看和删除集合中的索引?

查看集合中的索引

使用 db.collection.getIndexes() 命令可以查看集合中的所有索引。

语法

db.collection.getIndexes()

示例
假设有一个 users 集合,查看其索引:

db.users.getIndexes()

输出示例

[
  {
    "v": 2,
    "key": { "_id": 1 },
    "name": "_id_",
    "ns": "test.users"
  },
  {
    "v": 2,
    "key": { "age": 1 },
    "name": "age_1",
    "ns": "test.users"
  }
]
  • key:索引的字段和排序方式(1 表示升序,-1 表示降序)。
  • name:索引的名称。
  • ns:索引所属的命名空间(数据库名.集合名)。

删除集合中的索引

可以使用 db.collection.dropIndex()db.collection.dropIndexes() 命令删除索引。

删除单个索引

使用 db.collection.dropIndex() 命令,可以指定索引的名称或键来删除索引。

语法

db.collection.dropIndex(indexNameOrKey)

示例

  1. 通过索引名称删除:
    db.users.dropIndex("age_1")
    
  2. 通过索引键删除:
    db.users.dropIndex({ age: 1 })
    

输出
如果删除成功,会返回:

{ "nIndexesWas": 2, "ok": 1 }
删除所有索引(除了 _id 索引)

使用 db.collection.dropIndexes() 命令可以删除集合中的所有索引(_id 索引不会被删除)。

语法

db.collection.dropIndexes()

示例

db.users.dropIndexes()

输出
如果删除成功,会返回:

{ "nIndexesWas": 2, "msg": "non-_id indexes dropped for collection", "ok": 1 }

注意事项
  • _id 索引_id 索引是 MongoDB 自动为每个集合创建的,不能删除。
  • 索引名称:如果没有显式指定索引名称,MongoDB 会默认生成一个名称(如 age_1 表示 age 字段的升序索引)。
  • 删除索引的影响:删除索引后,依赖该索引的查询性能可能会下降,需谨慎操作。

复合索引的创建原则是什么?

在 MongoDB 中,复合索引(Compound Index)是指基于多个字段创建的索引。

1. 字段顺序原则

复合索引的字段顺序非常重要,因为它决定了索引的存储和查询效率。

ESR 原则(Equality, Sort, Range)

  • Equality(等值查询):将用于等值查询的字段放在索引的最前面。
  • Sort(排序):将用于排序的字段放在等值查询字段之后。
  • Range(范围查询):将用于范围查询的字段放在最后。

示例
假设有一个查询:

db.users.find({ age: 25, score: { $gte: 80 } }).sort({ name: 1 })

根据 ESR 原则,复合索引应创建为:

db.users.createIndex({ age: 1, name: 1, score: 1 })
  • age:等值查询字段,放在最前面。
  • name:排序字段,放在中间。
  • score:范围查询字段,放在最后。

2. 覆盖查询原则

如果查询的所有字段都在复合索引中,MongoDB 可以直接从索引中返回结果,而无需访问实际文档。这种查询称为覆盖查询(Covered Query)。

示例
假设有一个查询:

db.users.find({ age: 25 }, { name: 1, _id: 0 })

// { name: 1, _id: 0 }
// 投影用于控制查询结果中返回哪些字段。1 返回;0 不返回

创建以下索引可以支持覆盖查询:

db.users.createIndex({ age: 1, name: 1 })

3. 排序原则

如果查询中包含排序操作,复合索引的字段顺序应与排序字段的顺序一致。

示例
假设有一个查询:

db.users.find({ age: 25 }).sort({ name: 1 })

创建以下索引可以支持排序:

db.users.createIndex({ age: 1, name: 1 })

如果排序方向不一致(如 sort({ name: -1 })),索引应创建为:

db.users.createIndex({ age: 1, name: -1 })

4. 前缀原则

复合索引支持前缀查询(Prefix Query),即查询中只使用索引的前几个字段。

示例
假设有一个索引:

db.users.createIndex({ age: 1, name: 1, score: 1 })

以下查询可以利用该索引:

  • db.users.find({ age: 25 })(使用前缀 age
  • db.users.find({ age: 25, name: "Alice" })(使用前缀 agename

以下查询不能利用该索引:

  • db.users.find({ name: "Alice" })(跳过了前缀 age

在什么情况下不适合创建索引?

  • 数据量非常小。
  • 查询模式不固定。
  • 字段值分布不均匀或非常长。
  • 查询频率低。
  • 复合索引字段过多。

四、复制集

MongoDB 有三种常见的部署架构

  • 单机版:只有一个单节点,一般用来做开发和测试
  • 复制集:绝大部分 MongoDB 实例上线的时候都使用复制集、高可用模式,1主2从(或更多从节点),至少是三个节点的架构
  • 分片集群:节点数明显增多,一般有 9 个实例

绝大部分使用场景是复制集

在这里插入图片描述

什么是复制集?

复制集 由一组MongoDB实例(进程)组成,包含一个 Primary (主)节点和多个 Secondary (从)节点。

MongoDB Driver(客户端)的所有数据都写入 Primary,Secondary 从 Primary 同步写入的数据,以保持复制集内所有成员存储相同的数据集,提供数据的高可用。

下图是一个典型的 MongoDB 复制集,包含一个 Primary 节点和两个 Secondary 节点。
在这里插入图片描述

主节点和从节点的职责分别是什么?

职责主节点(Primary)从节点(Secondary)
写操作处理所有写操作。不处理写操作。
读操作默认处理读操作。可以处理读操作,分担主节点负载。
数据复制将操作日志(Oplog)发送给从节点。从主节点拉取操作日志,并应用数据。
选举参与不参与选举。参与选举,可能成为新的主节点。
数据备份不直接充当数据备份。可以充当数据备份。
特殊角色无特殊角色。可配置为隐藏节点、延迟节点或只读节点。

复制集的同步过程(数据一致性)?

Primary 与 Secondary 之间通过 oplog 来同步数据。
Primary 上的写操作完成后,会向特殊的 local.oplog.rs 集合 写入一条 oplog,Secondary 不断的从Primary 获取新的 oplog 并应用。

如下 oplog 的格式,包含 tshopnso 等字段。

    {
      "ts" : Timestamp(1446011584, 2),
      "h" : NumberLong("1687359108795812092"), 
      "v" : 2, 
      "op" : "i", 
      "ns" : "test.nosql", 
      "o" : { "_id" : ObjectId("563062c0b085733f34ab4129"), "name" : "mongodb", "score" : "100" } 
    }

字段说明如下:

  • ts:操作时间,当前timestamp + 计数器,计数器每秒都被重置。
  • h:操作的全局唯一标识。
  • v:oplog版本信息。
  • op:操作类型,取值说明:
  • i:插入操作。
  • u:更新操作。
  • d:删除操作。
  • c:执行命令(如createDatabase,dropDatabase)。
  • n:空操作,特殊用途。
  • ns:操作针对的集合。
  • o:操作内容。

Secondary 初次同步数据时,会先执行 init sync,从 Primary 同步全量数据(T1时间完成)。

然后不断通过执行tailable cursorPrimarylocal.oplog.rs 集合里查询最新的 oplog (T2-T1时间段)并应用到自身。

选举机制

MongoDB 的选举机制基于 Raft 协议,这是一种分布式一致性算法。

选举过程由从节点(Secondary)和仲裁节点(Arbiter)参与,确保在大多数节点同意的情况下选出新的主节点。


触发选举的条件

以下情况会触发选举:

  • 主节点不可用(如宕机或网络故障)。
  • 主节点与其他节点的通信中断超过一定时间(默认为 10 秒)。
  • 主节点主动退出(如人为执行 rs.stepDown() 命令)。

选举的参与者
  • 从节点(Secondary):存储数据,可以参与选举并成为主节点。
  • 仲裁节点(Arbiter):不存储数据,仅参与选举投票。

选举的过程

选举过程包括以下步骤:

(1) 检测主节点故障

  • 从节点和仲裁节点会定期与主节点通信(通过心跳机制)。
  • 如果主节点在指定时间内(默认为 10 秒)未响应,从节点会认为主节点不可用。

(2) 发起选举

  • 从节点会发起选举请求,向其他节点发送投票请求。
  • 每个从节点和仲裁节点都会参与投票。

(3) 投票规则

  • 新主节点必须获得大多数节点的投票(例如,在 3 个节点的复制集中,至少需要 2 票)。
  • 优先级高的节点更有可能被选为主节点(可以通过 priority 参数设置优先级)。

(4) 选出新主节点

  • 获得大多数投票的从节点将成为新的主节点。
  • 新的主节点开始处理写操作,并继续将操作日志(Oplog)发送给其他从节点。

选举的配置参数

可以通过以下参数配置选举行为:

(1) priority

  • 设置节点的优先级,优先级高的节点更有可能被选为主节点。
  • 默认优先级为 1,优先级为 0 的节点不能成为主节点。

(2) votes

  • 设置节点是否有投票权(默认所有节点都有投票权)。
  • 无投票权的节点不参与选举。

(3) arbiterOnly

  • 将节点配置为仲裁节点(不存储数据,仅参与投票)。

示例配置

cfg = rs.conf()
cfg.members[0].priority = 2  # 设置第一个节点的优先级为 2
cfg.members[1].priority = 1  # 设置第二个节点的优先级为 1
cfg.members[2].arbiterOnly = true  # 将第三个节点配置为仲裁节点
rs.reconfig(cfg)

手动触发选举

如果需要手动触发选举(如维护主节点),可以使用以下命令:

rs.stepDown()  # 让当前主节点主动退出

如何向复制集中添加新节点?

1. 准备工作

  • 新节点:确保新节点的 MongoDB 实例已安装并启动。
  • 网络:确保新节点与复制集中的其他节点可以互相通信。
  • 配置文件:在新节点的 mongod.conf 中配置复制集名称(replSetName),与现有复制集一致。

示例配置

replication:
  replSetName: "rs0"

2. 连接到主节点
使用 mongo shell 连接到复制集的 主节点(Primary)。

mongo --host primary:27017

3. 添加新节点
使用 rs.add() 命令将新节点添加到复制集中。

语法

rs.add("新节点地址:端口")

4. 检查复制集状态
使用 rs.status() 命令检查复制集的状态,确保新节点已成功加入并正常运行。

rs.status()

在输出中,新节点应显示为 SECONDARY 状态。


5. 验证数据同步
在新节点上验证数据是否已从主节点同步。

步骤 1:连接到新节点

mongo --host new-node:27017

步骤 2:设置从节点可读
默认情况下,从节点不能处理读操作,需要设置 slaveOk

rs.slaveOk()

步骤 3:查询数据

db.test.find()

6. 配置节点属性(可选)
如果需要为新节点配置特殊属性(如优先级、隐藏节点、延迟节点等),可以使用 rs.reconfig() 命令。

示例:设置优先级

cfg = rs.conf()
cfg.members[3].priority = 1  // 假设新节点是第 4 个成员
rs.reconfig(cfg)

7. 添加仲裁节点(可选)
如果希望添加仲裁节点(Arbiter),可以使用 rs.addArb() 命令。

语法

rs.addArb("仲裁节点地址:端口")

五、分片

复制集与分片的区别?

复制时让多台服务器都拥有同样的数据副本,每一台服务器都是其他服务器的镜像。

而每一个分片都和其他分片拥有不同的数据子集

分片原理

当MongoDB存储海量的数据时,一台机器可能不足以存储数据,也可能不足以提供可接受的读写吞吐量。
这时,可通过在多台机器上分割数据,使得数据库系统能存储和处理更多的数据。
即通过分片进行水平扩展

分片架构

主要组件

在这里插入图片描述

1. Shard(分片)
用于存储实际的数据块。每个 Shard 是一个独立的 MongoDB 实例或复制集,负责存储数据的一部分。

特点

  • 数据分片:数据根据分片键(Shard Key)被划分到不同的 Shard 中。
  • 水平扩展:通过增加 Shard,可以扩展集群的存储容量和吞吐量。

示例
假设有一个分片集群,包含 3 个 Shard(分片):

  • shard1:存储 user_id 范围为 [0, 1000) 的数据。
  • shard2:存储 user_id 范围为 [1000, 2000) 的数据。
  • shard3:存储 user_id 范围为 [2000, 3000) 的数据。

2. Config Server(配置服务器)
存储分片集群的 元数据,包括集群的配置信息和分片数据的位置信息。

Query Routers(查询路由器)
Query Routers(也称为 mongos)是 MongoDB 分片集群的查询路由组件,负责将客户端请求路由到正确的 Shard。

客户端无需知道数据的具体分布,只需连接到 mongos 即可。


分片的查询流程

  1. 客户端请求:客户端连接到 mongos 实例,发送查询或写操作请求。
  2. 查询路由mongos 根据分片键和 Config Server 中的元数据信息,将请求路由到相应的 Shard。
  3. 数据操作:Shard 执行查询或写操作,并将结果返回给 mongos
  4. 结果返回mongos 将结果返回给客户端。

shard key (分片键)

在集合中分发文档,MongoDB 使用 shard key 对进行进行分片。

shard key 既可以是集合的每个文档的索引字段也可以是集合中每个文档都有的组合索引字段。

MongoDB 将 shard keys 值按照块(chunks)划分,并且均匀的将这些chunks分配到各个分片上。

MongoDB使用基于范围划分或基于散列划分来划分chunks的。

注意:确定shard key时需要谨慎,以确保集群性能和效率。分片后不能更改shard key,也不能取消分片。

Shard Key 的特点

  • 唯一性:每个文档的 Shard Key 值必须是唯一的,或者至少是高度唯一的。
  • 不可变性:一旦文档插入集合,其 Shard Key 的值不能被修改。
  • 分布性:Shard Key 的值应尽可能均匀分布,以确保数据在各个 Shard 上均衡分布。

Shard Key 的类型

(1) 单字段 Shard Key

  • 使用单个字段作为 Shard Key。
  • 示例:{ user_id: 1 }

(2) 复合 Shard Key

  • 使用多个字段的组合作为 Shard Key。
  • 示例:{ user_id: 1, timestamp: 1 }

Shard Key 的配置

在 MongoDB 中,可以通过以下步骤配置 Shard Key:

(1) 启用分片

sh.enableSharding("testDB")

(2) 选择 Shard Key

sh.shardCollection("testDB.users", { user_id: 1 })

Shard Key 的优化

(1) 避免写热点

  • 如果 Shard Key 的值单调递增(如时间戳),可能导致写操作集中在某个 Shard 上。
  • 解决方案:使用哈希 Shard Key 或将单调递增字段与其他字段组合。

(2) 避免跨分片查询

  • 如果查询条件不包含 Shard Key,MongoDB 需要在所有 Shard 上执行查询,性能较差。
  • 解决方案:确保查询条件包含 Shard Key。

分片策略

基于范围划分

MongoDB 通过 shard key 值将数据集划分到不同的范围就称为基于范围划分。

相邻的数据通常存储在同一个 Shard 上。

在这里插入图片描述

基于散列划分

MongoDB 计算每个字段的 hash 值,然后用这些 hash 值建立chunks。

相邻的数据可能存储在不同的 Shard 上。

基于散列值的数据分布有助于更均匀的数据分布,尤其是在shard key单调变化的数据集中。

但是,散列分布意味着对shard key的基于范围的查询不太可能以单个分片为目标,从而导致更多群集范围的广播操作。
在这里插入图片描述

基于范围和基于散列划分的对比
特性基于范围的分片基于散列的分片
数据分布相邻数据通常存储在同一个 Shard 上。数据均匀分布,相邻数据可能存储在不同 Shard 上。
范围查询性能性能较好,查询可以路由到特定 Shard。性能较差,需要跨多个 Shard 查询。
精确查询性能性能较好,查询可以路由到特定 Shard。性能较好,查询可以路由到特定 Shard。
写分布可能导致写热点。写操作均匀分布,避免写热点。
适用场景查询模式以范围查询为主。查询模式以精确查询为主,或需要避免写热点。

MongoDB 默认使用 ​基于范围的分片


在 MongoDB 分片集群中,分片键(Shard Key) 的选择对集群的性能、扩展性和数据分布至关重要。以下是选择合适分片键的原则和步骤:


分片键的选择原则

(1) 高基数(High Cardinality)

  • 分片键的值应具有高基数(即大量唯一值),以确保数据能够均匀分布。
  • 示例:user_id 是一个高基数字段,而性别 gender 是一个低基数字段。

(2) 低频率(Low Frequency)

  • 分片键的值应具有低频率(即每个值出现的次数较少),以避免某些 Chunk 过大。
  • 示例:timestamp 是一个低频率字段,而 status 可能是一个高频率字段。

(3) 查询模式(Query Patterns)

  • 分片键应支持常见的查询模式,避免跨分片查询,以提高查询性能。
  • 示例:如果查询模式主要基于 user_id,选择 user_id 作为分片键。

(4) 写分布(Write Distribution)

  • 分片键应确保写操作能够均匀分布到各个 Shard 上,避免写热点
  • 示例:如果 timestamp 是单调递增的,直接使用它作为分片键可能导致写热点,可以使用哈希分片。

(5) 不可变性(Immutable)

  • 分片键的值一旦插入文档后不能被修改,否则会导致数据迁移和性能问题。

分片键的类型

(1) 单字段分片键

  • 使用单个字段作为分片键。
  • 示例:{ user_id: 1 }

(2) 复合分片键

  • 使用多个字段的组合作为分片键。
  • 示例:{ user_id: 1, timestamp: 1 }

(3) 哈希分片键

  • 使用哈希函数对字段值进行哈希计算,将数据均匀分布到不同的 Shard 上。
  • 示例:{ user_id: "hashed" }

分片键的配置

在 MongoDB 中,可以通过以下步骤配置分片键:

(1) 启用分片

sh.enableSharding("testDB")

(2) 选择分片键

sh.shardCollection("testDB.users", { user_id: 1 })

分片键的优化

(1) 避免写热点

  • 如果分片键的值单调递增(如时间戳),可能导致写操作集中在某个 Shard 上。
  • 解决方案:使用哈希分片或将单调递增字段与其他字段组合。

(2) 避免跨分片查询

  • 如果查询条件不包含分片键,MongoDB 需要在所有 Shard 上执行查询,性能较差。
  • 解决方案:确保查询条件包含分片键。

(3) 监控和调整 Chunk 大小

  • 使用 sh.status() 监控 Chunk 分布,并根据需要手动拆分或迁移 Chunk。

六、性能优化

  1. MongoDB查询性能慢的可能原因有哪些?
  2. 如何通过索引优化查询性能?
  3. 如何避免全表扫描?
  4. 如何优化MongoDB的内存使用?
  5. 调整缓存大小对性能有何影响?
  6. 如何优化写入性能?
  7. 在高并发场景下如何优化性能?
  8. 如何使用性能分析工具定位问题?

七、安全

  1. 如何在MongoDB中启用身份验证?
  2. MongoDB内置角色的权限范围是什么?
  3. 如何创建自定义角色并分配权限?
  4. 如何配置网络访问安全(如绑定IP地址)?
  5. 如何使用SSL/TLS加密数据传输?
  6. 如何备份和恢复MongoDB数据?
  7. 如何管理多用户环境下的权限?
  8. 如何防止MongoDB受到常见安全攻击(如注入攻击)?

八、高级应用

  1. 如何在MongoDB中实现事务处理?
  2. 如何使用MongoDB的地理空间功能?
  3. 如何在MongoDB中实现全文搜索?
  4. 如何与Node.js、Python等技术集成使用MongoDB?
  5. 如何处理大规模数据的导入和导出?
  6. 如何对MongoDB进行水平扩展和垂直扩展?
  7. 如何设计和优化数据模型以适应复杂业务需求?
  8. 如何在MongoDB中实现数据的版本控制?


http://www.niftyadmin.cn/n/5868866.html

相关文章

一周学会Flask3 Python Web开发-Jinja2模板过滤器使用

锋哥原创的Flask3 Python Web开发 Flask3视频教程: 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 在Jinja2中,过滤器(filter)是一些可以用来修改和过滤变量值的特殊函数,过滤器和变量用一个竖线 | &a…

Redis存储​⑫​哨兵Sentinel_高可用实现方案

目录 1. 哨兵Sentinel概念 1.1 主从复制的缺点 1.2 人工恢复主节点故障 1.3 哨兵自动恢复主节点故障 2. 重新选举过程 3. 选举原理 3.1 主观下线 3.2 客观下线 3.3 选举出哨兵的 leader 3.4 leader挑选出master 本篇完。 Redis 的主从复制模式下,一旦主节…

OpenCV计算摄影学(2)图像去噪函数denoise_TVL1()

操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 原始-对偶算法是用于解决特定类型变分问题(即,寻找一个函数以最小化某个泛函)的算法。特别地,图像…

Three.js 入门(辅助、位移、父子关系、缩放旋转、响应式布局)

本篇主要学习内容 : 三维坐标系与辅助坐标系物体位移与父子元素物体的缩放与物体的旋转设置响应式画布与全屏控制 点赞 关注 收藏 学会了 本文使用 Three.js 的版本:171 基于 Vue3vite开发调试 1.三维坐标系与辅助坐标系 1.1) 导入three和轨道控制器 // 导入…

AI将会取代生活的方方面面吗?

当然,无法完全取代不代表没有影响。当我们探讨“干什么不会被取代”时,就意味着一部分“取代”正在或已经发生。 从上述种种案例也能看出,AI足以扛下众多行业中最简单、最前端的“低技能”工作,且与此前技术革命解放体力劳动相比&…

AI驱动的自动化留给人类的时间不多了

时间紧迫!时间紧迫!时间紧迫! 关于AI工作流催生的行业任务自动化时间窗口,结合技术成熟度、成本效益、行业特性等维度,可划分为以下阶段: 一、技术渗透阶段(2025-2028年) 高重复性任…

网络安全复习资料

网络安全复习资料 1.计算机网络安全是指保持网络中的硬件、软件系统正常运行,使他们不因自然和人为的因素而受到破坏、更改和泄露。 2.网络安全:物理安全,软件安全,信息安全,运行安全。 3.安全防范措施&#xff1a…

SV基础(一):System Verilog与Verilog核心区别详解

文章目录 **1. 设计增强功能****数据类型扩展****接口(Interface)****2. 验证功能增强****断言(Assertions)****约束随机测试****功能覆盖率****3. 面向对象编程(OOP)****4. 测试平台(Testbench)改进****5. 语法简化****6. 其他关键区别****学习建议**System Verilog 是…