MongoDB
MongoDB基础
MongoDB是什么
MongoDB 是一个基于文档模型的 NoSQL 数据库,用来存储和管理非结构化或半结构化数据。它以 JSON 类似的 BSON 格式存储数据,每条记录是一个文档(document),不同文档可以拥有不同的字段和结构,因此更灵活。
MongoDB 的优势在于其数据模型和存储引擎的灵活性、架构的可扩展性以及对强大的索引支持。
MongoDB的特点
MongoDB 是一个面向文档的 NoSQL 数据库,具有以下几个核心特点:
- 灵活的数据模型 使用 BSON(类似 JSON)格式存储文档,不需要固定表结构,字段可动态添加,适合存储非结构化或半结构化数据。
- 高性能读写 支持高并发访问,读写效率高,适用于实时性要求强的场景。
- 强大的查询能力 支持丰富的查询语法、聚合管道、全文搜索和索引机制,满足复杂的数据检索需求。
- 高可用性和容灾能力 通过复制集(Replica Set)实现主从复制和故障自动切换,保障服务连续性。
- 良好的扩展性 内置自动分片(Sharding)机制,支持横向扩展,可支撑大规模数据增长。
- 天然支持嵌套结构和数组 非关系型模型更适合表示层级、嵌套的数据,如评论、聊天记录等。
MongoDB的应用场景
MongoDB 适用于结构灵活、数据量大、变化频繁、强一致性要求不高的场景,典型如内容管理、日志系统、IoT 数据、社交评论、地图服务等。
MongoDB 通常用于以下几类场景:
- 数据结构不固定或经常变化的业务 比如内容管理系统、CMS、用户自定义表单等,字段不固定,关系型数据库维护困难,MongoDB 的文档模型更灵活。
- 需要快速迭代的互联网应用 例如初创项目、电商商品库、活动页配置等,开发周期短、需求变化快,MongoDB 省去了建表和字段管理的繁琐。
- 日志收集与分析 MongoDB 非结构化文档存储适合记录日志、埋点数据等,配合聚合查询和索引可以进行实时分析。
- 物联网(IoT)设备数据存储 设备上报的数据格式多样、更新频繁,用 MongoDB 存储更方便管理和查询。
- 地理位置类应用 比如地图服务、附近的人、LBS 系统,MongoDB 支持地理空间索引,适合高效进行地理位置查询。
- 社交网络、评论、聊天记录等嵌套数据结构场景 这类数据常包含嵌套数组、层级结构,MongoDB 能自然表达这类文档,比传统关系型数据库更适配。
- 缓存层或用户画像等非核心数据系统 对强一致性要求不高,但读写性能要求高,适合用 MongoDB 提供支撑。
和MySQL、Redis有什么区别
MongoDB、MySQL 和 Redis 是三种性质不同的数据库系统,适用于不同类型的业务需求。首先,从数据模型上看,MySQL 是典型的关系型数据库,采用表格结构,字段固定、模式严格,适合结构清晰、强一致性要求高的场景。而 MongoDB 是一个面向文档的 NoSQL 数据库,使用 BSON 格式存储数据,结构灵活,字段可以动态变化,更适合处理结构不确定、变化频繁的业务,比如内容管理系统或用户自定义数据。
从存储和使用方式上来看,MongoDB 和 MySQL 都是主要存储在磁盘上的持久化数据库,适合作为主业务的数据存储。而 Redis 是一个典型的内存数据库,数据主要存在内存中,读写速度非常快,通常用于缓存、限流、排行榜、分布式锁等场景,强调性能和实时性,而不是数据结构的复杂性或存储的长期性。
此外,MySQL 原生支持事务和 SQL 标准,适合金融、电商等对一致性和复杂查询要求较高的系统。MongoDB 在高版本中引入了事务支持,但更侧重于高可用性和可扩展性,适合互联网类的大数据量业务。Redis 虽然也支持持久化和事务,但本质上并不适合用作主数据存储,更适合作为 MySQL 或 MongoDB 的缓存层补充。
存储结构
MongoDB 的存储结构区别于传统的关系型数据库,主要由如下三个单元组成:
- 文档(Document):MongoDB 中最基本的单元,由 BSON 键值对(key-value)组成,类似于关系型数据库中的行(Row)。
- 集合(Collection):一个集合可以包含多个文档,类似于关系型数据库中的表(Table)。
- 数据库(Database):一个数据库中可以包含多个集合,可以在 MongoDB 中创建多个数据库,类似于关系型数据库中的数据库(Database)。
文档
MongoDB 中的记录就是一个 BSON 文档,它是由键值对组成的数据结构,类似于 JSON 对象,是 MongoDB 中的基本数据单元。字段的值可能包括其他文档、数组和文档数组。
BSON [bee·sahn] 是 Binary [JSON]的简称,是 JSON 文档的二进制表示,支持将文档和数组嵌入到其他文档和数组中,还包含允许表示不属于 JSON 规范的数据类型的扩展。
集合
MongoDB 集合存在于数据库中,没有固定的结构,也就是 无模式 的,这意味着可以往集合插入不同格式和类型的数据。不过,通常情况下,插入集合中的数据都会有一定的关联性。
数据库
数据库用于存储所有集合,而集合又用于存储所有文档。一个 MongoDB 中可以创建多个数据库,每一个数据库都有自己的集合和权限。
MongoDB 预留了几个特殊的数据库。
- admin : admin 数据库主要是保存 root 用户和角色。例如,system.users 表存储用户,system.roles 表存储角色。一般不建议用户直接操作这个数据库。将一个用户添加到这个数据库,且使它拥有 admin 库上的名为 dbAdminAnyDatabase 的角色权限,这个用户自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行,比如关闭服务器。
- local : local 数据库是不会被复制到其他分片的,因此可以用来存储本地单台服务器的任意 collection。一般不建议用户直接使用 local 库存储任何数据,也不建议进行 CRUD 操作,因为数据无法被正常备份与恢复。
- config : 当 MongoDB 使用分片设置时,config 数据库可用来保存分片的相关信息。
- test : 默认创建的测试库,连接 mongod 服务时,如果不指定连接的具体数据库,默认就会连接到 test 数据库。
存储引擎
与 MySQL 一样,MongoDB 采用的也是 插件式的存储引擎架构 ,支持不同类型的存储引擎。
在存储引擎刚出来的时候,默认是使用 MMAPV1 存储引擎,MongoDB4.x 版本不再支持 MMAPv1 存储引擎。
现在主要有下面这两种存储引擎:
- WiredTiger 存储引擎:自 MongoDB 3.2 以后,默认的存储引擎为 WiredTiger 存储引擎。非常适合大多数工作负载,建议用于新部署。WiredTiger 提供文档级并发模型、检查点和数据压缩(后文会介绍到)等功能。
- In-Memory 存储引擎:In-Memory 存储引擎在 MongoDB Enterprise 中可用。它不是将文档存储在磁盘上,而是将它们保留在内存中以获得更可预测的数据延迟。
此外,MongoDB 3.0 提供了 可插拔的存储引擎 API ,允许第三方为 MongoDB 开发存储引擎,这点和 MySQL 也比较类似。
WiredTiger 使用的是 B+ 树作为其存储结构,此外,WiredTiger 还支持 LSM(Log Structured Merge) 树作为存储结构。
使用 B+ 树时,WiredTiger 以 page 为基本单位往磁盘读写数据。B+ 树的每个节点为一个 page,共有三种类型的 page:
- root page(根节点):B+ 树的根节点。
- internal page(内部节点):不实际存储数据的中间索引节点。
- leaf page(叶子节点):真正存储数据的叶子节点,包含一个页头(page header)、块头(block header)和真正的数据(key/value),其中页头定义了页的类型、页中实际载荷数据的大小、页中记录条数等信息;块头定义了此页的 checksum、块在磁盘上的寻址位置等信息。
聚合
实际项目中,我们经常需要将多个文档甚至是多个集合汇总到一起计算分析(比如求和、取最大值)并返回计算后的结果,这个过程被称为 聚合操作 。
MongoDB 提供了两种执行聚合的方法:
- 聚合管道(Aggregation Pipeline):执行聚合操作的首选方法。
- 单一目的聚合方法(Single purpose aggregation methods):也就是单一作用的聚合函数比如
count()
、distinct()
、estimatedDocumentCount()
。
事务
与关系型数据库一样,MongoDB 事务同样具有 ACID 特性。
MongoDB 单文档原生支持原子性,也具备事务的特性。当谈论 MongoDB 事务的时候,通常指的是 多文档 。MongoDB 4.0 加入了对多文档 ACID 事务的支持,但只支持复制集部署模式下的 ACID 事务,也就是说事务的作用域限制为一个副本集内。MongoDB 4.2 引入了 分布式事务 ,增加了对分片集群上多文档事务的支持,并合并了对副本集上多文档事务的现有支持。
从 MongoDB 4.2 开始,多文档事务支持副本集和分片集群,其中:主节点使用 WiredTiger 存储引擎,同时从节点使用 WiredTiger 存储引擎或 In-Memory 存储引擎。在 MongoDB 4.0 中,只有使用 WiredTiger 存储引擎的副本集支持事务。
在 MongoDB 4.2 及更早版本中,你无法在事务中创建集合。从 MongoDB 4.4 开始,您可以在事务中创建集合和索引。
数据压缩
Amazon Document
索引
MongoDB的索引有什么用
索引的目的主要是用来提高查询效率,如果没有索引的话,MongoDB 必须执行 集合扫描 ,即扫描集合中的每个文档,以选择与查询语句匹配的文档。如果查询存在合适的索引,MongoDB 可以使用该索引来限制它必须检查的文档数量。并且,MongoDB 可以使用索引中的排序返回排序后的结果。
虽然索引可以显著缩短查询时间,但是使用索引、维护索引是有代价的。在执行写入操作时,除了要更新文档之外,还必须更新索引,这必然会影响写入的性能。因此,当有大量写操作而读操作少时,或者不考虑读操作的性能时,都不推荐建立索引。
MongoDB支持哪些索引
MongoDB 支持多种类型的索引,包括单字段索引、复合索引、多键索引、哈希索引、文本索引、 地理位置索引等,每种类型的索引有不同的使用场合。
- 单字段索引: 建立在单个字段上的索引,索引创建的排序顺序无所谓,MongoDB 可以头/尾开始遍历。
- 复合索引: 建立在多个字段上的索引,也可以称之为组合索引、联合索引。
- 多键索引:MongoDB 的一个字段可能是数组,在对这种字段创建索引时,就是多键索引。MongoDB 会为数组的每个值创建索引。就是说你可以按照数组里面的值做条件来查询,这个时候依然会走索引。
- 哈希索引:按数据的哈希值索引,用在哈希分片集群上。
- 文本索引: 支持对字符串内容的文本搜索查询。文本索引可以包含任何值为字符串或字符串元素数组的字段。一个集合只能有一个文本搜索索引,但该索引可以覆盖多个字段。MongoDB 虽然支持全文索引,但是性能低下,暂时不建议使用。
- 地理位置索引: 基于经纬度的索引,适合 2D 和 3D 的位置查询。
- 唯一索引:确保索引字段不会存储重复值。如果集合已经存在了违反索引的唯一约束的文档,则后台创建唯一索引会失败。
- TTL 索引:TTL 索引提供了一个过期机制,允许为每一个文档设置一个过期时间,当一个文档达到预设的过期时间之后就会被删除。
其他
高可用
复制集群
MongoDB 的复制集群又称为副本集群,是一组维护相同数据集合的 mongod 进程。
客户端连接到整个 Mongodb 复制集群,主节点机负责整个复制集群的写,从节点可以进行读操作,但默认还是主节点负责整个复制集群的读。主节点发生故障时,自动从从节点中选举出一个新的主节点,确保集群的正常使用,这对于客户端来说是无感知的。
通常来说,一个复制集群包含 1 个主节点(Primary),多个从节点(Secondary)以及零个或 1 个仲裁节点(Arbiter)。
- 主节点:整个集群的写操作入口,接收所有的写操作,并将集合所有的变化记录到操作日志中,即 oplog。主节点挂掉之后会自动选出新的主节点。
- 从节点:从主节点同步数据,在主节点挂掉之后选举新节点。不过,从节点可以配置成 0 优先级,阻止它在选举中成为主节点。
- 仲裁节点:这个是为了节约资源或者多机房容灾用,只负责主节点选举时投票不存数据,保证能有节点获得多数赞成票。
为什么要用复制集群?
- 实现 failover:提供自动故障恢复的功能,主节点发生故障时,自动从从节点中选举出一个新的主节点,确保集群的正常使用,这对于客户端来说是无感知的。
- 实现读写分离:我们可以设置从节点上可以读取数据,主节点负责写入数据,这样的话就实现了读写分离,减轻了主节点读写压力过大的问题。MongoDB 4.0 之前版本如果主库压力不大,不建议读写分离,因为写会阻塞读,除非业务对响应时间不是非常关注以及读取历史数据接受一定时间延迟。
分片集群
分片集群是 MongoDB 的分布式版本,相较副本集,分片集群数据被均衡的分布在不同分片中, 不仅大幅提升了整个集群的数据容量上限,也将读写的压力分散到不同分片,以解决副本集性能瓶颈的难题。
MongoDB 的分片集群由如下三个部分组成:
- Config Servers:配置服务器,本质上是一个 MongoDB 的副本集,负责存储集群的各种元数据和配置,如分片地址、Chunks 等
- Mongos:路由服务,不存具体数据,从 Config 获取集群配置讲请求转发到特定的分片,并且整合分片结果返回给客户端。
- Shard:每个分片是整体数据的一部分子集,从 MongoDB3.6 版本开始,每个 Shard 必须部署为副本集(replica set)架构
为什么要用分片集群
随着系统数据量以及吞吐量的增长,常见的解决办法有两种:垂直扩展和水平扩展。
垂直扩展通过增加单个服务器的能力来实现,比如磁盘空间、内存容量、CPU 数量等;水平扩展则通过将数据存储到多个服务器上来实现,根据需要添加额外的服务器以增加容量。
类似于 Redis Cluster,MongoDB 也可以通过分片实现 水平扩展 。水平扩展这种方式更灵活,可以满足更大数据量的存储需求,支持更高吞吐量。并且,水平扩展所需的整体成本更低,仅仅需要相对较低配置的单机服务器即可,代价是增加了部署的基础设施和维护的复杂性。
也就是说当你遇到如下问题时,可以使用分片集群解决:
- 存储容量受单机限制,即磁盘资源遭遇瓶颈。
- 读写能力受单机限制,可能是 CPU、内存或者网卡等资源遭遇瓶颈,导致读写能力无法扩展
分片键
什么是分片键
分片键(Shard Key) 是数据分区的前提, 从而实现数据分发到不同服务器上,减轻服务器的负担。也就是说,分片键决定了集合内的文档如何在集群的多个分片间的分布状况。
分片键就是文档里面的一个字段,但是这个字段不是普通的字段,有一定的要求:
- 它必须在所有文档中都出现。
- 它必须是集合的一个索引,可以是单索引或复合索引的前缀索引,不能是多索引、文本索引或地理空间位置索引。
- MongoDB 4.2 之前的版本,文档的分片键字段值不可变。MongoDB 4.2 版本开始,除非分片键字段是不可变的
_id
字段,否则您可以更新文档的分片键值。MongoDB 5.0 版本开始,实现了实时重新分片(live resharding),可以实现分片键的完全重新选择。 - 它的大小不能超过 512 字节。
如何选择分片键
选择合适的片键对 sharding 效率影响很大,主要基于如下四个因素
- 取值基数 取值基数建议尽可能大,如果用小基数的片键,因为备选值有限,那么块的总数量就有限,随着数据增多,块的大小会越来越大,导致水平扩展时移动块会非常困难。 例如:选择年龄做一个基数,范围最多只有 100 个,随着数据量增多,同一个值分布过多时,导致 chunck 的增长超出 chuncksize 的范围,引起 jumbo chunk,从而无法迁移,导致数据分布不均匀,性能瓶颈。
- 取值分布 取值分布建议尽量均匀,分布不均匀的片键会造成某些块的数据量非常大,同样有上面数据分布不均匀,性能瓶颈的问题。
- 查询带分片 查询时建议带上分片,使用分片键进行条件查询时,mongos 可以直接定位到具体分片,否则 mongos 需要将查询分发到所有分片,再等待响应返回。
- 避免单调递增或递减 单调递增的 sharding key,数据文件挪动小,但写入会集中,导致最后一篇的数据量持续增大,不断发生迁移,递减同理。
综上,在选择片键时要考虑以上 4 个条件,尽可能满足更多的条件,才能降低 MoveChunks 对性能的影响,从而获得最优的性能体验。