一,MongoDB是什么

MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展,高性能和高可用性的数据存储解决方案。

它是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,也支持对数据建立索引。

一句话概况来说:MongoDB是NoSQL数据库中的文档数据库,使用BSON做为文档数据存储和网络传输格式。

官方文档:https://www.mongodb.com/docs/manual/

1,MongoDb核心概念

document

mongo中的记录是一个文档document,文档就是键值对的一个有序集合,类似于java中的json对象,对应关系数据库中的一行。其结构如下:
在这里插入图片描述

​主键:mongo自动将“_id”字段设置为主键,并自动索引,它的值在集合中必须是唯一的,是不可变的。默认情况下," _id “字段的类型为ObjectId,是MongoDB的BSON类型之一。如果需要,用户还可以将” _id “覆盖为ObjectID以外的其他值。通常,您不必担心生成ObjectID。如果未为文档分配” _id "值,MongoDB将自动生成一个。如:
{ " _id" : ObjectId("63f9d0fedf510f98a2c4905e"), "name" : "ChenYuRong", "age" : 15, "addr" : "sz" }

数据类型:BSON格式中,key必须是String类型,value可以是null,布尔,数字,数组,或者嵌套文档,还支持Date类型。注:数字默认是64位的浮点数,如表示整形值,可以使用NumberInt或者NumberLong表示,如db.user.insert({x:NumberInt(10)})

collection

​多个文档组成集合,类似关系数据库中的表。集合是无模式的,即集合中的文档可以是各式各样,不要求field字段名称和个数必须一致。可以使用“.”按照命名空间将集合划分为子集合。如blog集合的两个子集合blog.user,blog.article,这样划分只是让组织结构更好一些,如db.blogdb.blog.userdb.blog.article三个集合之间没有任何关系。

database

​多个集合组成数据库。一个mongod实例可以承载多个数据库,它们之间是相互独立的,每个数据库都有独立的权限控制。在磁盘上,不同的数据库存放在不同的文件中。

系统数据库:

  • Admin 数据库:一个权限数据库,如果创建用户的时候将该用户添加到admin 数据库中,那么该用户就自动继承了所有数据库的权限。
  • Local 数据库:这个数据库永远不会被复制,可以用来存储本地单台服务器的任意集合。
  • Config 数据库:当MongoDB 使用分片模式时,config数据库在内部使用,用于保存分片的信息。

2,MongoDB和sql方式对比

SQL术语/概念 MongoDB术语/概念 解释/说明
database database 数据库
table collection 数据库表/集合
row document 数据记录行/文档
column field 数据字段/域
index index 索引
table joins 表连接,MongoDB不支持
primary key primary key 主键,MongoDB自动将_id字段设置为主键

在这里插入图片描述

二,MongoDB应用场景

1,适用场景

  • 数据库具体数据格式不明确或者数据格式经常变化

  • 数据量巨大,数据价值低(如评论内容,粉丝信息),数据结构特别(如地理坐标)

  • 高性能读写需求

  • 对事务性要求不高

几个实际应用案例

  • 社交场景,使用 MongoDB 存储存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附近的人、地点等功能。
  • 游戏场景,使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、高效率存储和访问。
  • 物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以 MongoDB 内嵌数组的形式来存储,一次查询就能将订单所有的变更读取出来。
  • 物联网场景,使用 MongoDB 存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析。
  • 视频直播,使用 MongoDB 存储用户信息、点赞互动信息等。

2,确定选型

  • 是新应用,需求会变,数据模型无法确定,想快速迭代开发
  • 数据量是有亿万级或者需要不断扩容
  • 应用不需要事务及复杂 join 支持
  • 需要2000-3000以上的读写每秒
  • 系统需要99.999%高可用
  • 系统需要大量的地理位置查询
  • 系统需要提供最小的latency

三,MongoDB三种集群架构

1,主从复制

虽然 MongoDB 官方建议用副本集替代主从复制,但是本节还是从主从复制入手,让大家了解 MongoDB 的复制机制。

主从复制是 MongoDB 中最简单的数据库同步备份的集群技术,其基本的设置方式是建立一个主节点(Primary)和一个或多个从节点(Secondary),这种方式比单节点的可用性好很多,可用于备份、故障恢复、读扩展等。集群中的主从节点均运行 MongoDB 实例,完成数据的存储、查询与修改操作。

主从复制模式的集群中只能有一个主节点,主节点提供所有的增、删、查、改服务,从节点不提供任何服务,但是可以通过设置让从节点提供查询服务,以减少主节点的压力。

另外,每个从节点要知道主节点的地址,主节点记录在其上的所有操作在oplog日志文件中,从节点定期轮询主节点获取这些操作,然后对自己的数据副本执行这些操作,从而保证从节点的数据与主节点一致。

在主从复制的集群中,当主节点出现故障时,只能人工介入,指定新的主节点,从节点不会自动升级为主节点。同时,在这段时间内,该集群架构只能处于只读状态。

2,副本集

副本集是自带故障转移功能的主从复制。

此集群拥有一个主节点和多个从节点,这一点与主从复制模式类似,且主从节点所负责的工作也类似,但是副本集与主从复制的区别在于:当集群中主节点发生故障时,副本集可以自动投票,选举出新的主节点,并引导其余的从节点连接新的主节点,而且这个过程对应用是透明的。

副本集中的各节点会通过心跳信息来检测各自的健康状况,当主节点出现故障时,多个从节点会触发一次新的选举操作,选举其中一个从节点作为新的主节点。为了保证选举票数不同,副本集的节点数一般保持为奇数。

3,分片+副本集

在这里插入图片描述

副本集可以解决主节点发生故障导致数据丢失或不可用的问题,但遇到需要存储海量数据的情况时,副本集机制就束手无策了。副本集中的一台机器可能不足以存储海量数据,或者说集群不足以提供可接受的读写吞吐量。这就需要用到 MongoDB 的分片(Sharding)技术,这也是 MongoDB 的另外一种集群部署模式。

分片是指将数据拆分并分散存放在不同机器上的过程。有时也用分区来表示这个概念。将数据分散到不同的机器上,不需要功能强大的大型计算机就可以存储更多的数据,处理更大的负载。

MongoDB 支持自动分片,可以使数据库架构对应用程序不可见,简化系统管理。对应用程序而言,就如同始终在使用一个单机的 MongoDB 服务器一样。
MongoDB 的分片机制允许创建一个包含许多台机器的集群,将数据子集分散在集群中,每个分片维护着一个数据集合的子集。与副本集相比,使用集群架构可以使应用程序具有更强大的数据处理能力。

构建一个 MongoDB 的分片集群,需要三个重要的组件,分别是分片服务器(Shard Server)、配置服务器(Config Server)和路由服务器(Route Server)。

1,Shard Server 或者叫mongod
一个 mongodb数据库实例,启动一个mongod进程,用于存储实际的数据块。整个数据库集合分成多个块存储在不同的 Shard Server 中。
在实际生产中,一个 Shard Server 通常由几台机器组成一个副本集来承担,防止因主节点单点故障导致整个系统崩溃。

2,Config Server
这是一个特殊的 mongod 进程,保存集群和分片的元数据,在集群启动最开始时建立,保存各个分片包含数据的信息。

3,Route Server 或者叫mongos 就是 “MongoDB Shard” 的简写
这是独立的一个 mongos 进程,Route Server 在集群中可作为路由使用,客户端由此接入,让整个集群看起来像是一个单一的数据库,提供客户端应用程序和分片集群之间的接口。
Route Server 本身不保存数据,启动时从 Config Server 加载集群信息到缓存中,并将客户端的请求路由给每个 Shard Server,在各 Shard Server 返回结果后进行聚合并返回客户端。

4,生产环境部署案例

以上介绍了 MongoDB 的三种集群模式,副本集已经替代了主从复制,通过备份保证集群的可靠性,分片机制为集群提供了可扩展性,以满足海量数据的存储和分析的需求。

在实际生产环境中,副本集和分片是结合起来使用的,可满足实际应用场景中高可用性和高可扩展性的需求。

如下是一个“三分片+三副本”的高可用集群的部署信息:

进程 节点1 节点2 节点 启动端口
shard0 172.18.17.73 172.18.17.74 172.18.17.75 27017
shard1 172.18.17.73 172.18.17.74 172.18.17.75 27018
shard2 172.18.17.73 172.18.17.74 172.18.17.75 27019
config server 172.18.17.70 172.18.17.71 172.18.17.72 17017
mongos 172.18.17.70 172.18.17.71 172.18.17.72 20017

一台节点上27017,27018,27019三个不同的端口表示启动了三个mongod实例,代表三个分片副本。每个分片由部署在三个节点上的副本共同构成副本集。

默认端口
27017:mongod默认端口
17017 :config server进程
20017 :mongos进程端口,即客户端连接用的端口

四,MongoDB认证登录

1,客户端连接

#mongodb认证登录
mongo 172.18.17.70:20017
use admin
db.auth("username","*************此处键入密码***************")

#一键认证登录
mongo 172.18.17.70:20017/admin -u username -p ***********此处键入密码**********

2,认证数据库

创建用户时,是以数据库为单位来建立的,所以需要先切换到指定库。该数据库是用户的认证的数据库,简称验证库。登录时,需要在它的验证库下。删除用户时,也要切换的用户的验证库下。
用户可以跨不同数据库拥有权限; 即用户的权限不限于认证数据库。 通过分配给其他数据库中的用户角色,在一个数据库中创建的用户可以拥有对其他数据库的操作权限。

用户名和验证库作为该用户的唯一标识符。 也就是说,如果两个用户具有相同的名称,但是在不同的数据库中创建,则它们是两个不同的用户。 如果您打算拥有具有多个数据库权限的单个用户,请在适用的数据库中创建具有角色的单个用户,而不是在不同数据库中多次创建用户。

五,MongoDB基本shell命令

1,基本命令

db --查看当前数据库
show dbs/database --展示数据库列表(没有数据的数据库不展示)
use myDb --创建数据库或者切换数据库
db.dropDatabase() --删除当前数据库,需要在use选择数据库后执行
show collections --展示集合
db.user.drop() --删除user集合,user为自己创建的集合
db.user.count() --统计集合的大小

help --查看使用说明
db.help() --当前数据库支持的方法,查看增删改查方法
db.user.help() --查看当前集合支持的方法
db.user.function --查看方法声明

2,新增操作

#新增数据,往user集合新增一条文档,user不存在会自动创建
--对象的键一般可不加引号(方便看),但是查看集合数据时系统会自动加;mongodb会给每条数据增加一个全球唯一的_id键
db.user.insert({     
...     name: "wang",
...     age: 25,
...     addr: "sz"
... })

#批量插入数据,方法传入一个json数组即可
db.user.insert([
... {
...     "name": "shengr",
...     "age": 15,
...     "addr": "sz"
... },
... {
...     "name": "xiaoming",
...     "age": 65,
...     "addr": "sz"
... },
... {
...     "name": "wang",
...     "age": 27,
...     "addr": "gd"
... }])

3,删除操作

#语法
db.user.remove( {条件} [,是否删除一条])
--是否删除一条,默认值false,匹配到的数据都删除;true表示只删除第一条数据

#删除所有叫做wang的
db.user.remove({"name":"wang"})
#清空所有数据
db.user.remove({})

4,修改操作

# 语法
db.user.update( {条件},{修改器:{k:v}} [upsert没有匹配的行是否新增,multi匹配多个是否都修改])
--修改器:inc增加列值  set修改列值 unset删除列 rename重命名列
--没有匹配是否新增,默认false
--匹配多个是否都修改,默认false

# 修改wang的名字
db.user.update({name:'wang'},{$set:{name:'laowang'}})

# wang的年龄加5岁,减5岁,值改为-5即可
db.user.update({name:'wang'},{$inc:{age:5}})

# score列追加一个值,该列不存在会新增
db.user.update({name:'laowang'},{$push:{score:16}})

# 移除core列的某些值
db.user.update({name:'laowang'},{$pullAll:{score:[10,20,30]}})

# 循环插入,并自动过滤重复数据,如果没有$each,相当于插入一个数组类型子元素
db.user.update({name:'laowang'},{$addToSet:{score:{$each:[10,2,3,4,5,6]}}})

# 增加日期列currentDate,可用$type指定日期类型为date或timestamp
db.user.update({name:'laowang'},{$currentDate:{currentDate:true}})
db.user.update({name:'wang'},{$currentDate:{currentDate:{$type:"timestamp"}}})

# setOnInsert示例,生效的前提是过滤器没有匹配到行
db.user.update({name:'shengr'},{$setOnInsert:{weight:80}},true)
db.user.update({name:'shengrr'},{$set:{age:888},$setOnInsert:{weight:80}},true)

常用mongo修改器

--普通类型修改器:
$inc --对数字类型的值进行增减
$set --修改列值,并且不存在的列默认添加
$setOnInsert --如果更新导致插入文档(新增文档),则设置字段的值,对修改现有文档的更新操作无效。upsert选项必须设置为true,可同时与set搭配使用。
$unset  --删除列, {$unset:{'name':0/1/-1}}  值是0或者1或者-1 都会被删除。
$currentDate --设置值为当前日期

--数组修改器:
$push --如果数组列不存在则添加,如果存在则给数组的末尾追加一个值。
$pop  --值为1,末尾移除一位。值为-1,开头移除一位。移除了了最后一个元素为空数组。
$pull  --移除数组中指定的数值
$pullAll  --移除数组中指定的某几个值。
$addToSet  --如果数组中已经存在要添加的值则无法添加成功。

5,查询操作

#查询语法
db.collection.find(query, [projection]) 
--query是个json,指定查询条件, projection投影操作也是json,key为需要显示的列名,value 为 1(显示) 或 0(不显示)。
#query查询条件写法
db.user.find({
            键:{运算符:值}
            })

#示例
db.user.find() --不加条件,则查找全部
db.user.find().limit(1)  --limit操作,限制查询条数
db.user.find( {age:15} ) --查询年龄=15的
db.user.find( {age:15,addr:"sz"} ) --查询年龄=15,并且是sz的
db.user.find( {age:{$gt:15}} ) --查询年龄>15的


--特殊查询:查找一个并修改
--该方法会修改匹配到的第一个文档,如果没有匹配到文档,默认不会新增,除非设置upsert选项为true,则没有匹配到会新增一个文档。
--重点是:该方法默认返回的是修改前的文档,要想返回新文档,可设置returnNewDocument为true。

db.user.findOneAndUpdate({name:"han"},{$inc:{weight:1}})
db.user.findOneAndUpdate({name:"han"},{$inc:{weight:1}},{returnNewDocument:true})
db.user.findOneAndUpdate({name:"hanhan"},{$inc:{weight:1}},{returnNewDocument:true,upsert:true})

运算符 作用
$gt 大于
$gte 大于等于
$lt 小于
$lte 小于等于
$ne 不等于
$in in
$nin not in

6,其他操作

#and操作
即用逗号分隔的k:v对
#or操作
db.user.find(
   {
      $or: [
	     {key1: value1}, {key2:value2}
      ]
   }
).pretty()

#排序操作
db.user.find().sort({KEY:1})  --KEY指定字段,1 为升序排列,而-1是用于降序排列

六,MongoDB索引介绍

默认情况下,集合中的_id字段就是索引,我们可以通过getIndexes()方法来查看一个集合中的索引:db.user.getIndexes() 。但是基于查询条件中的普通列做查询时,如db.user.flind({age:13}),查找年龄为13的所有用户,默认是全表扫描,很低效,此时可以给该字段创建索引。

单字段索引

db.user.createIndex({age:1},{name:"myfirstIndex"})

1表示升序,-1表示降序。当我们给age字段建立索引之后,再根据age字段去查询,速度就非常快了。
注:老版本可能存在ensureIndex()方法,使用方式和上面是一样的。

复合索引

基于两个字段以上的索引。复合索引中列出的字段的顺序很重要。索引将包含对文档的引用,如下,这些文档首先按a字段的值排序,然后在该字段的每个值内,再按b字段的值排序。复合索引除了支持在所有索引字段上都匹配的查询之外,还可以支持在索引字段的前缀上匹配的查询,如匹配a的查询。

db.products.createIndex( { "a": 1, "b": 1 } )

此外,索引创建还有很多其他可选参数,如下

db.user.createIndex({age:1},{name:"myfirstIndex",dropDups:true,background:true,unique:true,sparse:true,v:1,weights:99999})

#参数说明
1. name表示索引的名称
2.dropDups表示创建唯一性索引时如果出现重复,则将重复的删除,只保留第一个
3.background是否在后台创建索引,在后台创建索引不影响数据库当前的操作,默认为false
4.unique是否创建唯一索引,默认false
5.sparse对文档中不存在的字段是否不起用索引,默认false
6.v表示索引的版本号,默认为2
7.weights表示索引的权重

索引其他命令

--查看索引大小
db.user.totalIndexSize()
--按名称删除索引
db.user.dropIndex("myfirstIndex")
--删除所有索引(不包括'_id')
db.user.dropIndexes()

索引是个好东西,可以有效的提高查询速度,但是索引过多会降低插入、更新和删除的速度,因为这些操作不仅要更新文档,还要更新索引,MongoDB 限制每个集合上最多有64个索引,我们在创建索引时要仔细斟酌索引的字段。另外,在索引的构建期间,应用程序可能会遇到性能下降,包括对集群的读写访问受限。

七,MongoDB的Java 客户端操作

引入pom依赖

<dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongo-java-driver</artifactId>
    <version>3.11.2</version>
</dependency>

CRUD操作

package mongoClient;
import com.mongodb.*;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.FindOneAndUpdateOptions;
import com.mongodb.client.model.ReturnDocument;
import org.bson.Document;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;


/**
 * Created by shengR on 2023/3/2
 * @desc: todo
 */
public class ClientTest {
    public static void main(String[] args) {
        //创建服务地址
        ServerAddress serverAddress = new ServerAddress("$ip", 20017);
        //创建凭证,三个参数:用户名,验证库,密码
        MongoCredential mc = MongoCredential.createScramSha1Credential("$username","$db","$password".toCharArray());
        //创建客户端连接选项
        MongoClientOptions options = MongoClientOptions.builder()
                //设置连接超时时间为10s
                .connectTimeout(1000*10)
                //设置最长等待时间为10s
                .maxWaitTime(1000*10)
                .build();
        //创建客户端,其中可选参数options,用于设置连接所需的配置信息
        MongoClient client = new MongoClient(serverAddress,mc,options);

        //获取数据库,没有则创建
        MongoDatabase db = client.getDatabase("test");
        //获取集合,没有则创建
        MongoCollection<Document> teacher = db.getCollection("teacher");

        //一,查询数据
        //全量查询
        FindIterable<Document> result1 = teacher.find();
        //条件查询1 :过滤name为shengr的文档
        FindIterable<Document> result2 = teacher.find(Filters.eq("name", "shengr"));
        //条件查询2 :过滤课程中同时包含Math和China的文档
        BasicDBObject query = new BasicDBObject("courses", new BasicDBObject("$elemMatch", new BasicDBObject("$eq", "Math").append("$eq","China")));
        FindIterable<Document> result3 = teacher.find(query);
        //遍历结果
        MongoCursor<Document> iterator = result2.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

        //二,插入数据(基本数据以及数组示范)
        Document d1=new Document();
        List<String> coursesList=new ArrayList<>();
        coursesList.add("Math");
        coursesList.add("English");
        coursesList.add("China");
        d1.append("name","shengr").append("age","26").append("addr","sz").append("courses",coursesList);
        teacher.insertOne(d1);

        //三,更新数据,updateOne为修改查到的第一条,updateMany修改查到的全部数据
        teacher.updateOne(Filters.eq("name", "shengr"), new Document("$set", new Document("name", "asseghuh")));
        teacher.updateMany(Filters.eq("name", "shengr"), new Document("$set", new Document("name", "asseghuh")));

        //findOneAndUpdate用法,该方法默认返回修改前的数据,且没有匹配的不会新增,可以设置如下选项更改默认行为
        FindOneAndUpdateOptions findOneAndUpdateOptions = new FindOneAndUpdateOptions()
                .returnDocument(ReturnDocument.AFTER)
                .upsert(true);

        Document d=new Document()
                .append("$set", new Document("addr", "shanghai"))
                .append("$set",new Document("age","99").append("updateTime",new Date()).append("flag","1"));

        Document result = teacher.findOneAndUpdate(Filters.eq("name", "ggg"), d, findOneAndUpdateOptions);

        //四,删除操作,one和many的区别同上
        teacher.deleteOne(Filters.eq("name", "aaaaa"));
        teacher.deleteMany(Filters.eq("name", "bbbbb"));

    }
}

find()常用的两个重载方法说明

//无参,表示全量查询
FindIterable<TDocument> find();
//传入一个Bson类型的查询条件
FindIterable<TDocument> find(Bson query);

BasicDBObject对象说明

一个Bson对象的基本实现,可用于构造mongo查询条件,更新mongo等。其继承关系如下:
public class BasicDBObject extends BasicBSONObject implements DBObject, Bson

1,构造方法

public BasicDBObject()
public BasicDBObject(final String key, final Object value) 
public BasicDBObject(final Map map)

2,使用案例

//基本用法
BasicDBObject query = new BasicDBObject();
query.put( key, value );

//设置单个字段条件
BasicDBObject query = new BasicDBObject("name", "shengr");

//in 的用法
BasicDBObject query = new BasicDBObject("age", new BasicDBObject("$in", ageList));

//数组字段元素匹配
BasicDBObject query = new BasicDBObject();
query.put("courses", new BasicDBObject("$elemMatch", new BasicDBObject("$eq", "Math").append("$eq", "China")));

//exists 用法,  1代表存在该字段,值为null也示为存在
BasicDBObject query = new BasicDBObject("courses", new BasicDBObject("$exists", 1));

八,小结

继续深入学习看这里:
MongoDb官方文档:点这里跳转
MongoDb中文社区:点这里跳转

在这里插入图片描述

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐