MongoDB 笔记

一、基础概念

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 副本集 架构

  • 一个 MongoDB 副本集由多个节点组成,包括:
    • 主节点(Primary):负责处理所有写操作和读操作。
    • 从节点(Secondary):复制主节点的数据,提供读操作(可选)。
    • 仲裁节点(Arbiter):不存储数据,仅参与选举。
  • 当主节点失效时,副本集会通过选举机制选出一个新的主节点。

选举机制

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

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

Raft 是一种分布式一致性算法,旨在通过选举和日志复制来保证分布式系统的一致性和高可用性。MongoDB 在 Raft 的基础上进行了一些调整和优化,以适应其特定的需求。

Raft 算法的核心思想

Raft 算法通过以下机制保证分布式系统的一致性:

  • Leader 选举:选出一个主节点(Leader),负责管理日志复制和客户端请求。
  • 日志复制:主节点将日志复制到所有从节点,确保数据一致性。
  • 安全性:通过大多数投票机制和日志匹配原则,保证系统的强一致性。

触发选举的条件

以下情况会触发选举:

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

手动触发选举

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

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

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

选举的过程

选举过程包括以下步骤:

(1) 检测主节点故障

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

(2) 发起选举

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

(3) 投票规则

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

(4) 选出新主节点

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

MongoDB 允许为副本集中的节点设置优先级(Priority),优先级高的节点更有可能被选为主节点。

  • 优先级为 0 的节点永远不会成为主节点。
  • 优先级高的节点在选举中更有可能获得投票。

选举的配置参数

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

(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)

选举超时
  • 选举过程必须在 settings.electionTimeoutMillis(默认 10 秒)内完成,否则选举失败,重新发起选举。

选举中的脑裂问题
  • 在网络分区的情况下,可能会出现多个主节点(脑裂问题)。
  • MongoDB 通过大多数投票机制来避免脑裂,只有获得大多数节点认可的节点才能成为主节点。

选举的影响
  • 性能影响:选举期间,副本集无法处理写操作,直到选出新的主节点。
  • 数据一致性:选举期间,可能会丢失部分未提交的写操作。

优化选举
  • 增加节点数量:确保副本集中有足够多的节点,避免因节点故障导致选举失败。
  • 合理配置优先级:将性能较好的节点设置为高优先级。
  • 优化网络:减少网络延迟和分区,确保节点之间的通信稳定。

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

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。

分片键的监控

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


六、性能优化

查询性能慢的可能原因有哪些?

1. 索引问题

原因

  • 缺少索引:查询未使用索引,导致全表扫描。
  • 索引不合理:索引未覆盖查询条件或排序字段。
  • 索引过多:过多的索引会增加写操作的开销,影响性能。

解决方法

  • 使用 explain() 分析查询计划,确保查询使用了索引。
  • 创建合适的索引,覆盖查询条件和排序字段。
  • 删除不必要的索引,减少写操作的开销。

2. 查询条件不合理

原因

  • 范围查询过大:范围查询(如 { age: { $gte: 0, $lte: 100 } })导致扫描大量文档。
  • 正则表达式查询:正则表达式查询(如 { name: /^A/ })性能较差。
  • 跨分片查询:在分片集群中,查询条件不包含分片键,导致跨分片查询。

解决方法

  • 优化查询条件,缩小范围查询的范围。
  • 尽量避免使用正则表达式查询,或使用索引支持的正则表达式。
  • 在分片集群中,确保查询条件包含分片键。

3. 数据分布不均匀

原因

  • 分片键选择不合理:分片键导致数据分布不均匀,某些 Shard 负载过高。
  • Chunk 分布不均匀:Chunk 分布不均匀,导致某些 Shard 负载过高。

解决方法

  • 选择合适的分片键,确保数据均匀分布。
  • 使用 sh.status() 监控 Chunk 分布,并根据需要手动拆分或迁移 Chunk。

4. 硬件资源不足

原因

  • 内存不足:内存不足导致频繁的磁盘 I/O 操作。
  • CPU 瓶颈:CPU 负载过高,无法及时处理查询请求。
  • 磁盘性能差:磁盘 I/O 性能差,影响数据读取和写入速度。

解决方法

  • 增加内存,确保 MongoDB 能够缓存更多的数据和索引。
  • 升级 CPU,提高计算能力。
  • 使用高性能磁盘(如 SSD),提高 I/O 性能。

5. 锁争用

原因

  • 写锁争用:高并发的写操作导致锁争用,影响查询性能。
  • 全局锁:某些操作(如创建索引)会占用全局锁,影响其他操作。

解决方法

  • 优化写操作,减少锁争用。
  • 在低峰期执行占用全局锁的操作。

6. 网络延迟

原因

  • 网络带宽不足:网络带宽不足导致数据传输速度慢。
  • 网络延迟高:网络延迟高导致查询响应时间增加。

解决方法

  • 增加网络带宽,提高数据传输速度。
  • 优化网络配置,减少网络延迟。

7. 查询计划缓存失效

原因

  • 查询计划缓存失效:查询计划缓存失效导致 MongoDB 需要重新生成查询计划,影响性能。

解决方法

  • 使用 explain() 分析查询计划,确保查询计划缓存有效。
  • 优化查询条件,减少查询计划缓存失效的可能性。

explain

explain() 的基本用法

explain() 可以附加到查询操作(如 find()aggregate()update() 等)之后,返回查询的执行计划。

语法

db.collection.find(query).explain()

示例

db.users.find({ age: { $gte: 25 } }).explain()

explain() 的详细模式

explain() 支持三种模式,分别提供不同详细程度的执行计划信息:

(1) queryPlanner 模式

  • 默认模式,提供查询计划的基本信息,如是否使用索引、扫描的文档数量等。
  • 语法
    db.collection.find(query).explain("queryPlanner")
    

(2) executionStats 模式

  • 提供查询执行的详细统计信息,如执行时间、扫描的文档数量、返回的文档数量等。
  • 语法
    db.collection.find(query).explain("executionStats")
    

(3) allPlansExecution 模式

  • 提供所有候选查询计划的执行统计信息,帮助分析 MongoDB 选择最优查询计划的过程。
  • 语法
    db.collection.find(query).explain("allPlansExecution")
    

explain() 输出解析

以下是 explain() 输出的关键字段及其含义:

(1) queryPlanner

  • winningPlan:MongoDB 选择的查询计划。
    • stage:查询阶段(如 COLLSCAN 表示全表扫描,IXSCAN 表示索引扫描)。
    • indexName:使用的索引名称。
    • direction:索引扫描方向(如 forwardbackward)。
  • rejectedPlans:被拒绝的候选查询计划。

(2) executionStats

  • executionTimeMillis:查询执行时间(毫秒)。
  • totalDocsExamined:扫描的文档数量。
  • totalKeysExamined:扫描的索引键数量。
  • nReturned:返回的文档数量。
  • executionStages:查询执行的详细阶段信息。

(3) allPlansExecution

  • allPlansExecution:所有候选查询计划的执行统计信息。

举例

{"queryPlanner": {"plannerVersion": 1,"namespace": "testDB.users","indexFilterSet": false,"parsedQuery": {"age": { "$gte": 25 }},"winningPlan": {"stage": "FETCH","inputStage": {"stage": "IXSCAN",  // 使用了索引扫描"keyPattern": { "age": 1 },"indexName": "age_1",  // 命中的索引名称"direction": "forward","indexBounds": {"age": ["[25, inf.0]"]}}},"rejectedPlans": []},"executionStats": {"executionSuccess": true,"nReturned": 10,  // 查询返回了 10 条文档。"executionTimeMillis": 5,  // 查询执行时间为 5 毫秒"totalKeysExamined": 10,  // 扫描了 10 条文档"totalDocsExamined": 10,  // 扫描了 10 个索引键。"executionStages": {"stage": "FETCH","nReturned": 10,"executionTimeMillisEstimate": 1,"inputStage": {"stage": "IXSCAN","nReturned": 10,"executionTimeMillisEstimate": 0,"keyPattern": { "age": 1 },"indexName": "age_1","direction": "forward","indexBounds": {"age": ["[25, inf.0]"]}}}},"serverInfo": {"host": "localhost","port": 27017,"version": "5.0.9","gitVersion": "abcdefg"},"ok": 1
}

使用 explain() 优化查询

通过 explain() 可以分析查询的性能瓶颈,并采取相应的优化措施:

(1) 检查是否使用索引

  • 如果 stageCOLLSCAN,表示查询未使用索引,需要创建合适的索引。
  • 示例:
    db.users.find({ age: { $gte: 25 } }).explain("executionStats")
    

(2) 检查索引覆盖

  • 如果 totalDocsExamined 大于 nReturned,表示索引未完全覆盖查询条件,需要优化索引。
  • 示例:
    db.users.find({ age: { $gte: 25 } }, { name: 1, _id: 0 }).explain("executionStats")
    

(3) 检查查询执行时间

  • 如果 executionTimeMillis 较高,需要优化查询条件或索引。
  • 示例:
    db.users.find({ age: { $gte: 25 } }).explain("executionStats")
    

(4) 检查扫描的文档数量

  • 如果 totalDocsExamined 较高,表示查询扫描了大量文档,需要优化查询条件或索引。
  • 示例:
    db.users.find({ age: { $gte: 25 } }).explain("executionStats")
    

七、高级应用

如何备份和恢复MongoDB数据?

1. 备份 MongoDB 数据

使用 mongodump 备份

mongodump 是 MongoDB 提供的备份工具,可以将数据导出为 BSON 文件。

语法

mongodump --uri <connectionString> --out <backupDirectory>

示例

备份整个数据库到 /backup 目录:

mongodump --uri "mongodb://localhost:27017" --out /backup

备份指定数据库(如 testDB):

mongodump --uri "mongodb://localhost:27017/testDB" --out /backup

备份指定集合(如 testDB.users):

mongodump --uri "mongodb://localhost:27017" --db testDB --collection users --out /backup

2. 恢复 MongoDB 数据

使用 mongorestore 恢复

mongorestore 是 MongoDB 提供的恢复工具,可以将 BSON 文件导入到 MongoDB 中。

语法

mongorestore --uri <connectionString> <backupDirectory>

示例
恢复整个数据库:

mongorestore --uri "mongodb://localhost:27017" /backup

恢复指定数据库(如 testDB):

mongorestore --uri "mongodb://localhost:27017" --db testDB /backup/testDB

恢复指定集合(如 testDB.users):

mongorestore --uri "mongodb://localhost:27017" --db testDB --collection users /backup/testDB/users.bson

示例完整流程
(1) 备份

mongodump --uri "mongodb://localhost:27017" --out /backup

(2) 恢复

mongorestore --uri "mongodb://localhost:27017" /backup

事务处理

MongoDB 从 4.0 版本开始支持多文档事务,并在 4.2 版本中扩展到了分片集群。

  • ACID 特性
    • 原子性(Atomicity):事务中的所有操作要么全部成功,要么全部失败。
    • 一致性(Consistency):事务执行前后,数据库的状态保持一致。
    • 隔离性(Isolation):事务执行过程中,其他操作无法看到未提交的数据。
    • 持久性(Durability):事务提交后,数据将永久保存。

事务的使用条件
  • MongoDB 版本:4.0 及以上版本支持副本集事务,4.2 及以上版本支持分片集群事务。
  • 存储引擎:必须使用 WiredTiger 存储引擎。
  • 集合类型:事务中的集合必须是已存在的集合。

实现事务的步骤

(1) 启动会话(Session)

事务需要在会话(Session)中执行。会话是一个 MongoDB 客户端与服务器之间的上下文。

语法

const session = db.getMongo().startSession();

(2) 启动事务

在会话中启动事务。

语法

session.startTransaction();

(3) 执行操作

在事务中执行多个操作(如插入、更新、删除等)。

语法

const collection = session.getDatabase("testDB").getCollection("users");
collection.insertOne({ name: "Alice", age: 25 });
collection.updateOne({ name: "Alice" }, { $set: { age: 26 } });

(4) 提交事务

如果所有操作成功,提交事务。

语法

session.commitTransaction();

(5) 回滚事务

如果任何操作失败,回滚事务。

语法

session.abortTransaction();

(6) 结束会话

事务结束后,关闭会话。

语法

session.endSession();

事务的完整示例

以下是一个完整的 MongoDB 事务示例:

// javascriptconst session = db.getMongo().startSession();try {session.startTransaction();const usersCollection = session.getDatabase("testDB").getCollection("users");const ordersCollection = session.getDatabase("testDB").getCollection("orders");// 插入用户usersCollection.insertOne({ user_id: 1, name: "Alice", balance: 100 });// 插入订单ordersCollection.insertOne({ user_id: 1, order_id: 1, amount: 50 });// 更新用户余额usersCollection.updateOne({ user_id: 1 }, { $inc: { balance: -50 } });// 提交事务session.commitTransaction();console.log("Transaction committed successfully.");
} catch (error) {// 回滚事务session.abortTransaction();console.log("Transaction aborted due to error:", error);
} finally {// 结束会话session.endSession();
}

预分配空间机制

MongoDB 使用预分配空间机制来管理数据文件,以提高写入性能和减少磁盘碎片。

特点

  • 文件大小增长:每次新分配的数据文件大小是上一个文件的 2 倍,直到达到 2GB 的最大限制。
    • 例如:第一个文件为 64MB,第二个为 128MB,第三个为 256MB,依此类推。
  • 填充 0:预分配的文件会用 0 进行填充,确保文件在磁盘上是连续的,减少写入时的碎片化。

优点

  • 减少频繁分配文件的开销,提高写入性能。
  • 保持文件的连续性,减少磁盘碎片。

缺点

  • 可能导致磁盘空间的浪费,尤其是在数据库初始化时。

文件存储结构

MongoDB 的数据文件和命名空间文件在磁盘上的存储结构如下:

数据文件

  • 文件名为 dbname.0dbname.1dbname.2 等。
  • 文件大小按 2 倍增长,最大为 2GB。
  • 文件内部划分为多个 盘区(Extent),用于存储集合或索引的数据。

命名空间文件

  • 文件名为 dbname.ns
  • 存储集合和索引的命名空间元数据。
  • 默认大小为 16MB,可通过 --nssize 参数调整。

$freelist

  • 用于记录不再使用的盘区(如删除的集合或索引)。
  • 当需要分配新盘区时,MongoDB 会优先从 $freelist 中回收空闲的磁盘空间。

盘区分配策略

MongoDB 的盘区分配策略旨在平衡空间利用率和数据连续性。

特点

  • 盘区大小增长:每次分配的盘区大小是上一次的 2 倍。
  • 非连续性:每个命名空间对应的盘区不一定是连续的。
  • 空间复用:删除集合或索引后,其占用的盘区会被记录在 $freelist 中,供后续分配时复用。

优点

  • 减少空间浪费,提高磁盘利用率。
  • 保持数据的连续性,提高查询性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/25773.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小程序Three Dof识别 实现景区AR体验

代码工程 GitCode - 全球开发者的开源社区,开源代码托管平台 dof

ABAP语言的动态程序

通过几个例子&#xff0c;由浅入深讲解 ABAP 动态编程。ABAP 动态编程主要通过 RTTS (Runtime Type Services) 来实现&#xff0c;包括 RTTI 和 RTTC: 运行时类型标识&#xff08;RTTI&#xff09; – 提供在运行时获取数据对象的类型定义的方法。运行时类型创建&#xff08;R…

【安卓】BroadcastReceiver 动态声明为 RECEIVER_NOT_EXPORTED 后无法接收任何 Intent 的问题

一、问题起因 自 Android 14 (API 级别 34) 起&#xff0c;使用 context.registerReceiver(receiver, filter, flags) 动态注册广播接收器时&#xff0c;必须显式地声明 RECEIVER_NOT_EXPORTED 或 RECEIVER_EXPORTED 。 如果声明为 RECEIVER_EXPORTED &#xff0c;任何第三方应…

unity pico开发二:创建基本的交互

文章目录 导入UnityXR Interaction ToolKit构建基础内容 导入UnityXR Interaction ToolKit 检查一下packagemanager&#xff0c;unityxr interactionToolkit是否自动导入 我们需要升级到一个不超过3.x的版本&#xff0c;因为pico还不支持3.x的内容 然后右侧samples里导入初始…

[STM32]从零开始的STM32 DEBUG问题讲解及解决办法

一、前言 最近也是重装了一次keil&#xff0c;想着也是重装了&#xff0c;也是去官网下载了一个5.41的最新版&#xff0c;在安装和配置编译器和别的版本keil都没太大的区别&#xff0c;但是在调试时&#xff0c;遇到问题了&#xff0c;在我Debug的System Viewer窗口中没有GPIO&…

学习threejs,使用ShaderMaterial自定义着色器材质

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.ShaderMaterial1.1.1…

查看ITHOR全部仿真家庭场景

1. 目标 按序号显示所有120个家庭场景统计单个场景里物体数量 2. 代码 import time from ai2thor.controller import Controller# 统计当前场景中的物体数量 def count_objects_in_scene(controller):objects controller.last_event.metadata["objects"]object_c…

ES6 特性全面解析与应用实践

1、let let 关键字用来声明变量&#xff0c;使用let 声明的变量有几个特点&#xff1a; 1) 不允许重复声明 2) 块儿级作用域 3) 不存在变量提升 4) 不影响作用域链 5) 暂时性死区 6&#xff09;不与顶级对象挂钩 在代码块内&#xff0c;使用let命令声明变量之前&#x…

VSCode轻松调试运行C#控制台程序

1.背景 我一直都是用VS来开发C#项目的&#xff0c;用的比较顺手&#xff0c;也习惯了。看其他技术文章有介绍VS Code更轻量&#xff0c;更方便。所以我专门花时间来使用VS Code&#xff0c;看看它是如何调试代码、如何运行C#控制台。这篇文章是一个记录的过程。 2.操作 2.1 V…

【多模态】Magma多模态AI Agent

1. 前言 微软杨建伟团队&#xff0c;最近在AI Agent方面动作连连&#xff0c;前两天开源了OmniParser V2&#xff0c;2月26日又开源了Magma&#xff0c;OmniParser专注在对GUI的识别解析&#xff0c;而Magma则是基于多模态技术&#xff0c;能够同时应对GUI和物理世界的交互&…

spring Boot入门

目录 Spring Boot 概述 新建Spring Boot项目 方式一&#xff1a;使用Spring Initializr创建SpringBoot项目 方式二&#xff1a;使用Maven方式构建Spring Boot项目 Spring Boot 概述 简介 •Spring Boot是基于Spring框架开发的全新框架&#xff0c;其设计目的是简化Java…

手机隐私数据彻底删除工具:回收或弃用手机前防数据恢复

软件介绍 有这样一款由吾爱网友chenwangjun 原创开发的数据处理软件&#xff0c;名为 AndroidDiskClear。它的核心功能十分强大&#xff0c;能够将你手机里已经删除的各类文件&#xff0c;像图片、普通文件、文字信息等彻底清除干净&#xff0c;有效杜绝数据恢复类软件的二次恢…

docker使用代理的简单配置

1准备代理服务器 准备代理服务器&#xff0c;例如192.168.120.168:52209 配置docker.service文件 查看service文件的位置 systemctl status docker 编辑service文件 vim /usr/lib/systemd/system/docker.service 添加代理配置 ... [Service] Environment"HTTP_PROXY…

Coze与Dify:企业级大模型应用开发认知陷阱与破局之道

前言 当前大模型应用开发似乎陷入了一种“范式陷阱”&#xff1a;当人们谈论AI Agent或智能体时&#xff0c;脑海里浮现的往往是Coze、Dify这类以对话交互为核心的低代码平台。这些工具确实降低了体验大模型的门槛&#xff0c;但也让行业陷入一种危险的认知偏差——将大模型等…

GitHub 语析 - 基于大模型的知识库与知识图谱问答平台

语析 - 基于大模型的知识库与知识图谱问答平台 GitHub 地址&#xff1a;https://github.com/xerrors/Yuxi-Know &#x1f4dd; 项目概述 语析是一个强大的问答平台&#xff0c;结合了大模型 RAG 知识库与知识图谱技术&#xff0c;基于 Llamaindex VueJS FastAPI Neo4j 构…

由浅入深系列——Distinctive Image Featuresfrom Scale-Invariant Keypoints(SIFT)

第一章&#xff1a;为什么我们需要"图像指纹"&#xff1f;——SIFT的诞生 想象一下&#xff0c;你带着一张埃菲尔铁塔的明信片来到巴黎。站在铁塔脚下&#xff0c;你举起明信片想拍张对比照——但无论怎么调整角度&#xff0c;手机APP就是识别不出两张图片的对应关系…

spring注解开发(Spring整合MyBatis——Mapper代理开发模式、(Spring、MyBatis、Jdbc)配置类)(6)

目录 一、纯MyBatis独立开发程序。 &#xff08;1&#xff09;数据库与数据表。 &#xff08;2&#xff09;实体类。 &#xff08;3&#xff09;dao层接口。&#xff08;Mapper代理模式、无SQL映射文件——注解配置映射关系&#xff09; &#xff08;4&#xff09;MyBatis核心配…

Redis缓存一致性难题:如何让数据库和缓存不“打架”?

标题&#xff1a;Redis缓存一致性难题&#xff1a;如何让数据库和缓存不“打架”&#xff1f;&#xff08;附程序员脱发指南&#xff09; 导言&#xff1a;当数据库和缓存成了“异地恋” 想象一下&#xff1a;你刚在美团下单了一份麻辣小龙虾&#xff0c;付款后刷新页面&#…

eMMC安全简介

1. 引言 术语“信息安全”涵盖多种不同的设计特性。一般而言&#xff0c; 信息安全是指通过实践防止信息遭受未经授权的访问、使用、披露、中断、篡改、检查、记录或销毁。 信息安全的三大核心目标为 机密性&#xff08;Confidentiality&#xff09;、完整性&#xff08;Integr…

如何用python画一棵分形树

这个代码会生成一个彩色的分形树图案&#xff0c;可以通过调整draw_tree函数中的参数来改变树的形状和大小 import turtle import random# 递归函数绘制分形树 def draw_tree(branch_len, t):if branch_len > 5:t.color(random.choice(colors))t.pensize(branch_len / 10)t…