目录结构
一,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.blog
、db.blog.user
、db.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));