仅为学习记录,方便回顾复习,如有侵权请联系删除!
MongoDB笔记
目录
文章目录
1 认识MongoDB
MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。
MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。
它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。
MongoDB服务端可运行在Linux、Windows或mac os x平台,支持32位和64位应用,默认端口为27017。
推荐运行在64位平台,因为MongoDB在32位模式运行时支持的最大文件尺寸为2GB。
2 MongoDB主要特点
1、文档
MongoDB中的记录是一个文档,它是由字段和值对组成的数据结构。
多个键及其关联的值有序地放在一起就构成了文档。
MongoDB文档类似于JSON对象。字段的值可以包括其他文档,数组和文档数组。

{“greeting”:“hello,world”}
这个文档只有一个键“greeting”
,对应的值为“hello,world”
。多数情况下,文档比这个更复杂,它包含多个键/值对。
文档中的值不仅可以是双引号中的字符串,也可以是其他的数据类型,例如,整型、布尔型等,也可以是另外一个文档,即文档可以嵌套。文档中的键类型只能是字符串。
使用文档的优点是:
- 文档(即对象)对应于许多编程语言中的本机数据类型。
- 嵌入式文档和数组减少了对昂贵连接的需求。
- 动态模式支持流畅的多态性。
2、集合
集合就是一组文档,类似于关系数据库中的表。
集合是无模式的,集合中的文档可以是各式各样的。例如,{“hello,word”:“Mike”}和{“foo”: 3},它们的键不同,值的类型也不同,但是它们可以存放在同一个集合中,也就是不同模式的文档都可以放在同一个集合中。
这是因为所有文档都放在同一个集合中,无论对于开发者还是管理员,都很难对集合进行管理,而且这种情形下,对集合的查询等操作效率都不高。所以在实际使用中,往往将文档分类存放在不同的集合中。
例如,对于网站的日志记录,可以根据日志的级别进行存储,Info级别日志存放在Info 集合中,Debug 级别日志存放在Debug 集合中,这样既方便了管理,也提供了查询性能。
但是需要注意的是,这种对文档进行划分来分别存储并不是MongoDB 的强制要求,用户可以灵活选择。
可以使用“.”按照命名空间将集合划分为子集合。
例如,对于一个博客系统,可能包括blog.user 和blog.article 两个子集合,这样划分只是让组织结构更好一些,blog 集合和blog.user、blog.article 没有任何关系。虽然子集合没有任何特殊的地方,但是使用子集合组织数据结构清晰,这也是MongoDB 推荐的方法。
3、数据库
MongoDB 中多个文档组成集合,多个集合组成数据库。
一个MongoDB 实例可以承载多个数据库。它们之间可以看作相互独立,每个数据库都有独立的权限控制。在磁盘上,不同的数据库存放在不同的文件中。
MongoDB 中存在以下系统数据库。
-
Admin 数据库:一个权限数据库,如果创建用户的时候将该用户添加到admin 数据库中,那么该用户就自动继承了所有数据库的权限。
-
Local 数据库:这个数据库永远不会被复制,可以用来存储本地单台服务器的任意集合。
-
Config 数据库:当MongoDB 使用分片模式时,config 数据库在内部使用,用于保存分片的信息。
4、数据模型
一个MongoDB 实例可以包含一组数据库,一个DataBase 可以包含一组Collection(集合),一个集合可以包含一组Document(文档)。
一个Document包含一组field(字段),每一个字段都是一个key/value pair
。
-
key: 必须为字符串类型。
-
value:可以包含如下类型。
- 基本类型,例如,string,int,float,timestamp,binary 等类型。
- 一个document。
- 数组类型。
3 Linux安装MongoDB
一、下载
官方下载地址:https://www.mongodb.com/download-center/community
二、解压安装
1、解压
tar -zxvf mongodb-linux-x86_64-rhel70-4.4.15.tgz
2、创建目录/usr/local/mongo,并将解压完的mongodb目录移动到/usr/local/mongo下
mkdir -p /usr/local/mongo
mv mongodb-linux-x86_64-rhel70-4.4.15/* /usr/local/mongo/
3、切到/usr/local/mongo目录下,创建目录
mkdir -p data/db #数据库目录
mkdir -p logs #日志目录
mkdir -p conf #配置文件目录
mkdir -p pids #进程描述文件目录
操作记录:
[root@MiWiFi-RA69-srv mongodb-linux-x86_64-rhel70-4.4.15]# cd /usr/local/mongo
[root@MiWiFi-RA69-srv mongo]# ll
总用量 132
drwxr-xr-x. 2 root root 70 7月 31 08:11 bin
-rw-r--r--. 1 root root 30608 6月 15 23:08 LICENSE-Community.txt
-rw-r--r--. 1 root root 16726 6月 15 23:08 MPL-2
-rw-r--r--. 1 root root 1977 6月 15 23:08 README
-rw-r--r--. 1 root root 76728 6月 15 23:08 THIRD-PARTY-NOTICES
[root@MiWiFi-RA69-srv mongo]# mkdir -p data/db
[root@MiWiFi-RA69-srv mongo]# mkdir -p logs
[root@MiWiFi-RA69-srv mongo]# mkdir -p conf
[root@MiWiFi-RA69-srv mongo]# mkdir -p pids
4、在conf目录,增加配置文件mongo.conf
vi /usr/local/mongo/conf/mongo.conf
#数据保存路径
dbpath=/usr/local/mongo/data/db/
#日志保存路径
logpath=/usr/local/mongo/logs/mongo.log
#进程描述文件
pidfilepath=/usr/local/mongo/pids/mongo.pid
#日志追加写入
logappend=true
bind_ip_all=true
#mongo默认端口
port=27017
#操作日志容量
oplogSize=10000
#开启子进程
fork=true
5、通过配置文件启动mongo服务端
/usr/local/mongo/bin/mongod -f /usr/local/mongo/conf/mongo.conf
启动成功如下
[root@MiWiFi-RA69-srv ~]# /usr/local/mongo/bin/mongod -f /usr/local/mongo/conf/mongo.conf
about to fork child process, waiting until server is ready for connections.
forked process: 2668
child process started successfully, parent exiting
[root@MiWiFi-RA69-srv ~]#
6、启动mongo客户端
启动命令
/usr/local/mongo/bin/mongo --host 127.0.0.1 --port 27017
启动成功如下
[root@MiWiFi-RA69-srv mongo]# /usr/local/mongo/bin/mongo --host 127.0.0.1 --port 27017
MongoDB shell version v4.4.15
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("0e522800-eae2-41e4-8dbc-392114883c93") }
MongoDB server version: 4.4.15
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
https://docs.mongodb.com/
Questions? Try the MongoDB Developer Community Forums
https://community.mongodb.com
---
The server generated these startup warnings when booting:
2022-07-31T08:34:40.136+08:00: Access control is not enabled for the database. Read and write access to data and configuration is unrestricted
2022-07-31T08:34:40.137+08:00: You are running this process as the root user, which is not recommended
2022-07-31T08:34:40.137+08:00: /sys/kernel/mm/transparent_hugepage/enabled is 'always'. We suggest setting it to 'never'
2022-07-31T08:34:40.137+08:00: /sys/kernel/mm/transparent_hugepage/defrag is 'always'. We suggest setting it to 'never'
2022-07-31T08:34:40.137+08:00: Soft rlimits too low
2022-07-31T08:34:40.137+08:00: currentValue: 1024
2022-07-31T08:34:40.137+08:00: recommendedMinimum: 64000
---
---
Enable MongoDB's free cloud-based monitoring service, which will then receive and display
metrics about your deployment (disk utilization, CPU, operation statistics, etc).
The monitoring data will be available on a MongoDB website with a unique URL accessible to you
and anyone you share the URL with. MongoDB may use this information to make product
improvements and to suggest MongoDB products and deployment options to you.
To enable free monitoring, run the following command: db.enableFreeMonitoring()
To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
---
>
4 Windows安装MongoDB
一、MongoDB简介
MongoDB是一个基于分布式文件存储的数据库,由C++语言编写,旨在为WEB应用提供可扩展的高性能数据存储解决方案。
MongoDB将数据存储为一个文档,数据结构由键值(key=>value)对组成,MongoDB文档类似于JSON对象,字段值可以包含其他文档,数组及文档数组。
MongoDB服务端可运行在Linux、Windows或mac os x平台,支持32位和64位应用,默认端口为27017。
MongoDB支持各种编程语言: Python,Java,C++,PHP,C#等多种语言。
二、下载MongoDB
MongoDB提供了可用于32位系统和64位系统的预编译二进制包(新版本没有了32位系统的安装文件),你可以进入MongoDB官网下载安装,MongoDB的预编译二进制包的下载地址为:https://www.mongodb.com/download-center/community,打开之后会看到如下图,直接点击Download下载即可,也可以在Version中选择你想要的版本:
我选的是MongoDB 4.4版本,安装在D:\developer_tools\MongoDB\Server\4.4\
安装时出现错误,只需在服务中把MongoDB的服务的属性中的登录选项卡中的登录身份改成本地系统个账户。
配置环境变量,使cmd终端在任何目录都可以使用mongo语言
三、安装MongoDB
四、配置MongoDB
五、可能遇到的问题
5 Windows安装可视化工具Robo 3T
已下载至D:\AllDownloads\ThunderDownload中的studio-3t-x64.zip
Robo 3T的
注册的账号密码
chw_1118@outlook.com
-jB1
mongodb默认端口27017
6 MongoDB 基本操作CRUD
一、基本操作
登录linux的mongo数据库
/usr/local/mongo/bin/mongo --host 127.0.0.1 --port 27017
1、查看和选择数据库
查看数据库
> show databases
admin 0.000GB
config 0.000GB
local 0.000GB
选择数据库
如果切换到一个没有的数据库,例如use admin2,那么会隐式创建这个数据库。(后期当该数据库有数据时,系统自动创建)
use admin2
如:
> use admin2
switched to db admin2
> show databases
admin 0.000GB
config 0.000GB
local 0.000GB
2、查看集合show collections
show collections
3、创建集合de.createCollection(‘name’)
db.createCollection('集合名')
演示
> db.createCollection('c1')
{ "ok" : 1 }
> show collections
c1
> db.createCollection('c2')
{ "ok" : 1 }
> show collections
c1
c2
4、删除集合db.‘name’.drop()
db.集合名.drop()
演示
> show collections
c1
c2
> db.c2.drop()
true
> show collections
c1
5、删除数据库db.dropDatabase
通过 use
语法选择数据库
通过 db.dropDatabase()
删除数据库
演示
> show databases
admin 0.000GB
admin2 0.000GB
config 0.000GB
local 0.000GB
test1 0.000GB
> use test1
switched to db test1
> db.dropDatabase()
{ "dropped" : "test1", "ok" : 1 }
> show databases
admin 0.000GB
admin2 0.000GB
config 0.000GB
local 0.000GB
二、增删改查
1、增加 db.集合名.insert(json数据)
1.1 命令介绍
db.集合名.insert(JSON数据)
如果集合存在,直接插入数据。如果集合不存在,则隐式创建。
演示:在test2数据库的c1集合中插入数据(姓名叫zelda,年龄16岁)
> use test2 #需要先切换到目标数据库,否则报错
switched to db test2
> db.c1.insert({name:"zelda",age:16})
WriteResult({ "nInserted" : 1 })
-
数据库和集合不存在都隐式创建
-
对象的键统一不加引号(方便看),但是查看集合数据时系统会自动加
-
mongodb会给每条数据增加一个全球唯一的_id键
-
> db.c1.insert({name:"zelda",age:16}) WriteResult({ "nInserted" : 1 }) > db.c1.find() { "_id" : ObjectId("62e5d5dde2dd6d254235722c"), "name" : "zelda", "age" : 16 }
-
_id
键的组成:时间戳+机器码+PID+计数器 -
自定义
_id
:只需给插入的json数据增加_id
键即可覆盖,但十分不推荐。-
db.c1.insert({_id:1, uname:"zelda", age:16})
-
-
1.2 一次性插入多条数据
传递数据,数组中写一个个JSON数据即可
db.c1.insert(
[
{uname:"link",age:16},
{uname:"paya",age:16},
{uname:"mipha",age:16}
]
)
演示结果
> db.c1.insert([{uname:"link",age:16},{uname:"paya",age:16},{uname:"mipha",age:16}])
BulkWriteResult({
"writeErrors" : [ ],
"writeConcernErrors" : [ ],
"nInserted" : 3,
"nUpserted" : 0,
"nMatched" : 0,
"nModified" : 0,
"nRemoved" : 0,
"upserted" : [ ]
})
1.3 快速插入10条数据
由于mongodb底层使用JS引擎实现的,所以支持部分js语法。因此:可以写for循环
for(var i=1; i<=10; i++){ db.c2.insert({name:"a"+i,age:i}) }
演示结果
> for(var i=1; i<=10; i++){ db.c2.insert({name:"a"+i,age:i}) } #for循环快速插入10条
WriteResult({ "nInserted" : 1 })
> show collections #显示集合
c1
c2
> db.c2.find() #查看c2集合内容
{ "_id" : ObjectId("62e5d8d5e2dd6d2542357230"), "name" : "a1", "age" : 1 }
{ "_id" : ObjectId("62e5d8d5e2dd6d2542357231"), "name" : "a2", "age" : 2 }
{ "_id" : ObjectId("62e5d8d5e2dd6d2542357232"), "name" : "a3", "age" : 3 }
{ "_id" : ObjectId("62e5d8d5e2dd6d2542357233"), "name" : "a4", "age" : 4 }
{ "_id" : ObjectId("62e5d8d5e2dd6d2542357234"), "name" : "a5", "age" : 5 }
{ "_id" : ObjectId("62e5d8d5e2dd6d2542357235"), "name" : "a6", "age" : 6 }
{ "_id" : ObjectId("62e5d8d5e2dd6d2542357236"), "name" : "a7", "age" : 7 }
{ "_id" : ObjectId("62e5d8d5e2dd6d2542357237"), "name" : "a8", "age" : 8 }
{ "_id" : ObjectId("62e5d8d5e2dd6d2542357238"), "name" : "a9", "age" : 9 }
{ "_id" : ObjectId("62e5d8d5e2dd6d2542357239"), "name" : "a10", "age" : 10 }
2、查询文档
2.1 命令介绍
db.集合名.find(条件,查询的列)
条件 | 写法 |
---|---|
查询所有数据 | {}或者不写 |
查询age=6的数据 | {age:6} |
age=6且性别=男 | {age:6,sex:‘男’} |
查询的列(可选参数) | 写法 |
---|---|
查询全部列(字段) | 不写 |
只显示age列(字段) | {age:1} |
除了age列(字段)都显示 | {age:0} |
其他语法
db.集合名.find({
键:{运算符:值}
})
运算符 | 作用 |
---|---|
$gt | 大于 |
$gte | 大于等于 |
$lt | 小于 |
$lte | 小于等于 |
$ne | 不等于 |
$in | in |
$nin | not in |
2.2 实例练习
查询所有数据
db.c1.find()
> db.c1.find()
{ "_id" : ObjectId("62e5d5dde2dd6d254235722c"), "name" : "zelda", "age" : 16 }
{ "_id" : ObjectId("62e5d820e2dd6d254235722d"), "uname" : "link", "age" : 16 }
{ "_id" : ObjectId("62e5d820e2dd6d254235722e"), "uname" : "paya", "age" : 16 }
{ "_id" : ObjectId("62e5d820e2dd6d254235722f"), "uname" : "mipha", "age" : 16 }
系统的_id无论如何都会存在
查询age大于5的数据
db.c1.find({age:{$gt:5}})
> db.c1.find({age:{$gt:5}})
{ "_id" : ObjectId("62e5d5dde2dd6d254235722c"), "name" : "zelda", "age" : 16 }
{ "_id" : ObjectId("62e5d820e2dd6d254235722d"), "uname" : "link", "age" : 16 }
{ "_id" : ObjectId("62e5d820e2dd6d254235722e"), "uname" : "paya", "age" : 16 }
{ "_id" : ObjectId("62e5d820e2dd6d254235722f"), "uname" : "mipha", "age" : 16 }
查询年龄是5岁、8岁、10岁的数据
db.c1.find({age:{$in:[5,8,10]}})
> db.c2.find({age:{$in:[5,8,10]}})
{ "_id" : ObjectId("62e5d8d5e2dd6d2542357234"), "name" : "a5", "age" : 5 }
{ "_id" : ObjectId("62e5d8d5e2dd6d2542357237"), "name" : "a8", "age" : 8 }
{ "_id" : ObjectId("62e5d8d5e2dd6d2542357239"), "name" : "a10", "age" : 10 }
> db.c1.find({age:{$in:[5,8,10]}})
{ "_id" : ObjectId("62e5dfbee2dd6d254235723a"), "uname" : "bokblin", "age" : 5 }
{ "_id" : ObjectId("62e5dfbee2dd6d254235723b"), "uname" : "moliblin", "age" : 8 }
{ "_id" : ObjectId("62e5dfbee2dd6d254235723c"), "uname" : "chuchu", "age" : 10 }
{ "_id" : ObjectId("62e5dfbee2dd6d254235723d"), "uname" : "lizard", "age" : 5 }
{ "_id" : ObjectId("62e5dfbee2dd6d254235723e"), "uname" : "bat", "age" : 8 }
{ "_id" : ObjectId("62e5dfbee2dd6d254235723f"), "uname" : "defender", "age" : 10 }
{ "_id" : ObjectId("62e5dfbee2dd6d2542357240"), "uname" : "bear", "age" : 5 }
{ "_id" : ObjectId("62e5dfbee2dd6d2542357241"), "uname" : "butterfly", "age" : 8 }
只看年龄列,或者年龄以外的列
只看年龄列:db.c2.find({},{age:1})
只看年龄之外的列:db.c2.find({},{age:0})
> db.c1.find({},{age:1})
{ "_id" : ObjectId("62e5d5dde2dd6d254235722c"), "age" : 16 }
{ "_id" : ObjectId("62e5d820e2dd6d254235722d"), "age" : 16 }
{ "_id" : ObjectId("62e5d820e2dd6d254235722e"), "age" : 16 }
{ "_id" : ObjectId("62e5d820e2dd6d254235722f"), "age" : 16 }
{ "_id" : ObjectId("62e5dfbee2dd6d254235723a"), "age" : 5 }
{ "_id" : ObjectId("62e5dfbee2dd6d254235723b"), "age" : 8 }
{ "_id" : ObjectId("62e5dfbee2dd6d254235723c"), "age" : 10 }
{ "_id" : ObjectId("62e5dfbee2dd6d254235723d"), "age" : 5 }
{ "_id" : ObjectId("62e5dfbee2dd6d254235723e"), "age" : 8 }
{ "_id" : ObjectId("62e5dfbee2dd6d254235723f"), "age" : 10 }
{ "_id" : ObjectId("62e5dfbee2dd6d2542357240"), "age" : 5 }
{ "_id" : ObjectId("62e5dfbee2dd6d2542357241"), "age" : 8 }
> db.c1.find({},{age:0})
{ "_id" : ObjectId("62e5d5dde2dd6d254235722c"), "name" : "zelda" }
{ "_id" : ObjectId("62e5d820e2dd6d254235722d"), "uname" : "link" }
{ "_id" : ObjectId("62e5d820e2dd6d254235722e"), "uname" : "paya" }
{ "_id" : ObjectId("62e5d820e2dd6d254235722f"), "uname" : "mipha" }
{ "_id" : ObjectId("62e5dfbee2dd6d254235723a"), "uname" : "bokblin" }
{ "_id" : ObjectId("62e5dfbee2dd6d254235723b"), "uname" : "moliblin" }
{ "_id" : ObjectId("62e5dfbee2dd6d254235723c"), "uname" : "chuchu" }
{ "_id" : ObjectId("62e5dfbee2dd6d254235723d"), "uname" : "lizard" }
{ "_id" : ObjectId("62e5dfbee2dd6d254235723e"), "uname" : "bat" }
{ "_id" : ObjectId("62e5dfbee2dd6d254235723f"), "uname" : "defender" }
{ "_id" : ObjectId("62e5dfbee2dd6d2542357240"), "uname" : "bear" }
{ "_id" : ObjectId("62e5dfbee2dd6d2542357241"), "uname" : "butterfly" }
3、修改文档
3.1 命令介绍
db.集合名.update(要修改的文档,{修改器:{键:值}},是否不存在插入,是否修改多条)
- 修改器作用(如果不使用修改器,则新数据将替换原来数据)
- $inc 递增
- $rename 重命名列
- $set 修改列值
- $unset 删除列
- 是否不存在插入
- 指匹配不到数据则插入。true是插入,false是不插入,默认为false。
- 是否修改多条
- true则将匹配成功的数据都修改,false则只修改一条,默认为false。
db.c3.update({uname:"zs30"},{$set:{age:30}},false,true)
#把uname为"zs30"的文档的age修改为30,不插入,修改全部。
3.2 实例练习
准备工作
use test2
for(var i=1;i<=10;i++){
db.c3.insert({uname:"zs"+i,age:i});
}
1、将{uname:"zs1"}
改为{uname:"zs2"}
db.c3.update({uname:"zs1"},{$set:{uname:"zs2"}})
> db.c3.find()
{ "_id" : ObjectId("62e5e269e2dd6d2542357242"), "uname" : "zs1", "age" : 1 } #update前
{ "_id" : ObjectId("62e5e269e2dd6d2542357243"), "uname" : "zs2", "age" : 2 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357244"), "uname" : "zs3", "age" : 3 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357245"), "uname" : "zs4", "age" : 4 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357246"), "uname" : "zs5", "age" : 5 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357247"), "uname" : "zs6", "age" : 6 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357248"), "uname" : "zs7", "age" : 7 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357249"), "uname" : "zs8", "age" : 8 }
{ "_id" : ObjectId("62e5e269e2dd6d254235724a"), "uname" : "zs9", "age" : 9 }
{ "_id" : ObjectId("62e5e269e2dd6d254235724b"), "uname" : "zs10", "age" : 10 }
> db.c3.update({uname:"zs1"},{$set:{uname:"zs2"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.c3.find()
{ "_id" : ObjectId("62e5e269e2dd6d2542357242"), "uname" : "zs2", "age" : 1 } #update后
{ "_id" : ObjectId("62e5e269e2dd6d2542357243"), "uname" : "zs2", "age" : 2 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357244"), "uname" : "zs3", "age" : 3 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357245"), "uname" : "zs4", "age" : 4 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357246"), "uname" : "zs5", "age" : 5 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357247"), "uname" : "zs6", "age" : 6 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357248"), "uname" : "zs7", "age" : 7 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357249"), "uname" : "zs8", "age" : 8 }
{ "_id" : ObjectId("62e5e269e2dd6d254235724a"), "uname" : "zs9", "age" : 9 }
{ "_id" : ObjectId("62e5e269e2dd6d254235724b"), "uname" : "zs10", "age" : 10 }
2、给 {uname:"zs10"}
的年龄加2或减2
db.c3.update({uname:"zs10"},{$inc:{age:2}})
递减只需要将2
改为-2
即可。
> db.c3.find()
{ "_id" : ObjectId("62e5e269e2dd6d2542357242"), "uname" : "zs2", "age" : 1 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357243"), "uname" : "zs2", "age" : 2 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357244"), "uname" : "zs3", "age" : 3 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357245"), "uname" : "zs4", "age" : 4 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357246"), "uname" : "zs5", "age" : 5 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357247"), "uname" : "zs6", "age" : 6 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357248"), "uname" : "zs7", "age" : 7 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357249"), "uname" : "zs8", "age" : 8 }
{ "_id" : ObjectId("62e5e269e2dd6d254235724a"), "uname" : "zs9", "age" : 9 }
{ "_id" : ObjectId("62e5e269e2dd6d254235724b"), "uname" : "zs10", "age" : 10 } #update前
> db.c3.update({uname:"zs10"},{$inc:{age:2}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.c3.find()
{ "_id" : ObjectId("62e5e269e2dd6d2542357242"), "uname" : "zs2", "age" : 1 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357243"), "uname" : "zs2", "age" : 2 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357244"), "uname" : "zs3", "age" : 3 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357245"), "uname" : "zs4", "age" : 4 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357246"), "uname" : "zs5", "age" : 5 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357247"), "uname" : "zs6", "age" : 6 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357248"), "uname" : "zs7", "age" : 7 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357249"), "uname" : "zs8", "age" : 8 }
{ "_id" : ObjectId("62e5e269e2dd6d254235724a"), "uname" : "zs9", "age" : 9 }
{ "_id" : ObjectId("62e5e269e2dd6d254235724b"), "uname" : "zs10", "age" : 12 } #update后
3、综合练习插入数据
db.c4.insert({uname:"盖侬",age:1000,sex:"male",nationality:"Hyrule"})
4、删除文档
4.1 命令介绍
db.集合名.remove(文档,是否删除1条)
#true:删除第一条数据
#false:删除匹配的所有条
4.2 实例练习
true 删除第一条
db.c3.remove({},true)
如果{}里加了数据,就是删除匹配到的第一条。
> db.c3.find()
{ "_id" : ObjectId("62e5e269e2dd6d2542357242"), "uname" : "zs2", "age" : 1 } #删除前
{ "_id" : ObjectId("62e5e269e2dd6d2542357243"), "uname" : "zs2", "age" : 2 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357244"), "uname" : "zs3", "age" : 3 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357245"), "uname" : "zs4", "age" : 4 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357246"), "uname" : "zs5", "age" : 5 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357247"), "uname" : "zs6", "age" : 6 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357248"), "uname" : "zs7", "age" : 7 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357249"), "uname" : "zs8", "age" : 8 }
{ "_id" : ObjectId("62e5e269e2dd6d254235724a"), "uname" : "zs9", "age" : 9 }
{ "_id" : ObjectId("62e5e269e2dd6d254235724b"), "uname" : "zs10", "age" : 12 }
> db.c3.remove({},true) #填true
WriteResult({ "nRemoved" : 1 })
> db.c3.find()
{ "_id" : ObjectId("62e5e269e2dd6d2542357243"), "uname" : "zs2", "age" : 2 } #删除后
{ "_id" : ObjectId("62e5e269e2dd6d2542357244"), "uname" : "zs3", "age" : 3 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357245"), "uname" : "zs4", "age" : 4 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357246"), "uname" : "zs5", "age" : 5 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357247"), "uname" : "zs6", "age" : 6 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357248"), "uname" : "zs7", "age" : 7 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357249"), "uname" : "zs8", "age" : 8 }
{ "_id" : ObjectId("62e5e269e2dd6d254235724a"), "uname" : "zs9", "age" : 9 }
{ "_id" : ObjectId("62e5e269e2dd6d254235724b"), "uname" : "zs10", "age" : 12 }
false 删除匹配到的多条
准备工作
> db.c3.insert([{uname:"zs15",age:7},{uname:"zs15",age:77},{uname:"zs15",age:777}])
删除匹配到的多条
db.c3.remove({uname:"zs15"},false)
> db.c3.find()
{ "_id" : ObjectId("62e5e269e2dd6d2542357243"), "uname" : "zs2", "age" : 2 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357244"), "uname" : "zs3", "age" : 3 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357245"), "uname" : "zs4", "age" : 4 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357246"), "uname" : "zs5", "age" : 5 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357247"), "uname" : "zs6", "age" : 6 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357248"), "uname" : "zs7", "age" : 7 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357249"), "uname" : "zs8", "age" : 8 }
{ "_id" : ObjectId("62e5e269e2dd6d254235724a"), "uname" : "zs9", "age" : 9 }
{ "_id" : ObjectId("62e5e269e2dd6d254235724b"), "uname" : "zs10", "age" : 12 }
{ "_id" : ObjectId("62e5e917e2dd6d254235724f"), "uname" : "zs15", "age" : 7 } #删除前
{ "_id" : ObjectId("62e5e917e2dd6d2542357250"), "uname" : "zs15", "age" : 77 } #删除前
{ "_id" : ObjectId("62e5e917e2dd6d2542357251"), "uname" : "zs15", "age" : 777 } #删除前
> db.c3.remove({uname:"zs15"},false) #改为false
WriteResult({ "nRemoved" : 3 })
> db.c3.find()
{ "_id" : ObjectId("62e5e269e2dd6d2542357243"), "uname" : "zs2", "age" : 2 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357244"), "uname" : "zs3", "age" : 3 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357245"), "uname" : "zs4", "age" : 4 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357246"), "uname" : "zs5", "age" : 5 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357247"), "uname" : "zs6", "age" : 6 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357248"), "uname" : "zs7", "age" : 7 }
{ "_id" : ObjectId("62e5e269e2dd6d2542357249"), "uname" : "zs8", "age" : 8 }
{ "_id" : ObjectId("62e5e269e2dd6d254235724a"), "uname" : "zs9", "age" : 9 }
{ "_id" : ObjectId("62e5e269e2dd6d254235724b"), "uname" : "zs10", "age" : 12 }
#删除后
#删除后
#删除后
5、总结
所有数据库都需要增删改查CRUD标识
CRUD
- C:Create,增加
- MongoDB语法:
db.集合名.insert(JSON数据)
- MongoDB语法:
- R:Read,查询
- MongoDB语法:
db.集合名.find(条件,查询的列)
- MongoDB语法:
- U:Update,修改
- MongoDB语法:
db.集合名.update(要修改的文档,{修改器:{键:值}},是否不存在插入,是否修改多条)
- MongoDB语法:
- D:Delete,删除
- MongoDB语法:
db.集合名.remove(文档,是否删除1条true/false默认false)
- MongoDB语法:
7 MongoDB 存储数据类型
BSON介绍
MongoDB中每条记录称作一个文档,这个文档和我们平时用的JSON有点像,但也不完全一样。JSON是一种轻量级的数据交换格式。简洁和清晰的层次结构使得JSON成为理想的数据交换语言,JSON易于阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率,但是JSON也有它的局限性,比如它只有null、布尔、数字、字符串、数组和对象这几种数据类型,没有日期类型,只有一种数字类型,无法区分浮点数和整数,也没法表示正则表达式或者函数。由于这些局限性,BSON闪亮登场啦,BSON是一种类JSON的二进制形式的存储格式,简称Binary JSON,它和JSON一样,支持内嵌的文档对象和数组对象,但是BSON有JSON没有的一些数据类型,如Date和BinData类型,MongoDB使用BSON做为文档数据存储和网络传输格式。
一、数字
shell默认使用64位浮点型数值,如下:
db.sang_collec.insert({x:3.1415926})
db.sang_collec.insert({x:3})
对于整型值,我们可以使用NumberInt或者NumberLong表示,如下:
db.sang_collec.insert({x:NumberInt(10)})
db.sang_collec.insert({x:NumberLong(12)})
二、字符串
字符串也可以直接存储,如下:
db.sang_collec.insert({x:"hello,MongoDB!"})
三、正则表达式
正则表达式主要用在查询里边,查询时我们可以使用正则表达式,语法和JavaScript中正则表达式的语法相同,比如查询所有key为x,value以hello开始的文档且不区分大小写:
db.sang_collec.find({x:/^(hello)(.[a-zA-Z0-9])+/i})
正则表达式中/i,/g,/ig,/gi,/m的区别和含义
/i (忽略大小写)
/g (全文查找出现的所有匹配字符)
/m (多行查找)
/gi(全文查找、忽略大小写)
/ig(全文查找、忽略大小写)
四、数组类型
数组也支持,如下:
db.sang_collec.insert({x:[1,2,3,4,new Date()]})
数组中的数据类型可以是多种多样的。
五、日期类型
MongDB支持Date类型的数据,可以直接new一个Date对象
db.sang_collec.insert({x:new Date()})
六、内嵌文档
一个文档也可以作为另一个文档的value,这个其实很好理解,如下:
db.sang_collec.insert({name:"三国演义",author:{name:"罗贯中",age:999}})
书有一个属性是作者,作者又有name,年龄等属性。
8 MongoDB 中的索引
一、索引
1、什么是索引
索引是特殊的数据结构,它以一种易于遍历的形式存储集合数据集的一小部分。索引存储一个或一组特定字段的值,按字段的值排序。索引项的排序支持有效的相等匹配和基于范围的查询操作。此外,MongoDB可以通过使用索引中的排序返回排序后的结果。
MongoDB使用的存储引擎是WiredTiger,其中索引构建使用的是 B+ tree 。
比如基于 B+ tree 的索引数据结构图示如下:

1.1 单键索引
如基于主键ID 进行的B+ tree 数据结构
1.2 复合索引
复合索引只能支持前缀子查询。
例如:基于(name, age, position)建立的复合索引
以下排列会走索引:
name
name age
name age postion
以下排列不会走索引:
age :
position
age position
2、索引的特点
索引支持更快的查询,更快的排序。
3、默认id索引
在创建集合期间,MongoDB 在_id字段上创建唯一索引。该索引可防止客户端插入两个具有相同值的文档。你不能将_id字段上的index删除。
二、实战
数据构造:
> db.members.insertMany(
[
{name:"zhangsan",age:19,tags:["00","It","SH"]},
{name:"lisi",age:35,tags:["80","It","BJ"]},
{name:"wangwu",age:31,tags:["90","It","SZ"]}
]
)
> db.members.find()
{ "_id" : ObjectId("62e5f6a3e2dd6d2542357252"), "name" : "zhangsan", "age" : 19, "tags" : [ "00", "It", "SH" ] }
{ "_id" : ObjectId("62e5f6a3e2dd6d2542357253"), "name" : "lisi", "age" : 35, "tags" : [ "80", "It", "BJ" ] }
{ "_id" : ObjectId("62e5f6a3e2dd6d2542357254"), "name" : "wangwu", "age" : 31, "tags" : [ "90", "It", "SZ" ] }
1、索引查询
命令:db.集合名.getIndexes()
> db.members.getIndexes()
[ { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_" } ]
可以看到生成的集合默认是存在索引的。
- v:索引版本
- key:针对哪个字段生成的索引,这里是针对"_id"生成的;1表示升序,-1表示降序
- name:索引名称
2、索引创建
命令:db.集合名.createIndex(<keys>,<options>)
- <keys>指定创建索引的字段
- <options>指定为1表示升序,指定为-1表示降序
2.1 创建一个单键索引
命令:db.members.createIndex({name:1})
> db.members.createIndex({name:1}) #创建单键索引
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
> db.members.getIndexes() #查询索引
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_"
},
{
"v" : 2,
"key" : {
"name" : 1
},
"name" : "name_1" #索引的默认名称为 "键名_升序降序符号(即1或-1)"
}
]
索引的默认名称是索引键和索引中每个键的方向(即1或-1)的连接,使用下划线作为分隔符, 也可以通过指定 name 来自定义索引名称;
db.members.createIndex({name:1},{ name: "whatever u like."});
对于单字段索引和排序操作,索引键的排序顺序(升序或降序)并不重要,因为MongoDB可以从任何方向遍历索引。
2.2 创建一个复合索引
MongoDB支持在多个字段上创建用户定义索引,即 复合索引。
复合索引中列出的字段的顺序具有重要意义。如果一个复合索引由 {name: 1, age: -1} 组成,索引首先按name 升序排序,然后在每个name值内按 age 降序 排序。
db.members.createIndex({name:1,age:-1})
对于复合索引和排序操作,索引键的排序顺序(升序或降序)可以决定索引是否支持排序操作。
2.3 创建多键索引
MongoDB使用多键索引来索引存储在数组中的内容。如果索引包含数组值的字段,MongoDB为数组的每个元素创建单独的索引项。数组字段中的每一个元素,都会在多键索引中创建一个键。
db.members.createIndex({tags:1})
2.4 删除索引
可以根据名称删除:
db.members.dropIndex("索引名称")
演示
> db.members.dropIndex("name_1")
{ "nIndexesWas" : 2, "ok" : 1 }
也可以根据定义删除,比如
创建索引db.members.createIndex({tags:1})
删除索引db.members.dropIndex({tags:1})
> db.members.dropIndex({tags:1})
{ "nIndexesWas" : 2, "ok" : 1 }
如果需要更改某些字段上已经创建的索引,必须首先删除原有索引,再重新创建新索引,否则,新索引不会包含原有文档。
3、索引的效果解析
db.集合名.explain().method(?)
可以使用 explain 进行分析的操作包含 aggregate, count, distinct, find ,group, remove, update.
这里我们需要重点关注winningPlan中stage 的值含义:
- COLLSCAN:整个集合扫描
- IXScan:索引扫描
- FETCH:根据索引指向的文档的地址进行查询(相当于mysql中的回表查询)
- PROJECTION_COVERED:映射覆盖,不需要回表查询
- SORT:需要在内存中排序,效率不高
在查询之前先清空members集合中的所有索引。
然后进行查询{name:“xxx”},即查询键为name,值为xxx的数据
> db.members.explain().find({name:"xxx"}) #查询键为name,值为xxx的数据
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test2.members",
"indexFilterSet" : false,
"parsedQuery" : {
"name" : {
"$eq" : "xxx"
}
},
"queryHash" : "01AEE5EC",
"planCacheKey" : "01AEE5EC",
"winningPlan" : {
"stage" : "COLLSCAN", #整个集合扫描
"filter" : {
"name" : {
"$eq" : "xxx"
}
},
"direction" : "forward"
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "MiWiFi-RA69-srv",
"port" : 27017,
"version" : "4.4.15",
"gitVersion" : "bc17cf2c788c5dda2801a090ea79da5ff7d5fac9"
},
"ok" : 1
}
现在没有添加任何索引,所以我们按照name过滤的时候就需要扫描整个集合。
在来给name字段加一个索引,然后再来查看执行计划:
db.members.createIndex({name:1}) #添加索引
> db.members.createIndex({name:1}) #添加索引
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
> db.members.explain().find({name:"xxx"}) #查询键为name,值为xxx的数据
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test2.members",
"indexFilterSet" : false,
"parsedQuery" : {
"name" : {
"$eq" : "xxx"
}
},
"queryHash" : "01AEE5EC",
"planCacheKey" : "4C5AEA2C",
"winningPlan" : {
"stage" : "FETCH", #根据索引指向的文档的地址进行查询
"inputStage" : {
"stage" : "IXSCAN", #索引扫描
"keyPattern" : {
"name" : 1
},
"indexName" : "name_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"xxx\", \"xxx\"]"
]
}
}
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "MiWiFi-RA69-srv",
"port" : 27017,
"version" : "4.4.15",
"gitVersion" : "bc17cf2c788c5dda2801a090ea79da5ff7d5fac9"
},
"ok" : 1
}
可以看到,现在的winningPlan.stage变成了FETCH,表示根据索引指向的文档的地址进行查询。
此时我们可以再关注一下winningPlan.inputStage中的值:
其中的stage是IXSCAN,表示索引扫描。
其实这里是可以进行优化的。我们可以使用覆盖查询来进行优化。
4、覆盖查询
当查询条件和查询的<投影>只包含索引字段时,MongoDB直接从索引返回结果,而不扫描任何文档或将文档带入内存。这些覆盖的查询可能非常高效。
db.members.explain().find({name:"zhangsan"},{_id:0, name:1})
这时不需要 fetch, 可以直接从索引中获取数据。
> db.members.explain().find({name:"zhangsan"},{_id:0,name:1})
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test2.members",
"indexFilterSet" : false,
"parsedQuery" : {
"name" : {
"$eq" : "zhangsan"
}
},
"queryHash" : "3066FB64",
"planCacheKey" : "A5386A93",
"winningPlan" : {
"stage" : "PROJECTION_COVERED", #映射覆盖,不需要回表查询
"transformBy" : {
"_id" : 0,
"name" : 1
},
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"name" : 1
},
"indexName" : "name_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"zhangsan\", \"zhangsan\"]"
]
}
}
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "MiWiFi-RA69-srv",
"port" : 27017,
"version" : "4.4.15",
"gitVersion" : "bc17cf2c788c5dda2801a090ea79da5ff7d5fac9"
},
"ok" : 1
}
可以看到此时winningPlan.stage是PROJECTION_COVERED,此时MongoDB直接从索引返回结果,而不扫描任何文档或将文档带入内存。
我们在实际的使用中要尽量优化成 PROJECTION_COVERED。
使用已创建索引的字段进行排序,能利用索引的顺序,不需要重新排序,效率高。
db.members.explain().find().sort({name:1,age:-1}) #先按name升序,在同一个name里按age降序
db.members.explain().find().sort({name:1,age:1}) #先按name升序,在同一个name里按age升序
> db.members.explain().find().sort({name:1,age:1})
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test2.members",
"indexFilterSet" : false,
"parsedQuery" : {
},
"queryHash" : "B7EE220C",
"planCacheKey" : "B7EE220C",
"winningPlan" : {
"stage" : "SORT", #此时的winningPlan.stage是SORT.
"sortPattern" : {
"name" : 1,
"age" : 1
},
"memLimit" : 104857600,
"type" : "simple",
"inputStage" : {
"stage" : "COLLSCAN",
"direction" : "forward"
}
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "MiWiFi-RA69-srv",
"port" : 27017,
"version" : "4.4.15",
"gitVersion" : "bc17cf2c788c5dda2801a090ea79da5ff7d5fac9"
},
"ok" : 1
}
此时的winningPlan.stage是SORT。
使用未创建索引的字段进行排序, 因为和创建索引时的顺序不一致,所以需要重新排序,效率低。
如果需要更改某些字段上已经创建的索引,必须首先删除原有索引,再重新创建新索引,否则,新索引不会包含原有文档。
db.集合名.dropIndex()
5、索引的唯一性
索引的unique属性使MongoDB拒绝索引字段的重复值。
除了唯一性约束,唯一索引和MongoDB其他索引功能上是一致的。
db.members.createIndex({age:1},{unique:true})
如果文档中的字段已经出现了重复值,则不可以创建该字段的唯一性索引。
如果新增的文档不具备加了唯一索引的字段,则只有第一个缺失该字段的文档可以被添加,索引中该键值被置为null。
复合键索引也可以具有唯一性,这种情况下,不同的文档之间,其所包含的复合键字段值的组合不可以重复。
6、索引的稀疏性
索引的稀疏属性可确保索引仅包含具有索引字段的文档的条目。
索引会跳过没有索引字段的文档。
可以将稀疏索引与唯一索引结合使用,以防止插入索引字段值重复的文档,并跳过索引缺少索引字段的文档。
准备数据:
db.sparsedemo.insertMany([{name:"xxx",age:19},{name:"zs",age:20}]);
创建 唯一键,稀疏索引:
db.sparsedemo.createIndex({name:1},{unique:true ,sparse:true});
如果同一个索引既具有唯一性,又具有稀疏性,就可以保存多篇缺失索引键值的文档了。
db.sparsedemo.insertOne({name:"zs2w",age:20});
db.sparsedemo.insertOne({name:"zs2w2",age:20});
说明:如果只单纯的 唯一键索引,则 缺失索引键的字段,只能有一个
复合键索引也可以具有稀疏性,在这种情况下,只有在缺失复合键所包含的所有字段的情况下,文档才不会被加入到索引中。
7、索引的生存时间
针对日期字段,或者包含了日期元素的数组字段,可以使用设定了生存时间的索引,来自动删除字段值超过生存时间的文档。
构造数据:
db.members.insertMany(
[
{
name:"zhangsanss",
age:19,
tags:["00","It","SH"],
create_time:new Date()
}
]
)
db.members.createIndex({create_time:1},{expireAfterSeconds:30})
#在create_time字段上面创建了一个生存时间是30s的索引。
在create_time字段上面创建了一个生存时间是30s的索引。
操作演示
> db.members.insertMany([{name:"zhangsanss",age:19,tags:["00","It","SH"],create_time:new Date()}]);
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("62e619a8e2dd6d2542357255")
]
}
> db.members.createIndex({create_time:1},{expireAfterSeconds:30})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 2,
"numIndexesAfter" : 3,
"ok" : 1
}
复合键索引不具备生存时间的特性
当索引键是包含日期元素的数组字段时,数组中最小的日期将被用来计算文档是否已经过期。
数据库使用一个后台线程来监测和删除过期的文档,删除操作可能会有一定的延迟 。
三、总结
索引是个好东西,可以有效的提高查询速度,但是索引会降低插入、更新和删除的速度,因为这些操作不仅要更新文档,还要更新索引,MongoDB
限制每个集合上最多有64
个索引,我们在创建索引时要仔细斟酌索引的字段。
9 Java操作MongoDB
方式一:原生Java操作MongoDB
1、前期准备
引入依赖
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver</artifactId>
<version>3.12.11</version>
</dependency>
2、获取集合
com/chw/mongodb/HelloMongoDB.java
package com.chw.mongodb;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
public class HelloMongoDB {
public static void main(String[] args) {
//首先需要一个MongoClient
MongoClient client = new MongoClient("127.0.0.1", 27017);
//获取数据库,如果数据库存在直接获取,不存在则MongoDB会自动创建
MongoDatabase sang = client.getDatabase("sang");
//获取集合,存在直接获取,不存在则由MongoDB自动创建
MongoCollection<Document> c = sang.getCollection("c1");
}
}
有了集合之后,我们就可以向集合中插入数据了。
3、CRUD操作
3.1 增加操作
和在shell中的操作一样,我们可以一条一条的添加数据,也可以批量添加。
添加单条数据操作如下:
...
MongoCollection<Document> c = sang.getCollection("c1");
...
Document d1 = new Document();
d1.append("name", "三国演义").append("author", "罗贯中");
c.insertOne(d1);
添加多条数据操作如下:
...
MongoCollection<Document> c = sang.getCollection("c1");
...
//添加多条数据
ArrayList<Document> collections = new ArrayList<>();
Document d2 = new Document();
d2.append("name", "红楼梦").append("author", "曹雪芹");
Document d3 = new Document();
d3.append("name", "水浒传").append("author", "施耐庵");
collections.add(d2);
collections.add(d3);
c.insertMany(collections);
3.2 修改操作
可以修改查到的第一条数据,操作如下:
c.updateOne(Filters.eq("author","罗贯中"),new Document("$set",new Document("name","三国演义123")));
//$set表示修改列值
//把author值为罗贯中的那条文档中的name列的值修改为三国演义123,修改查到的第一条数据
上例中小伙伴们也看到了修改器要如何使用,不管是inc,用法都一致,我这里不再一个一个演示。也可以修改查到的所有数据,如下:
c.updateMany(Filters.eq("author","罗贯中"),new Document("$set",new Document("name","三国演义456")));
//$set表示修改列值
//把author值为罗贯中的那条文档中的name列的值修改为三国演义123,修改查到的所有数据
3.3 删除操作
可以删除查到的一条数据,如下:
c.deleteOne(Filters.eq("author","罗贯中"));
也可以删除查到的所有数据:
c.deleteMany(Filters.eq("author","罗贯中"));
Filters里边还有其他的查询条件,都是见名知意,不赘述。
3.4 查询操作
可以直接查询所有文档:
@Test
void test3() {
//首先需要一个MongoClient
MongoClient client = new MongoClient("127.0.0.1", 27017);
//获取数据库,如果数据库存在直接获取,不存在则MongoDB会自动创建
MongoDatabase sang = client.getDatabase("sang");
//获取集合,存在直接获取,不存在则由MongoDB自动创建
MongoCollection<Document> c = sang.getCollection("c1");
FindIterable<Document> documents = c.find();
MongoCursor<Document> iterator = documents.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
也可以按照条件查询:
@Test
void test4() {
//首先需要一个MongoClient
MongoClient client = new MongoClient("127.0.0.1", 27017);
//获取数据库,如果数据库存在直接获取,不存在则MongoDB会自动创建
MongoDatabase sang = client.getDatabase("sang");
//获取集合,存在直接获取,不存在则由MongoDB自动创建
MongoCollection<Document> c = sang.getCollection("c1");
FindIterable<Document> documents = c.find(Filters.eq("author","曹雪芹"));
MongoCursor<Document> iterator = documents.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
其他的方法基本都是见名知意,这里不再赘述。
4、验证问题(MongoDB需要登录的情况)
上面我们演示的获取一个集合是不需要登录MongoDB数据库的,如果需要登录,我们获取集合的方式改为下面这种:
ServerAddress serverAddress = new ServerAddress("192.168.248.128", 27017);
List<MongoCredential> credentialsList = new ArrayList<MongoCredential>();
MongoCredential mc = MongoCredential.createScramSha1Credential("readuser","sang","123".toCharArray());
credentialsList.add(mc);
MongoClient client = new MongoClient(serverAddress,credentialsList);
MongoDatabase sang = client.getDatabase("sang");
c = sang.getCollection("c1");
MongoCredential是一个凭证,第一个参数为用户名,第二个参数是要在哪个数据库中验证,第三个参数是密码的char数组,然后将登录地址封装成一个ServerAddress,最后将两个参数都传入MongoClient中实现登录功能。
5、其他配置(超时时间、等待时间)
在连接数据库的时候也可以设置连接超时等信息,在MongoClientOptions中设置即可,设置方式如下:
ServerAddress serverAddress = new ServerAddress("192.168.248.128", 27017);
List<MongoCredential> credentialsList = new ArrayList<MongoCredential>();
MongoCredential mc = MongoCredential.createScramSha1Credential("rwuser","sang","123".toCharArray());
credentialsList.add(mc);
MongoClientOptions options = MongoClientOptions.builder()
//设置连接超时时间为10s
.connectTimeout(1000*10)
//设置最长等待时间为10s
.maxWaitTime(1000*10)
.build();
MongoClient client = new MongoClient(serverAddress,credentialsList,options);
MongoDatabase sang = client.getDatabase("sang");
c = sang.getCollection("c1");
方式二:SpringBoot操作MongoDB
使用Spring Initializr创建工程,初始依赖选择Spring Web和Spring Data MongoDB。
1、检查MongoDB依赖是否导入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
2、配置yml文件
application.yml
spring:
data:
mongodb:
host: 127.0.0.1
database: studentdb
port: 27017
3、创建实体类
创建包com.chw.springbootmongodb.pojo用于存放实体类,创建实体类
com/chw/springbootmongodb/pojo/Student.java
package com.chw.springbootmongodb.pojo;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.io.Serializable;
@Setter
@Getter
@ToString
@Document(collection = "student")
public class Student implements Serializable {
@Id
private String id;
private String name;
private String sex;
private String age;
private String introduce;
}
4、创建数据访问接口
com/chw/springbootmongodb/dao/StudentDao.java
package com.chw.springbootmongodb.dao;
import com.chw.springbootmongodb.pojo.Student;
import org.bson.types.ObjectId;
import java.util.List;
public interface StudentDao {
void save(Student student);
void update(Student student);
List<Student> findAll();
void delete(String id);
}
5、创建业务逻辑类
com/chw/springbootmongodb/impl/StudentDaoImpl.java
package com.chw.springbootmongodb.impl;
import com.chw.springbootmongodb.dao.StudentDao;
import com.chw.springbootmongodb.pojo.Student;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class StudentDaoImpl implements StudentDao {
@Autowired
private MongoTemplate mongoTemplate;
/**
* 新增信息
* @param student
*/
@Override
public void save(Student student) {
mongoTemplate.save(student);
}
/**
* 修改信息
* @param student
*/
@Override
public void update(Student student) {
//修改的条件
Query query = new Query(Criteria.where("id").is(student.getId()));
//修改的内容
Update update = new Update();
update.set("name", student.getName());
mongoTemplate.updateFirst(query, update, Student.class);
}
/**
* 查询所有信息
* @return
*/
@Override
public List<Student> findAll() {
return mongoTemplate.findAll(Student.class);
}
/**
* 根据id删除信息
* @param id
*/
@Override
public void delete(String id) {
Student delStu = mongoTemplate.findById(id, Student.class);
mongoTemplate.remove(delStu);
}
}
6、测试类测试
test文件夹中
com/chw/springbootmongodb/SpringbootMongodbApplicationTests.java
package com.chw.springbootmongodb.impl;
import com.chw.springbootmongodb.dao.StudentDao;
import com.chw.springbootmongodb.pojo.Student;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class StudentDaoImpl implements StudentDao {
@Autowired
private MongoTemplate mongoTemplate;
/**
* 新增信息
* @param student
*/
@Override
public void save(Student student) {
mongoTemplate.save(student);
}
/**
* 修改信息
* @param student
*/
@Override
public void update(Student student) {
//修改的条件
Query query = new Query(Criteria.where("id").is(student.getId()));
//修改的内容
Update update = new Update();
update.set("name", student.getName());
mongoTemplate.updateFirst(query, update, Student.class);
}
/**
* 查询所有信息
* @return
*/
@Override
public List<Student> findAll() {
return mongoTemplate.findAll(Student.class);
}
/**
* 根据id删除信息
* @param id
*/
@Override
public void delete(String id) {
Student delStu = mongoTemplate.findById(id, Student.class);
mongoTemplate.remove(delStu);
}
}
10 MongoDB 之副本集配置
一、MongoDB主从复制
主从复制是MongoDB最早使用的复制方式, 该复制方式易于配置,并且可以支持任意数量的从节点服务器,与使用单节点模式相比有如下优点:
在从服务器上存储数据副本,提高了数据的可用性, 并可以保证数据的安全性。
可配置读写分离,主节点负责写操作,从节点负责读操作,将读写压力分开,提高系统的稳定性。

MongoDB的主从复制至少需要两个服务器或者节点。其中一个是主节点,负责处理客户端请求,其它的都是从节点,负责同步主节点的数据。
主节点记录在其上执行的所有写操作,从节点定期轮询主节点获取这些操作,然后再对自己的数据副本执行这些操作。由于和主节点执行了相同的操作,从节点就能保持与主节点的数据同步。
主节点的操作记录称为oplog(operation log),它被存储在MongoDB 的 local数据库中。oplog 中的每个文档都代表主节点上执行的一个操作。需要重点强调的是oplog只记录改变数据库状态的操作。比如,查询操作就不会被存储在oplog中。这是因为oplog只是作为从节点与主节点保持数据同步的机制。
然而,主从复制并非生产环境下推荐的复制方式,主要原因如下两点:
1、灾备都是完全人工的 如果主节点发生故障失败,管理员必须关闭一个从服务器,然后作为主节点重新启动它。然后应用程序必须重新配置连接新的主节点。
2、数据恢复困难 因为oplog只在主节点存在,故障失败需要在新的服务器上创建新的oplog,这意味着任意存在的节点需要重新从新的主节点同步oplog。
因此,在新版本的MongoDB中已经不再支持使用主从复制这种复制方式了,取而代之的是使用副本集复制方式。
二、MongoDB副本集
1、介绍
MongoDB副本集(Replica Set)其实就是具有自动故障恢复功能的主从集群,和主从复制最大的区别就是在副本集中没有固定的“主节点;整个副本集会选出一个节点作为“主节点”,当其挂掉后,再在剩下的从节点中选举一个节点成为新的“主节点”,在副本集中总有一个主节点(primary)和一个或多个备份节点(secondary)。
除了primary和secondary之外,副本集中的节点还可以是以下角色:
成为primary | 对客户端可见 | 参与投票 | 延迟同步 | 复制数据 | |
---|---|---|---|---|---|
Default | 支持 | 支持 | 支持 | / | 支持 |
Secondary-Only | / | 支持 | 支持 | / | 支持 |
Hidden | / | / | 支持 | / | 支持 |
Delayed | 支持 | 支持 | 支持 | 支持 | / |
Arbiters | / | / | 支持 | / | / |
Non-Voting | 支持 | 支持 | / | / | 支持 |
官方推荐的副本集最小配置需要有三个节点:一个主节点接收和处理所有的写操作,两个备份节点通过复制主节点的操作来对主节点的数据进行同步备份。

2、配置副本集
2.1 环境准备
副本集各节点IP如下:
172.16.250.234
172.16.250.239
172.16.250.240
首先,先对三个MongoDB节点进行安装
然后,依次修改个节点的mongo.conf配置文件,增加副本集相关配置,内容如下:
mongo.conf文件位置
vi /usr/local/mongo/conf/mongo.conf #linux的
#数据保存路径
dbpath=/usr/local/mongo/data/db/
#日志保存路径
logpath=/usr/local/mongo/logs/mongo.log
#开启子进程
fork=true
#日志追加写入
logappend=true
bind_ip= #此处填写服务器的IP
#mongo默认端口
port=27017
#设置副本集名称,在各个配置文件中,其值必须相同
replSet=rs0
配置完成之后,分别在三个节点上执行如下命令通过加载文件配置来启动MongoDB服务:
mongod -config /usr/local/mongo/conf/mongo.conf
#或者
/usr/local/mongo/bin/mongod -f /usr/local/mongo/conf/mongo.conf
#这两种方式都可以启动
至此,3个MongoDB实例都已经以副本集方式启动,但它们彼此之间现在还不会进行通信,仍需要进行一些配置。
2.2 副本集初始化
通过Shell连接到任意一个MongoDB实例,执行rs.initiate()方法对副本集进行初始化。
[root@hadoop34 mongodb-4.0.2]# mongo 172.16.250.234:27017
> conf= #设置一个conf配置,稍后传给rs.initiate()进行初始化
{
"_id" : "rs0",
"members" : [
{ "_id" : 0, "host" : "172.16.250.234:27017" },
{ "_id" : 1, "host" : "172.16.250.239:27017" },
{ "_id" : 2, "host" : "172.16.250.240:27017" }
]
}
> rs.initiate(conf)
{
"ok" : 1,
"operationTime" : Timestamp(1542247326, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1542247326, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
rs0:SECONDARY>
如果在执行rs.initiate()方法时不传入任何参数,MongoDB将以默认的配置文档对副本集进行初始化,后续可以再通过rs.add()方法来向副本集中添加成员。
2.3 副本集更新
添加删除
# 向副本集中添加成员
rs.add("172.16.250.240:27017")
# 从副本集中删除成员
rs.remove("172.16.250.240:27017")
# 向副本集中添加仲裁
rs.addArb("172.16.250.240:27017")
# 向副本集中添加备份节点
rs.add({"_id":3,"host":"172.16.250.240:27017","priority":0,"hidden":true})
更改副本集配置
# 更改副本集配置
rs0:PRIMARY> var conf=rs.conf()
rs0:PRIMARY> conf.members[1].priority = 5
# PRIMARY节点上执行如下命令
rs0:PRIMARY> rs.reconfig(conf)
{
"ok" : 1,
"operationTime" : Timestamp(1542248518, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1542248518, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
# SECONDARY节点上执行如下命令,需增加 force 参数
rs0:SECONDARY> rs.reconfig(conf,{force:true})
{
"ok" : 1,
"operationTime" : Timestamp(1542248726, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1542248726, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
例如,强制让一个节点成为Primary,可以将该节点的优先级设置成最高。
cfg = rs.conf()
cfg.members[0].priority = 5
cfg.members[1].priority = 1
cfg.members[2].priority = 1
rs.reconfig(cfg)
2.4 副本集监控
查看副本集配置信息rs.conf()
# 查看副本集的配置信息
rs0:PRIMARY> rs.conf()
{
"_id" : "rs0",
"version" : 104658,
"protocolVersion" : NumberLong(1),
"writeConcernMajorityJournalDefault" : true,
"members" : [
{
"_id" : 0,
"host" : "172.16.250.234:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 1,
"host" : "172.16.250.239:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 5,
"tags" : {},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 2,
"host" : "172.16.250.240:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {},
"slaveDelay" : NumberLong(0),
"votes" : 1
}],
"settings" : {
"chainingAllowed" : true,
"heartbeatIntervalMillis" : 2000,
"heartbeatTimeoutSecs" : 10,
"electionTimeoutMillis" : 10000,
"catchUpTimeoutMillis" : -1,
"catchUpTakeoverDelayMillis" : 30000,
"getLastErrorModes" : {},
"getLastErrorDefaults" : {
"w" : 1,
"wtimeout" : 0
},
"replicaSetId" : ObjectId("5becd39e360189766762e057")
}
}
查看副本集运行状态rs.status()
rs0:PRIMARY> rs.status()
{
"set" : "rs0",
"date" : ISODate("2018-11-15T02:46:15.138Z"),
"myState" : 1,
"term" : NumberLong(2),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1542249966, 1),
"t" : NumberLong(2)
},
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1542249966, 1),
"t" : NumberLong(2)
},
"appliedOpTime" : {
"ts" : Timestamp(1542249966, 1),
"t" : NumberLong(2)
},
"durableOpTime" : {
"ts" : Timestamp(1542249966, 1),
"t" : NumberLong(2)
}
},
"lastStableCheckpointTimestamp" : Timestamp(1542249916, 1),
"members" : [
{
"_id" : 0,
"name" : "172.16.250.234:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 2651,
"optime" : {
"ts" : Timestamp(1542249966, 1),
"t" : NumberLong(2)
},
"optimeDurable" : {
"ts" : Timestamp(1542249966, 1),
"t" : NumberLong(2)
},
"optimeDate" : ISODate("2018-11-15T02:46:06Z"),
"optimeDurableDate" : ISODate("2018-11-15T02:46:06Z"),
"lastHeartbeat" : ISODate("2018-11-15T02:46:13.520Z"),
"lastHeartbeatRecv" : ISODate("2018-11-15T02:46:13.519Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "172.16.250.239:27017",
"syncSourceHost" : "172.16.250.239:27017",
"syncSourceId" : 1,
"infoMessage" : "",
"configVersion" : 104658
},
{
"_id" : 1,
"name" : "172.16.250.239:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 2799,
"optime" : {
"ts" : Timestamp(1542249966, 1),
"t" : NumberLong(2)
},
"optimeDate" : ISODate("2018-11-15T02:46:06Z"),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1542248524, 1),
"electionDate" : ISODate("2018-11-15T02:22:04Z"),
"configVersion" : 104658,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 2,
"name" : "172.16.250.240:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 1855,
"optime" : {
"ts" : Timestamp(1542249966, 1),
"t" : NumberLong(2)
},
"optimeDurable" : {
"ts" : Timestamp(1542249966, 1),
"t" : NumberLong(2)
},
"optimeDate" : ISODate("2018-11-15T02:46:06Z"),
"optimeDurableDate" : ISODate("2018-11-15T02:46:06Z"),
"lastHeartbeat" : ISODate("2018-11-15T02:46:13.520Z"),
"lastHeartbeatRecv" : ISODate("2018-11-15T02:46:13.520Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "172.16.250.239:27017",
"syncSourceHost" : "172.16.250.239:27017",
"syncSourceId" : 1,
"infoMessage" : "",
"configVersion" : 104658
}],
"ok" : 1,
"operationTime" : Timestamp(1542249966, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1542249966, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
查看备份节点的复制信息
# 查看备份节点的复制信息
rs0:PRIMARY> db.printSlaveReplicationInfo()
source: 172.16.250.234:27017
syncedTo: Thu Nov 15 2018 11:08:36 GMT+0800 (CST)
0 secs (0 hrs) behind the primary
source: 172.16.250.240:27017
syncedTo: Thu Jan 01 1970 08:00:00 GMT+0800 (CST)
1542251316 secs (428403.14 hrs) behind the primary
3、副本集测试
3.1 数据同步测试
在Primary上插入一万条客户数据:
rs0:PRIMARY> for(var i=0;i<10000;i++){db.customer.insert({"name":"user"+i})}
WriteResult({ "nInserted" : 1 })
rs0:PRIMARY> db.customer.count()
10000
在Secondary上查看客户数据是否已经同步:
rs0:SECONDARY> rs.slaveOk()
rs0:SECONDARY> db.customer.count()
10000
3.2 故障转移测试
执行如下命令关闭Primary节点,查看其他2个节点的情况:
mongod --shutdown --dbpath /usr/local/mongo/data
查看Primary节点关闭之前的状态
# 查看Primary节点关闭之前的状态
rs0:PRIMARY> rs.status()
{
"set" : "rs0",
"date" : ISODate("2018-11-15T03:36:31.393Z"),
"myState" : 1,
"term" : NumberLong(4),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1542252988, 1),
"t" : NumberLong(4)
},
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1542252988, 1),
"t" : NumberLong(4)
},
"appliedOpTime" : {
"ts" : Timestamp(1542252988, 1),
"t" : NumberLong(4)
},
"durableOpTime" : {
"ts" : Timestamp(1542252988, 1),
"t" : NumberLong(4)
}
},
"lastStableCheckpointTimestamp" : Timestamp(1542252978, 1),
"members" : [
{
"_id" : 0,
"name" : "172.16.250.234:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 425,
"optime" : {
"ts" : Timestamp(1542252988, 1),
"t" : NumberLong(4)
},
"optimeDurable" : {
"ts" : Timestamp(1542252988, 1),
"t" : NumberLong(4)
},
"optimeDate" : ISODate("2018-11-15T03:36:28Z"),
"optimeDurableDate" : ISODate("2018-11-15T03:36:28Z"),
"lastHeartbeat" : ISODate("2018-11-15T03:36:31.243Z"),
"lastHeartbeatRecv" : ISODate("2018-11-15T03:36:30.233Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "172.16.250.239:27017",
"syncSourceHost" : "172.16.250.239:27017",
"syncSourceId" : 1,
"infoMessage" : "",
"configVersion" : 104666
},
{
"_id" : 1,
"name" : "172.16.250.239:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 428,
"optime" : {
"ts" : Timestamp(1542252988, 1),
"t" : NumberLong(4)
},
"optimeDate" : ISODate("2018-11-15T03:36:28Z"),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1542252577, 2),
"electionDate" : ISODate("2018-11-15T03:29:37Z"),
"configVersion" : 104666,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 2,
"name" : "172.16.250.240:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 78,
"optime" : {
"ts" : Timestamp(1542252988, 1),
"t" : NumberLong(4)
},
"optimeDurable" : {
"ts" : Timestamp(1542252988, 1),
"t" : NumberLong(4)
},
"optimeDate" : ISODate("2018-11-15T03:36:28Z"),
"optimeDurableDate" : ISODate("2018-11-15T03:36:28Z"),
"lastHeartbeat" : ISODate("2018-11-15T03:36:31.376Z"),
"lastHeartbeatRecv" : ISODate("2018-11-15T03:36:29.597Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "172.16.250.239:27017",
"syncSourceHost" : "172.16.250.239:27017",
"syncSourceId" : 1,
"infoMessage" : "",
"configVersion" : 104666
}],
"ok" : 1,
"operationTime" : Timestamp(1542252988, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1542252988, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
在任意其他节点上查看Primary节点关闭之后的状态
# 在任意其他节点上查看Primary节点关闭之后的状态
> rs.status()
{
"set" : "rs0",
"date" : ISODate("2018-11-15T03:41:31.213Z"),
"myState" : 1,
"term" : NumberLong(5),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1542253290, 1),
"t" : NumberLong(5)
},
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1542253290, 1),
"t" : NumberLong(5)
},
"appliedOpTime" : {
"ts" : Timestamp(1542253290, 1),
"t" : NumberLong(5)
},
"durableOpTime" : {
"ts" : Timestamp(1542253290, 1),
"t" : NumberLong(5)
}
},
"lastStableCheckpointTimestamp" : Timestamp(1542253268, 1),
"members" : [
{
"_id" : 0,
"name" : "172.16.250.234:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 6115,
"optime" : {
"ts" : Timestamp(1542253290, 1),
"t" : NumberLong(5)
},
"optimeDate" : ISODate("2018-11-15T03:41:30Z"),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "could not find member to sync from",
"electionTime" : Timestamp(1542253288, 1),
"electionDate" : ISODate("2018-11-15T03:41:28Z"),
"configVersion" : 104666,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 1,
"name" : "172.16.250.239:27017",
"health" : 0,
"state" : 8,
"stateStr" : "(not reachable/healthy)",
"uptime" : 0,
"optime" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"optimeDurable" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"optimeDate" : ISODate("1970-01-01T00:00:00Z"),
"optimeDurableDate" : ISODate("1970-01-01T00:00:00Z"),
"lastHeartbeat" : ISODate("2018-11-15T03:41:30.593Z"),
"lastHeartbeatRecv" : ISODate("2018-11-15T03:41:18.148Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "Error connecting to 172.16.250.239:27017 :: caused by :: Connection refused",
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"configVersion" : -1
},
{
"_id" : 2,
"name" : "172.16.250.240:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 372,
"optime" : {
"ts" : Timestamp(1542253268, 1),
"t" : NumberLong(4)
},
"optimeDurable" : {
"ts" : Timestamp(1542253268, 1),
"t" : NumberLong(4)
},
"optimeDate" : ISODate("2018-11-15T03:41:08Z"),
"optimeDurableDate" : ISODate("2018-11-15T03:41:08Z"),
"lastHeartbeat" : ISODate("2018-11-15T03:41:30.591Z"),
"lastHeartbeatRecv" : ISODate("2018-11-15T03:41:31.106Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"configVersion" : 104666
}],
"ok" : 1,
"operationTime" : Timestamp(1542253290, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1542253290, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
再次启动172.16.250.239:27017
节点
再次启动172.16.250.239:27017节点,由于其选举优先级最高,自动被选举为Primary。
# 待172.16.250.239:27017 节点启动后再次查看副本集状态
> rs.status()
{
"set" : "rs0",
"date" : ISODate("2018-11-15T03:44:01.745Z"),
"myState" : 2,
"term" : NumberLong(6),
"syncingTo" : "172.16.250.239:27017",
"syncSourceHost" : "172.16.250.239:27017",
"syncSourceId" : 1,
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1542253435, 1),
"t" : NumberLong(6)
},
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1542253435, 1),
"t" : NumberLong(6)
},
"appliedOpTime" : {
"ts" : Timestamp(1542253435, 1),
"t" : NumberLong(6)
},
"durableOpTime" : {
"ts" : Timestamp(1542253435, 1),
"t" : NumberLong(6)
}
},
"lastStableCheckpointTimestamp" : Timestamp(1542253400, 1),
"members" : [
{
"_id" : 0,
"name" : "172.16.250.234:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 6265,
"optime" : {
"ts" : Timestamp(1542253435, 1),
"t" : NumberLong(6)
},
"optimeDate" : ISODate("2018-11-15T03:43:55Z"),
"syncingTo" : "172.16.250.239:27017",
"syncSourceHost" : "172.16.250.239:27017",
"syncSourceId" : 1,
"infoMessage" : "",
"configVersion" : 104666,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 1,
"name" : "172.16.250.239:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 23,
"optime" : {
"ts" : Timestamp(1542253435, 1),
"t" : NumberLong(6)
},
"optimeDurable" : {
"ts" : Timestamp(1542253435, 1),
"t" : NumberLong(6)
},
"optimeDate" : ISODate("2018-11-15T03:43:55Z"),
"optimeDurableDate" : ISODate("2018-11-15T03:43:55Z"),
"lastHeartbeat" : ISODate("2018-11-15T03:44:01.228Z"),
"lastHeartbeatRecv" : ISODate("2018-11-15T03:44:00.835Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1542253424, 1),
"electionDate" : ISODate("2018-11-15T03:43:44Z"),
"configVersion" : 104666
},
{
"_id" : 2,
"name" : "172.16.250.240:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 522,
"optime" : {
"ts" : Timestamp(1542253435, 1),
"t" : NumberLong(6)
},
"optimeDurable" : {
"ts" : Timestamp(1542253435, 1),
"t" : NumberLong(6)
},
"optimeDate" : ISODate("2018-11-15T03:43:55Z"),
"optimeDurableDate" : ISODate("2018-11-15T03:43:55Z"),
"lastHeartbeat" : ISODate("2018-11-15T03:44:01.166Z"),
"lastHeartbeatRecv" : ISODate("2018-11-15T03:44:01.414Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "172.16.250.239:27017",
"syncSourceHost" : "172.16.250.239:27017",
"syncSourceId" : 1,
"infoMessage" : "",
"configVersion" : 104666
}
],
"ok" : 1,
"operationTime" : Timestamp(1542253435, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1542253435, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
4、开启安全认证
4.1 创建用户
登录 PRIMARY节点创建用户,在此我们对 test 库开启安全认证。
rs0:PRIMARY> show dbs
admin 0.000GB
config 0.000GB
local 0.002GB
test 0.000GB
rs0:PRIMARY> use admin
switched to db admin
rs0:PRIMARY> db.createUser({user:"root",pwd:"123456",roles:[{role:"userAdminAnyDatabase",db:"admin"}]})
Successfully added user: {
"user" : "root",
"roles" : [
{
"role" : "userAdminAnyDatabase",
"db" : "admin"
}
]
}
rs0:PRIMARY> use test
switched to db test
rs0:PRIMARY> db.createUser({user:"admin",pwd:"admin",roles:[{role:"readWrite",db:"test"}]})
Successfully added user: {
"user" : "admin",
"roles" : [
{
"role" : "readWrite",
"db" : "test"
}
]
}
4.2 创建keyFile文件
先停掉所有SECONDARY节点的MongoDB服务,然后再停掉PRIMARY节点的MongoDB服务,并在PRIMARY节点所在服务器上创建keyFile文件。
[root@MiWiFi-RA69-srv bin]# openssl rand -base64 666 > /usr/local/mongo/keyfile
[root@MiWiFi-RA69-srv bin]# chmod 600 /usr/local/mongo/keyfile
将生成的keyFile文件拷贝到其他节点服务器上,并修改文件的操作权限为600。
chmod 600 /usr/local/mongo/keyfile
更新启动配置文件
修改PRIMARY节点的 mongo.conf文件,增加如下内容:
# Add below Config
auth=true
oplogSize=100
keyFile=/usr/local/mongo/keyfile
修改SECONDARY节点的 mongo.conf文件,增加如下内容:
# Add below Config
oplogSize=100
keyFile=/usr/local/mongo/keyfile
4.3 启动副本集
先以–auth 方式启动PRIMARY节点:
[root@MiWiFi-RA69-srv bin]# mongod -f /usr/local/mongo/conf/mongo.conf
再启动SECONDARY节点:
mongod -f /usr/local/mongo/mongo/mongo.conf
4.4 登录测试
[root@MiWiFi-RA69-srv bin]# mongo -uadmin -padmin 172.16.250.239:27017
MongoDB shell version v4.4.15
connecting to: mongodb://172.16.250.239:27017/test
MongoDB server version: 4.4.15
rs0:PRIMARY> show dbs;
test 0.000GB
: {
“ts” : Timestamp(1542253435, 1),
“t” : NumberLong(6)
},
“optimeDurable” : {
“ts” : Timestamp(1542253435, 1),
“t” : NumberLong(6)
},
“optimeDate” : ISODate(“2018-11-15T03:43:55Z”),
“optimeDurableDate” : ISODate(“2018-11-15T03:43:55Z”),
“lastHeartbeat” : ISODate(“2018-11-15T03:44:01.166Z”),
“lastHeartbeatRecv” : ISODate(“2018-11-15T03:44:01.414Z”),
“pingMs” : NumberLong(0),
“lastHeartbeatMessage” : “”,
“syncingTo” : “172.16.250.239:27017”,
“syncSourceHost” : “172.16.250.239:27017”,
“syncSourceId” : 1,
“infoMessage” : “”,
“configVersion” : 104666
}
],
“ok” : 1,
“operationTime” : Timestamp(1542253435, 1),
“$clusterTime” : {
“clusterTime” : Timestamp(1542253435, 1),
“signature” : {
“hash” : BinData(0,“AAAAAAAAAAAAAAAAAAAAAAAAAAA=”),
“keyId” : NumberLong(0)
}
}
}
### 4、开启安全认证
#### 4.1 创建用户
登录 PRIMARY节点创建用户,在此我们对 test 库开启安全认证。
```cmd
rs0:PRIMARY> show dbs
admin 0.000GB
config 0.000GB
local 0.002GB
test 0.000GB
rs0:PRIMARY> use admin
switched to db admin
rs0:PRIMARY> db.createUser({user:"root",pwd:"123456",roles:[{role:"userAdminAnyDatabase",db:"admin"}]})
Successfully added user: {
"user" : "root",
"roles" : [
{
"role" : "userAdminAnyDatabase",
"db" : "admin"
}
]
}
rs0:PRIMARY> use test
switched to db test
rs0:PRIMARY> db.createUser({user:"admin",pwd:"admin",roles:[{role:"readWrite",db:"test"}]})
Successfully added user: {
"user" : "admin",
"roles" : [
{
"role" : "readWrite",
"db" : "test"
}
]
}
4.2 创建keyFile文件
先停掉所有SECONDARY节点的MongoDB服务,然后再停掉PRIMARY节点的MongoDB服务,并在PRIMARY节点所在服务器上创建keyFile文件。
[root@MiWiFi-RA69-srv bin]# openssl rand -base64 666 > /usr/local/mongo/keyfile
[root@MiWiFi-RA69-srv bin]# chmod 600 /usr/local/mongo/keyfile
将生成的keyFile文件拷贝到其他节点服务器上,并修改文件的操作权限为600。
chmod 600 /usr/local/mongo/keyfile
更新启动配置文件
修改PRIMARY节点的 mongo.conf文件,增加如下内容:
# Add below Config
auth=true
oplogSize=100
keyFile=/usr/local/mongo/keyfile
修改SECONDARY节点的 mongo.conf文件,增加如下内容:
# Add below Config
oplogSize=100
keyFile=/usr/local/mongo/keyfile
4.3 启动副本集
先以–auth 方式启动PRIMARY节点:
[root@MiWiFi-RA69-srv bin]# mongod -f /usr/local/mongo/conf/mongo.conf
再启动SECONDARY节点:
mongod -f /usr/local/mongo/mongo/mongo.conf
4.4 登录测试
[root@MiWiFi-RA69-srv bin]# mongo -uadmin -padmin 172.16.250.239:27017
MongoDB shell version v4.4.15
connecting to: mongodb://172.16.250.239:27017/test
MongoDB server version: 4.4.15
rs0:PRIMARY> show dbs;
test 0.000GB
admin用户只能看到test库。因为4.1中创建用户时在test库中创建了admin用户。