MongoDB是一个基于分布式文件存储的非关系型数据库,由C++语言编写,存储bson格式数据,方便WEB应用扩展开发。
bson是一种类似于json的二进制形式的存储格式,简称 binary json,支持内嵌的文档对象和数组对象,但是bson相对于json增加了Date和BinData类型。
官网:https://www.mongodb.com/
核心概念:
RDBMS | MongoDB |
---|---|
数据库 | 库 |
表 | 集合 |
行 | 文档 |
列 | 字段 |
docker pull mongo:5.0.5 //拉取mongodb镜像
docker run -d -p 27017:27017 --name mongo --privileged=true
-v /root/mongodb/data:/data/db //挂载数据卷
-e MONGO_INITDB_ROOT_USERNAME=用户名
-e MONGO_INITDB_ROOT_PASSWORD=密码
mongo:5.0.5 //运行镜像
docker exec -it mongodb bash //进入容器
mongo //进入客户端
或者
docker exec -it mongodb mongo
use admin
db.auth('用户名', '密码') //登录认证
root用户需要切换到admin库进行认证,认证后才能进行操作:
安装可视化工具Navicat for MongoDB,测试加密:
显示隐藏的库:
参考文档:https://www.mongodb.com/docs/manual/
查询所有库:
show databases
#或者
show dbs
显示当前库:
db
创建库:
use 库名
注意:当数据库中没有数据时默认不显示
插入一条文档后显示
删除库:
db.dropDatabase()
查看库中所有集合:
show collections;
#或者
show tables;
创建集合:
db.createCollection('集合名称', [options])
options:
字段 | 解释 |
---|---|
capped | 如果为true,创建固定大小集合,必须指定size值 (当集合达到最大值时会覆盖最早的文档) |
size | 指定集合存储的最大值字节数 |
max | 指定集合能够存放文档的最大数量 |
删除集合:
db.集合名称.drop()
插入文档:
db.集合名称.insert({})
db.集合名称.insert([{},{}]) #插入多条文档
或者
db.集合名称.insertMany([{},{}])
# js脚本方式
for(let i = 0; i < 10; i++)
{
db.集合名称.insert({});
}
注意:每一个文档都有一个_id
作为唯一标识,如果没有指定的话会自动生成
删除文档:
db.集合名称.remove({},[option])
db.集合名称.remove({}) #填空 删除全部文档
option
justOne: 如果为true或1,则只删除一个文档,默认为false删除所有匹配的文档
注意:如果通过_id
匹配删除文档,对于自动生成的id需要加上ObjectId()函数才能删除
更新文档:
db.集合名称.update({},{},[option])
option
upsert: 默认为false 如果设置为true表示不存在更新的文档时插入新的文档
multi: 默认为false 只更新一条文档 如果设置为true表示更新所有匹配的文档
注意:更新操作相当于先删除匹配的文档再插入新的文档,如果要保存原来的字段需要加上$set:
更新所有匹配的文档:
文档查询:
db.集合名称.find({}, {projection})
projection: 指定返回的字段 1 返回 0 不返回
db.集合名称.find({}, {projection}).pretty() #以格式化的方式显示所有文档
运算符 | 格式 |
---|---|
等于 | {key:value} |
小于 | {key:{$lt:value}} |
小于等于 | {key:{$lte:value}} |
大于 | {key:{$gt:value}} |
大于等于 | {key:{$gte:value}} |
不等于 | {key:{$ne:value}} |
查询年龄等于19:
查询年龄小于或大于19:
AND与连接:
db.集合名称.find({ key:value, key:value})
OR或连接:
db.集合名称.find(
{
$or:[
{ key:value}, { key:value}
]
}
)
模糊查询(正则表达式):
db.集合名称.find({key:/value/})
按数组长度查询:
db.集合名称.find({key:{$size:value}})
排序:
db.集合名称.find().sort({key:1/-1}) # 1 升序 -1 降序
分页:
db.集合名称.find().skip(start).limit(rows) #start 起始文档 rows 查询几条 类似 limit start rows
注意:find()不是查询全部文档,默认只显示20条,输入it
查看更多
查询文档总条数:
db.集合名称.find().count()
去重:
db.集合名称.distinct('字段')
设置projection
不显示age字段(1 返回 0 不返回):
按字段类型查询:
db.集合名称.find({key:{$type:value}})
聚合查询:
db.集合名称.aggregate(
[
{
$group:{_id:"$分组字段名称", '聚合名称':{$聚合操作}}
}
]
)
聚合操作 | 描述 |
---|---|
$sum | 计算总和 |
$avg | 计算平均值 |
$min | 计算最小值 |
$max | 计算最大值 |
$push | 将值加入一个数组(可重复) |
$addToSet | 将值加入一个数组(不可重复) |
$first | 获取第一个文档 |
$last | 获取最后一个文档 |
按照性别分组查询最大年龄、最小年龄和平均年龄:
按照性别分类将爱好加入一个数组返回、获取第一个和最后一个爱好
索引用来提高查询的效率,和关系型数据库类似,遵循最左前缀法则
创建索引:
db.集合名称.createIndex(keys, option) #keys 指定建立索引的字段 1 字段升序 -1 字段降序
option
background: 由于建立索引会阻塞其他数据库操作,background指定以后台方式创建索,默认为false
unique: 默认为false,为true时建立唯一索引
name: 指定索引名称,如果没有指定会自动生成
expireAfterSeconds: 对于索引的数据设置过期时间,过期后数据自动删除
weights: 指定索引的权值,在1~99999之间,表示索引之间的优先
查看索引的大小:
db.集合名称.totalIndexSize()
查看索引:
db.集合名称.getIndexes()
删除索引:
db.集合名称.dropIndexes("索引名称") #删除指定索引
db.集合名称.dropIndexes() #删除所有索引
导入依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-mongodbartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
application.yaml:
spring:
data:
mongodb:
# uri: mongodb://ip地址:27017/库名 #未设置账号密码时
host: ip地址
port: 27017
database: 库名
username: 用户名
password: 密码
注意:uri和host、port、username、password、replicaSetName配置冲突,只能使用其中的一种
集合操作:
@SpringBootTest
class MongoDbApplicationTests {
@Autowired
private MongoTemplate mongoTemplate;
@Test
void createCollection() {
boolean exists = mongoTemplate.collectionExists("b");
if(!exists) {
mongoTemplate.createCollection("b");
}
}
@Test
void dropCollection() {
mongoTemplate.dropCollection("b");
}
}
文档操作:
@Document("users") //User类的对象相当于一条记录
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id //映射文档中的_id
private Integer id;
@Field //映射文档中的字段
private String name;
@Field
private Integer age;
}
相关注解:
注解 | 解释 |
---|---|
@Document | 标注在类上,表示该类的对象相当于一条文档,value属性指明集合的名称 |
@Id | 标注在成员变量上,表示文档的_id值 |
@Field | 标注在成员变量上,表示文档的字段名称 |
@Transient | 标注在成员变量上,不参与文档的序列化 |
@SpringBootTest
class MongoDbApplicationTests {
@Autowired
private MongoTemplate mongoTemplate;
@Test
void insertDocument() {
User user1 = new User(1, "张三", 18);
User user2 = new User(1, "李四", 19);
//mongoTemplate.insert(user1); //当_id存在时报错 DuplicateKeyException
//mongoTemplate.save(user2); //当_id存在时则会更新数据
ArrayList<User> users = new ArrayList<>();
users.add(new User(2, "阿四", 25));
users.add(new User(3, "小陈", 35));
mongoTemplate.insert(users, User.class); //insert可以插入多条文档
//mongoTemplate.insert(users, "users");
}
@Test
void queryDocument() {
//根据id查询
System.out.println("_id=1的文档:" + mongoTemplate.findById(1, User.class));
System.out.println("-----------------------------------");
//查询所有
System.out.println("所有文档:" + mongoTemplate.findAll(User.class));
System.out.println("所有文档:" + mongoTemplate.find(new Query(), User.class)); //query 查询对象为null
System.out.println("-----------------------------------");
//条件查询
System.out.println("名字是张三的文档:" + mongoTemplate.find(Query.query(Criteria.where("name").is("张三")), User.class));
System.out.println("年龄小于30的文档:" + mongoTemplate.find(Query.query(Criteria.where("age").lt(30)), User.class));
System.out.println("-----------------------------------");
//and or
System.out.println("年龄为30并且id为3的文档:" + mongoTemplate.find(Query.query(Criteria.where("age").gt(30).and("id").is(3)), User.class));
Criteria criteria = new Criteria();
criteria.orOperator(Criteria.where("age").gt(30), Criteria.where("age").lt(20));
System.out.println("年龄大于30或小于20的文档:" + mongoTemplate.find(Query.query(criteria), User.class));
System.out.println("-----------------------------------");
//排序
System.out.println("按年龄降序:" + mongoTemplate.find(new Query().with(Sort.by(Sort.Order.desc("age"))), User.class));
System.out.println("-----------------------------------");
//分页
System.out.println("分页:" + mongoTemplate.find(new Query().skip(0).limit(2), User.class));
System.out.println("-----------------------------------");
//总条数
System.out.println("文档总条数:" + mongoTemplate.count(new Query(), User.class));
System.out.println("-----------------------------------");
System.out.println("对于年龄去重:" + mongoTemplate.findDistinct(new Query(), "age", User.class, Integer.class));
}
@Test
void updateDocument() {
//更新符合条件的第一条文档
mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(1)), new Update().set("age", 20), User.class);
//更新符合条件的所有文档
mongoTemplate.updateMulti(new Query(), new Update().set("age", 18),User.class);
//不符合条件插入新的文档
mongoTemplate.upsert(Query.query(Criteria.where("id").is(4)), new Update().setOnInsert("name", "小四").setOnInsert("age", 1), User.class);
}
@Test
void deleteDocument() {
//删除所有文档
mongoTemplate.remove(new Query(), User.class);
//删除年龄为18的所有文档
mongoTemplate.remove(Query.query(Criteria.where("age").is(18)), User.class);
}
}
MongoDB副本集是可以自动故障恢复的主从集群,由一个主节点和多个从节点组成,当主节点故障时,会从从节点中选举出一个新的主节点,保证系统的高可用
选举示意图:
搭建一主两从副本集群:
节点 | 端口 |
---|---|
主节点 | 27017 |
从节点1 | 27018 |
从节点1 | 27019 |
创建对应的目录存放数据、配置文件和日志信息
tree -d //创建对应的目录存放数据、配置文件和日志信息
master主节点mongo.conf:
storage:
dbPath: /data/db # 存储数据目录
journal:
enabled: true
systemLog:
destination: file
logAppend: true
path: /var/log/mongodb/mongod.log # 存储日志目录
net:
port: 27017 # 端口号
bindIp: 0.0.0.0 # 允许外部访问
processManagement:
timeZoneInfo: /usr/share/zoneinfo
security:
keyFile: /opt/keyfile # 内部认证文件路径
authorization: enabled # 开启认证
replication:
replSetName: "rs0" # 副本集名称
slave从节点配置文件类似,仅修改端口号为27018、27019即可
生成keyfile文件用于节点之间的内部认证:
openssl rand -base64 756 > keyfile
chmod 400 keyfile //提供读取权限
chown 999 keyfile //!设置文件所有者(不然后面会报错)
运行容器:
//运行主节点
docker run -d -p 27017:27017 --name mongoMaster --privileged=true
-v /root/mongoReplication/master/data:/data/db //挂载数据目录
-v /root/mongoReplication/keyfile:/opt/keyfile //挂载keyfile文件
-v /root/mongoReplication/master/configdb/:/data/configdb //挂载配置目录
-v /root/mongoReplication/master/logs/:/var/log/mongodb/ //挂载日志目录
mongo:5.0.5 mongod --config /data/configdb/mongod.conf //通过配置文件运行
//运行从节点
docker run -d -p 27018:27018 --name mongoSlave1 --privileged=true
-v /root/mongoReplication/slave1/data:/data/db
-v /root/mongoReplication/keyfile:/opt/keyfile
-v /root/mongoReplication/slave1/configdb/:/data/configdb
-v /root/mongoReplication/slave1/logs/:/var/log/mongodb/
mongo:5.0.5 mongod --config /data/configdb/mongod.conf
docker run -d -p 27019:27019 --name mongoSlave2 --privileged=true
-v /root/mongoReplication/slave2/data:/data/db
-v /root/mongoReplication/keyfile:/opt/keyfile
-v /root/mongoReplication/slave2/configdb/:/data/configdb
-v /root/mongoReplication/slave2/logs/:/var/log/mongodb/
mongo:5.0.5 mongod --config /data/configdb/mongod.conf
进入主节点容器内部:
docker exec -it mongoMaster mongosh
var config = {
_id:"rs0",
members:[
{_id:0, host:"外网IP:27017"},
{_id:1, host:"外网IP:27018"},
{_id:2, host:"外网IP:27019"}
]
}
rs.initiate(config) //启动副本集
//创建用户用于认证
db.createUser(
{
user:"用户名",
pwd:"密码",
roles:[
{role: "root", db:"admin" }
]
}
)
//从节点暂时可读
rs.secondaryOk()
db.getMongo().setReadPref("primaryPreferred")
rs.conf() //显示副本集配置
测试:
主节点执行写操作,从节点同步
主节点可读写,从节点只读
当主节点宕机后,从节点会选举出一个新的主节点
当主节点恢复后会变成从节点
副本集扩缩容
rs.add({host:"IP:端口号"}) # 添加新的节点到副本集
rs.remove("IP:端口号") # 从副本集中删除节点(一次只能删除一个节点)
rs.reconfig(config) # 重新加载配置(一次可删除多个节点)
创建一个新的节点:
docker run -d -p 27020:27020 --name mongoSlave3 --privileged=true
-v /root/mongoReplication/newNode/data:/data/db
-v /root/mongoReplication/keyfile:/opt/keyfile
-v /root/mongoReplication/newNode/configdb/:/data/configdb
-v /root/mongoReplication/newNode/logs/:/var/log/mongodb/
mongo:5.0.5 mongod --config /data/configdb/mongod.conf
添加到副本集:
使用splice删除节点,重新加载配置:
SpringBoot连接副本集日志:
spring:
data:
mongodb:
host: 外网IP # 主节点
port: 端口 # 主节点
database: test # 操作数据库test
authentication-database: admin # 认证数据库admin
replica-set-name: rs0 # 副本集名称
username: 用户名
password: 密码
当客户端访问Router执行读写操作时,Router会去访问Config Servers(配置服务器)获取分片集群的信息,进而执行相应的操作
结构图:
节点 | 端口 |
---|---|
路由节点 | 27017 |
分片1主节点 | 27018 |
分片1从节点 | 27019 |
分片1从节点 | 27020 |
分片2主节点 | 27021 |
分片2从节点 | 27022 |
分片2从节点 | 27023 |
分片3主节点 | 27024 |
分片3从节点 | 27025 |
分片3从节点 | 27026 |
配置主节点 | 27027 |
配置从节点 | 27028 |
配置从节点 | 27029 |
分片服务器配置文件(其他分片类似):
storage:
dbPath: /data/db
journal:
enabled: true
systemLog:
destination: file
logAppend: true
path: /var/log/mongodb/mongod.log
net:
port: 27018
bindIp: 0.0.0.0
processManagement:
timeZoneInfo: /usr/share/zoneinfo
security:
keyFile: /opt/keyfile
authorization: enabled
replication:
replSetName: "s0" # 分片1
sharding:
clusterRole: shardsvr # 作为分片
# 分片服务器
var config = {
_id:"s0", # 分片1
members:[
{_id:0, host:"外网IP:27018"},
{_id:1, host:"外网IP:27019"},
{_id:2, host:"外网IP:27020"}
]
}
rs.initiate(config) # 启动副本集
var config = {
_id:"s1", # 分片2
members:[
{_id:0, host:"外网IP:27021"},
{_id:1, host:"外网IP:27022"},
{_id:2, host:"外网IP:27023"}
]
}
rs.initiate(config) # 启动副本集
var config = {
_id:"s2", # 分片3
members:[
{_id:0, host:"外网IP:27024"},
{_id:1, host:"外网IP:27025"},
{_id:2, host:"外网IP:27026"}
]
}
rs.initiate(config) # 启动副本集
# 配置服务器
var config = {
_id:"c0",
configsvr: true, # 指定副本集用于配置服务器
members:[
{_id:0, host:"外网IP:27027"},
{_id:1, host:"外网IP:27028"},
{_id:2, host:"外网IP:27029"}
]
}
rs.initiate(config) # 启动副本集
db.createUser(
{
user:"用户名",
pwd:"密码",
roles:[
{role: "root", db:"admin" }
]
}
)
分片服务器副本集:
配置服务器配置文件:
storage:
dbPath: /data/db
journal:
enabled: true
systemLog:
destination: file
logAppend: true
path: /var/log/mongodb/mongod.log
net:
port: 27027
bindIp: 0.0.0.0
processManagement:
timeZoneInfo: /usr/share/zoneinfo
security:
keyFile: /opt/keyfile
authorization: enabled
replication:
replSetName: "c0" # 配置服务器副本集名称
sharding:
clusterRole: configsvr # 作为配置服务器
docker run -d -p 27017:27017 --name router --privileged=true
-v /root/cluster/keyfile:/opt/keyfile
-v /root/cluster/router/logs:/var/log/mongodb/
-e MONGO_INITDB_ROOT_USERNAME=用户名
-e MONGO_INITDB_ROOT_PASSWORD=密码
mongo:5.0.5 mongos --keyFile /opt/keyfile # 这里使用mongos
--bind_ip=0.0.0.0,:: # 允许外部访问
--configdb 副本集名称/各个节点的IP:端口,... # 配置服务器副本集
# 添加三个分片
sh.addShard("s0/外网IP:27018,外网IP:27019,外网IP:27020")
sh.addShard("s1/外网IP:27021,外网IP:27022,外网IP:27023")
sh.addShard("s2/外网IP:27024,外网IP:27025,外网IP:27026")
sh.status() # 查看分片集群信息
sh.enableSharding("库名") # 数据库开启分片
sh.shardCollection("库名.集合名", { _id: "hashed"}) # 指定分片键按_id哈希散列对集合进行分片
sh.shardCollection("库名.集合名", { _id: 1}) # 指定分片键按_id范围对集合进行分片
插入数据测试分片:
# 插入1000数据 对于不同的分片键查看分片情况
for(let i = 0; i < 1000; i++){
db.users.insert({_id:i, name:"王" + i})
}
分片键_id单调递增,使用范围分片将导致大量数据存入同一个分片,分布不均匀
使用哈希分片,将数据尽量平均地分布在每一个分片