文章目录
- 1-1 初识MongoDB
- 1-2 MongoDB 数据库基本操作
- 2-1 MongoDB数据库安全
- 3-1 MongoDB 之聚合函数查询统计
- 3-2 MongoDB 之滴滴、摩拜都在用的索引
- 3-3 MongoDB 文档的高级查询操作
- 4-1 MongoDB 实验——java 和 MongoDB
- 5-1 MongoDB 实验——数据库优化
- 6-1 MongoDB实验——数据备份和恢复
- 7-1 MongoDB 复制集 &分片
1-1 初识MongoDB
1.1 DOS(Windows)端启动 MongoDB 服务
1.1.1 配置环境变量
在电脑->属性->环境变量->Path中添加 MongoDB 安装路径(根据自己的安装路径添加),如图所示
1.1.2 启动服务并进行相关配置
mongod.exe --bind_ip 127.0.0.1 --logpath D:\MongoDB\dblog\mongodb.log --logappend --dbpath D:\MongoDB\db --port 27017 --service
- bind_ip :绑定服务 IP,绑定127.0.0.1,则只能本机访问,不指定默认本地所有 IP ;
- logpath :配置日志存放的位置;
- logappend :日志使用追加的方式;
- dbpath :配置数据存放的位置;
- port :配置端口号,27017是默认端口;
- service :以服务方式启动,即后台启动。
1.2 Linux 端启动 MongoDB 服务
1.2.1 数据存放位置
在 /data 路径下创建文件夹 db_test 来存放 MongoDB 服务的数据。
cd /data # 进入data路径
mkdir db_test # 创建db文件夹
1.2.2 日志文件
在 /logs 路径下创建文件夹 mongo 存放日志文件 mongod.log(文件不用创建,到时候会自动生成,但路径即文件夹必须提前创建好)。
mkdir /logs # 创建/logs路径
cd /logs # 进入log路径
mkdir mongo # 创建mongo文件夹
1.2.3配置文件
在 /etc/mongod(没有路径就创建)路径下新建配置文件 mongod.conf,使用配置文件启动 MongoDB 服务(把命令写入配置文件,以后启动服务就不用再输入一长串的命令,直接启动配置文件即可)。
mkdir /etc/mongod # 创建/etc/mongod路径
cd /etc/mongod # 进入mongod路径
vim mongod.conf # 进入vim编辑器编辑mongod.conf文件
\# vim编辑器内容
\# 按 "i" 键切换到输入模式
\# 输入配置内容:
port=27018 # 配置端口号
dbpath=/data/db_test # 配置数据存放的位置
logpath=/logs/mongo/mongod.log # 配置日志存放的位置
logappend=true # 日志使用追加的方式
fork=true # 设置在后台运行
\# 按 "ESC" 键退出输入模式切换到命令行模式
\# 输入 ":wq" 并按 "Enter"键 ,保存文件并退出 Vim 编辑器
以上工作准备完成,便可以开启服务了,配置文件启动命令如下(在命令行中输入):
mongod -f /etc/mongod/mongod.conf
1.3 启动客户端
输入命令 mongo --port 27017
连接客户端(MongoDB 默认的连接方式无需输入用户名和密码,port 端口根据你服务启动的端口连接)
如图出现最后一行的>符号,说明连接成功。
1.4 退出客户端
在客户端输入 exit
便可顺利退出,如图所示:
1.5 关闭 MongoDB 服务
1.5.1 能连接到客户端时
use admin #使用系统数据库admin,只有在admin数据库中才可以进行关闭服务的操作
db.shutdownServer() #关闭服务
显示如下图所示,则说明服务已成功关闭:
1.5.2 客户端无法连接时
有时候数据库服务异常关闭时,我们连不上客户端,就可以使用该方法。
查看 Mongo 相关进程 ps -ef | grep mongo
,如图所示:
kill
mongod 开头的进程如图所示:
1-2 MongoDB 数据库基本操作
2.1 数据库创建
2.1.1 连接数据库
-
MongoDB 安装完成后,可以通过
pgrep mongo -l
命令来查看是否已经启动。如图所示:
-
在操作数据库之前,需要连接它,连接本地数据库服务器,输入命令:
mongo
,预期输出如图所示:
其中连接的警告可以不用管,只要最下方出现“ > ”符号,就说明连接成功。
2.1.2 创建数据库
-
创建一个名为 Testdb 的数据库,用命令:
use Testdb
语句来创建(如果数据库不存在,则创建数据库,否则切换到指定数据库),如图所示:
-
查看所有数据库我们可以通过
show dbs
命令来查看,如图所示:
-
然而并没有我们刚创建的 Testdb 数据库。要想显示它,我们需要向数据库插入一些数据:
db.Testdb.insert({_id:1,name:"王小明"})
,如图所示:
现在,就可以看到我们创建的数据库 Testdb 了。
2.1.3 删除数据库
-
MongoDB 删除数据库需要先切换到该数据库中:
use Testdb
-
然后再执行删除命令:
db.dropDatabase()
2.2 创建集合
2.2.1 在指定的数据库创建集合
-
先进入指定数据库
Testdb :use Testdb;
-
在 Testdb 数据库中创建创建固定集合 test ,整个集合空间大小512000KB,文档最大个数为1000个:
db.createCollection("test", { capped : true, autoIndexId : true, size : 512000, max : 1000 } )
-
capped :是一个布尔类型,true 时创建固定集合,必须指定 size。固定集合指有固定大小的集合,当达到最大值时,它会自动覆盖最早的文档。默认为 false;
-
autoIndexId :也是一个布尔类型,如为 true,自动在_id 字段创建索引。默认为 false ;
-
size :为固定集合指定一个最大值(以字节 KB 计);
-
max :指定固定集合中包含文档的最大数量。
-
不过,和 MySQL 不同的是,在 MongoDB 中,你不一定需要先创建集合。当你插入一些文档时,MongoDB 会自动创建集合。
2.2.2 默认id
MongoDB 中存储的文档 必须 有一个 _id 键(如果我们插入数据的时候未指定 _id ,系统会自动生成一个默认的 id )。
这个键的值可以是任何类型的,默认是个 ObjectId 对象。
在一个集合里面,每个文档都有唯一的 _id 值,来确保集合里面每个文档都能被唯一标识。
如果有两个集合的话,两个集合可以都有一个值为123的 _id 键,但是每个集合里面只能有一个 _id 是123的文档。
2.2.3 查询集合
命令:db.集合名.find()
由此我们也能看到 MongoDB 默认创建的_id:
2.2.4 删除集合
命令:db.集合名.drop()
如图:
2.3 文档操作
2.3.1 插入文档
命令:db.集合名.insert(文档)
-
该命令前文我们也有用到。除了前两关的用法以外,我们还可以将数据定义为一个变量:
document=({_id:1, name: '王小明', sex: '男', hobbies: ['乒乓球','羽毛球'], birthday: '1996-02-14' });
-
然后执行插入操作:
db.person.insert(document)
person 是集合名,如果该集合不在该数据库中,MongoDB 会自动创建该集合并插入文档,查看 person 集合,如图所示:
2.3.2 更新文档
2.3.2.1 update()
方法
<b>更新变量中单个键的值</b>
-
先把 document 插入到集合 person2:
db.person2.insert(document)
-
用 update() 方法来更新 person2 的数据,把王小明的出生日期替换成1996,命令如下:
db.person2.update({birthday:"1996-02-14"},{$set:{birthday:"1996"}})
$set
是一个更新操作符,用于设置指定字段的值。 -
更新后如图2所示( pretty() 方法的作用是使文档整齐的输出):
可以发现:
- update() 有两个参数,都是对象,中间用逗号“ ,”间隔;
- 第一个参数表示需要修改的值;
- 第二个参数用 $set 操作符指向更新后的值。
2.3.2.2 save()
方法
<b>替换集合中的单个变量</b>
-
我们先把 document 插入到集合 person2:
db.person3.insert(document)
-
用 save() 方法把王小明的数据修改为李小红的数据:
db.person3.save({ "_id" :1, "name" : "李小红", "sex" : "女", "hobbies" : [ "画画", "唱歌", "跳舞" ], "birthday" : "1996-06-14" })
注:如果 save() 法也指定了_id,则对文档进行更新;未指定_id则会执行插入功能,MongoDB 默认自动生成一个不重复的_id。
- update() 方法仅适用于修改某条数据中的某个键值;
- save() 方法适用于修改整条数据。
2.3.2 查询文档
-
先插入文档到集合 stu1 :
document=([{ name:'张小华', sex:'男', age:20, phone:'12356986594', hobbies:['打篮球','踢足球','唱歌'] },{ name:'李小红', sex:'女', age:18, phone:'12355487536', hobbies:['跳舞','唱歌'] }]) db.stu1.insert(document)
-
然后查看 stu1 ,使用命令
find()
,结果如图所示(因为我们没有设置_id,所以 MongoDB 会默认生成):
-
也可以使用
pretty()
方法,使输出更整齐,如图所示:
2.3.2.1 条件查询
操作 | 格式 | 范例 | 关系数据库中类似的语句 |
---|---|---|---|
等于 | {<key>:<value>} | db.stu1.find({"name":"李小红"}).pretty() | where name = '李小红' |
小于 | {<key>:{$lt:<value>}} | db.stu1.find({"age":{$lt:18}}).pretty() | where age < 18 |
小于或等于 | {<key>:{$lte:<value>}} | db.stu1.find({"age":{$lte:18}}).pretty() | where age <= 18 |
大于 | {<key>:{$gt:<value>}} | db.stu1.find({"age":{$gt:18}}).pretty() | where age > 18 |
大于或等于 | {<key>:{$gte:<value>}} | db.stu1.find({"age":{$gte:18}}).pretty() | where age >= 18 |
不等于 | {<key>:{$ne:<value>}} | db.stu1.find({"age":{$ne:18}}).pretty() | where age != 18 |
- 在我们来查找一下 age 大于18岁的数据,命令和效果如图所示:
2.3.2.2 AND 条件
find() 方法可以传入多个键 (key),每个键 (key) 以逗号隔开,即常规 SQL 的 AND 条件。
-
如查询集合 stu1 中年龄为20岁的男性信息:
db.stu1.find({"age":20, "sex":"男"}).pretty()
查询结果如图所示:
2.3.3 删除文档数据
2.3.3.1 删除指定的数据
db.stu1.remove({'age':20}) //删除年龄为20的数据
- 查看该集合的内容,如果只剩下如图所示的信息,说明数据删除成功:
2.3.3.2 删除全部数据(集合并不会删除)
db.remove({})
- 查看该集合的内容,如果如图所示,无显示或者显示为空,说明数据全部删除:
2-1 MongoDB数据库安全
1.1 创建管理员用户
1.1.2 创建管理员用户
-
切换到admin数据库(admin数据库是一个具有特殊权限的数据库,用户需要访问它以便执行某些管理命令)
use admin
在数据库admin中,创建管理员用户 abc ,密码为 123 ,拥有 root 权限。
db.createUser({user:"abc",pwd:"123",roles:[{role:"root",db:"admin"}]})
结果如下图说明添加管理员用户成功:
-
验证管理员用户是否存在:
db.auth("abc","123")
如下图,返回为1说明成功。
1.1.3 查看用户
进入admin数据库,查看我们创建的用户:
use admin
show users
显示如下图所示:
1.1.4 启用身份验证
启用身份验证是为了保护数据库的安全。
身份验证默认是禁用的,因此要在启动MongoDB数据库服务mongod时使用参数–auth来启用身份验证(启用身份
验证前,确保你至少已有一个管理员用户)。
-
先关闭现在有的数据库服务
use admin db.shutdownServer()
运行结果如下:
-
重新启动mongod服务
在命令行输入(下面代码所使用的路径为平台所用路径):
mongod --auth --port 27017 --dbpath /data/db --logpath /tmp/mongodb.log --fork
- auth: 开启身份验证;
- dbpath: 指定数据存放路径;
- logpath: 指定日志文件输出路径;
- fork: 后台运行。
1.1.5 管理员登录数据库
-
情况一:在命令行连接数据库(注意选择admin数据库)
mongo -uabc -p123 admin
-
情况二:在受限的情况下验证身份
开启身份验证后,我们使用命令mongo直接连接数据库,虽然也能成功连接,但是在进行数据库操作如show dbs时会受到限制:
这时我们就要进入到admin数据库,去进行身份验证:
use admin db.auth("abc","123")
返回数字1说明验证成功,0说明失败。
验证成功后将拥有管理员权限。
1.1.6 删除管理员用户
删除管理员用户abc命令如下:
db.system.users.remove({user:"abc"})
返回1说明删除成功,0说明删除失败。
1.2 创建普通用户
1.2.1 常用用户权限介绍
权限 | 说明 |
---|---|
Read | 允许用户读取指定数据库 |
readWrite | 允许用户读写指定数据库 |
dbAdmin | 允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile |
userAdmin | 允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户 |
clusterAdmin | 只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。 |
readAnyDatabase | 只在admin数据库中可用,赋予用户所有数据库的读权限 |
readWriteAnyDatabase | 只在admin数据库中可用,赋予用户所有数据库的读写权限 |
userAdminAnyDatabase | 只在admin数据库中可用,赋予用户所有数据库的userAdmin权限 |
dbAdminAnyDatabase | 只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。 |
root | 只在admin数据库中可用。超级账号,超级权限 |
1.2.2 创建普通用户
-
创建用户:user1,密码:useu1,拥有数据库test的读写权限
use test db.createUser({user:"user1",pwd:"user1",roles:[{role:"readWrite",db:"test"}]})
注:该用户只对数据库test有权限。
查看用户命令:
show users
-
创建用户:user2,密码:user2,对数据库test2有读写权限,对数据库test有只读权限
use test2 db.createUser({user:"user2",pwd:"user2",roles:[{role:"readWrite",db:"test2"},{role:"read",db:"test"}]})
查看用户:
1.2.3 删除用户
(要先确定自己有root权限)删除user2用户,先进入test2数据库:
use test2
db.dropUser("user2")
结果返回true说明删除成功:
1.3 数据库限制访问
1.3.1 限制IP访问
限制 IP 访问,需要重启数据库服务 mongod 时,启用相应的功能,步骤如下:
-
关闭服务(先用默认方法启动数据库:mongo ):
use admin #进入admin数据库 db.shutdownServer() #关闭服务 exit #退出数据库
-
启动服务(只有本机 IP 可以连接数据库):
mongod --dbpath /data/db --logpath /tmp/mongodb.log --bind_ip 127.0.0.1 --fork
binf_ip
:限制连接的网络接口,可以设置多个,以逗号隔开。
使用其他 IP 访问被限制,如图所示:
1.3.2 限制端口访问
当按照以上方法启动 mongod 时,默认情况下他会等待所有在端口27017上的入站连接,可以用 -port
修改该设置。也需要重新启动 mongod 服务,方法同上。
-
关闭服务(先用默认方法启动数据库:mongo ):
use admin #进入admin数据库 db.shutdownServer() #关闭服务 exit #退出数据库
-
启动服务(只有本机 IP 可以连接数据库,且限制只能端口20000连接):
mongod -port 20000 --dbpath /data/db --logpath /tmp/mongodb.log --bind_ip 127.0.0.1 --fork
-
连接数据库:
mongo 127.0.0.1:20000
设置了端口后如果不加端口连接会被拒绝访问,如图所示:
注:在数据库实际的应用中,最好不要使用这些默认设置,对这些设置进行一些修改,让你的数据更加安全。
3-1 MongoDB 之聚合函数查询统计
1.1 聚合管道操作符将文档定制格式输出
1.1.1 常用聚合管道操作符
操作符 | 作用 |
---|---|
$project | 修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档 |
$match | 用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作 |
$limit | 用来限制MongoDB聚合管道返回的文档数 |
$skip | 在聚合管道中跳过指定数量的文档,并返回余下的文档 |
$unwind | 将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值 |
$group | 将集合中的文档分组,可用于统计结果 |
$sort | 将输入文档排序后输出 |
注意:以上操作不会修改集合的内容,只是将集合以指定形式输出。
在数据库 mydb1 有集合 educoder 内容如下(后文均用此用例):
_id course author tags learning_num 1 Python表达式问题求解实训 李暾 Python基础,求解 1882 2 Java语言之基本语法 余跃 Java基础,语法 814 3 Python面向对象编程实训 李暾 Python基础,面向对象 143 4 Android综合实训之物联网移动应用开发(1) prophet5 Android,物联网,移动开发 207
1.1.2 $project
修改文档结构输出
有时候我们并不会用到文档的全部内容,只是使用其中几列,这时候就可以使用 $project
进行操作;或者有时候要重命名键值(列名),也可以使用 $project
。
对集合 educoder 进行操作:
-
只输出作者 author 和学习人数 learning_num 信息,_id也不要不显示(_id默认是显示的):
db.educoder.aggregate({$project:{_id:0,author:1,learning_num:1}})
0 为不显示,非 0 为显示。
运行结果如图所示:
-
重命名字段名(把 learning_num 重命名为 num):
db.educoder.aggregate({$project:{course:1,authoe:1,tags:1,num:'$learning_num'}})
运行结果如图所示:
1.1.3 $match
筛选文档输出
有时候我们要在集合中筛选出符合特定条件的文档,这时候使用 $match 便可以很快实现。
只输出作者为“李暾”的文档:
db.educoder.aggregate({$match:{author:'李暾'}})
运行结果如图所示:
1.1.4 $limit
限制文档数量输出
有时候集合中文档数量太大,我们只想选取前几行查看一下,这时候就可以用 $limit,输出前2条文档:
db.educoder.aggregate({$limit:2})
运行结果如图所示:
1.1.5 $skip
跳过前 n 条文档输出
与 $limit 相反,$skip 是跳过前 n 条文档,显示剩余文档。
将集合 educoder 中的前两条文档跳过,显示剩余文档:
db.educoder.aggregate({$skip:2})
$skip 接受一个数字 n,表示丢弃结果集中的前 n 个文档;
运行结果如图所示:
-
$limit 与 $skip 可以组合使用:
db.educoder.aggregate([{$skip:1},{$limit:2}]) #跳过第一条,显示前两条,也就是显示第2-3条文档 db.educoder.aggregate([{$limit:2},{$skip:1}]) #显示前两条,跳过第一条,也就是显示第2条文档
运行结果如图所示:
1.1.6 $unwind
拆分数组类型字段
将 educoder 中的 tags 字段拆分成多条,每条包含数组中的一个值:
db.educoder.aggregate({$unwind:'$tags'})
运行结果如图所示:
1.1.7 $group
分组输出
该操作比较常用,因为文档分组后利于我们的统计。比如,按照作者分组我们就可以统计出该集合总共有几个作者。
在集合 educoder 中,按作者分组:
db.educoder.aggregate({$group:{_id:'$author'}})
运行结果如图所示:
1.1.8 $sort
排序输出
按照学习人数 learning_num 降序排序输出文档:
db.educoder.aggregate({$sort:{learning_num:-1}})
-1 代表降序排序, 1 代表升序排序。
运行结果如图所示:
1.2 聚合表达式对文档数据进行统计
1.2.1 常用聚合表达式
表达式 | 用法 |
---|---|
$sum | 计算总和 |
$avg | 计算平均值 |
$min | 获取集合中所有文档对应值的最小值 |
$max | 获取集合中所有文档对应值的最大值 |
$push | 在结果文档中插入值到一个数组中 |
$addToSet | 在结果文档中插入值到一个数组中,但不创建副本 |
$first | 根据资源文档的排序获取第一个文档数据 |
$last | 根据资源文档的排序获取最后一个文档数据 |
1.2.3 如何对文档进行统计
用例使用对应集合数据详情见上文(点击跳转)
-
通过
aggregate()
方法来获取每个作者拥有的实训数量,命名为:num_course:db.educoder.aggregate([{$group:{_id:'$author',num_course:{$sum:1}}}])
查询结果如下:
命令解析:
-
先通过聚合管道操作符 $group 将 author 字段数据分组;
-
$sum:1 的含义:如果前面的情况出现一次,就加 1 ,如果后面为 $sum:2 ,那么前面条件每满足一次就加 2 ;
-
-
通过
aggregate()
方法来获取每个作者的实训学习总人数 learning_sum :db.educoder.aggregate([{$group:{_id:'$author',learning_sum:{$sum:'$learning_num'}}}])
查询结果如下:
3-2 MongoDB 之滴滴、摩拜都在用的索引
2.1 了解索引
-
什么是索引:
索引本质上是树,最小的值在最左边的叶子上,最大的值在最右边的叶子上,使用索引可以提高查询速度(而不用全表扫描)。 -
索引的原理:
对某个键按照升续或降续创建索引,查询时首先根据查询条件查找到对应的索引条目,然后找到索引条目对应的文档指针(文档在磁盘上的存储位置),根据文档指针再去磁盘中找到相应的文档,整个过程不需要扫描全表,速度比较快。每个文档被插入集合时,如果没有给它指定索引_id,MongoDB 会自动给它创建一个默认索引_id,是个 ObjectId 对象。如图所示:
2.2 索引的分类
索引类型 | 用途 |
---|---|
包括单字段索引(Single Field Index) | ==针对某一键 key ==创建了单字段索引,其能加速对 key 字段的各种查询请求,是最常见的索引形式,MongoDB 默认创建的 id 索引也是这种类型。 |
复合索引 (Compound Index) | 复合索引是单字索引的升级版本,它针对多个字段联合创建索引,先按第一个字段排序,第一个字段相同的文档按第二个字段排序,依次类推。 |
多 key 索引 (Multikey Index) | 当索引的字段为数组时,创建出的索引称为多 key 索引。 |
哈希索引(Hashed Index) | 按照某个字段的hash值来建立索引,目前主要用于 MongoDB Sharded Cluster 的 Hash 分片,哈希索引只能满足字段完全匹配的查询,不能满足范围查询等。 |
地理位置索引(Geospatial Index) | 能很好的解决 O2O 的应用场景,比如:查找附近的美食、查找某个区域内的车站等。 |
文本索引(Text Index) | 能解决快速文本查找的需求,比如有一个博客文章集合,需要根据博客的内容来快速查找,则可以针对博客内容建立文本索引。 |
2.3 索引基本操作
2.3.1 创建索引
db.person.createIndex({key:1})
key
:要创建索引的键;
如果为 1 说明是按照升序创建索引,而如果为 -1,则是按降序创建索引。
如图所示,说明创建索引成功:
2.3.2 查询索引
-
查询集合索引:
db.person.getIndexes()
效果如图所示:
-
查询系统全部索引:
db.system.indexes.find()
2.3.3 删除索引
-
通过指定索引名称删除该索引:
db.person.dropIndex("ageIdx")
效果如图所示:
-
通过指定集合删除集合中的全部索引:
db.person.dropIndexes()
效果如图所示:
注:默认索引_id不会且不能被删除
2.4 常见索引的创建
2.4.1 创建复合索引
和创建单字段索引的方法差不多,只是选取了多个键一同作为索引,中间以逗号隔开:
db.person.createIndex({age: 1, name: 1})
用法也和单字段索引差不多,就不再赘述。
2.4.2 创建多 key 索引
当索引的字段为数组时,创建出的索引称为多 key 索引,多 key 索引会为数组的每个元素建立一条索引,比如 person 集合加入一个 habbit 字段(数组)用于描述兴趣爱好:
{name : '王小明', age : 19, habbit: ['football', 'runnning']}
需要查询有相同兴趣爱好的人就可以利用 habbit 字段的多 key 索引。
db.person.createIndex( {habbit: 1} ) // 升序创建多key索引
db.person.find({habbit: 'football'}) //查找喜欢足球的人
2.4.3 创建哈希索引
创建命令如下:
db.person.createIndex( { _id: 'hashed' } )
2.4.4 文本索引的创建与使用
2.4.4.1 什么时候使用文本索引
假如我们用 Mongodb 存储了很多博客文章,那么如何快速找到所有关于 mongodb 这个主题的文章呢?这时候就要用到文本搜索了。
有文章集合 collection,如下:
{
title: 'enjoy the mongodb articles on educoder',
tags: [
'mongodb',
'educoder'
]
}
2.4.4.2 创建文本索引
创建文本索引命令:
db.collection.createIndex({ title: 'text'})
- 创建全文本索引的字段必须为 string 格式;
- 每个集合只支持一个文本索引。
运行效果如图所示:
2.4.4.3 使用文本索引
现在我们已经创建了 title 的索引,我们来搜索一下含有 educoder.net 的文章:
db.collection.find({$text:{$search:'educoder.net'}})
- search 后的关键词可以有多个,关键词之间的分隔符可以是多种字符,例如空格、下划线、逗号、加号等,但不能是-和\,因为这两个符号会有其他用途。搜索的多个关键字是 or 的关系,除非你的关键字包含-;
- 匹配时不是完整的单词匹配,相似的词也可以匹配到;
运行效果如图所示:
2.4.4.4 删除文本索引
-
通过命令获取索引名:
db.collection.getIndexes()
-
删除命令:
db.collection.dropIndex('title_text')
运行效果如图所示:
2.5 地理位置索引
2.5.1 GeoJson 数据
并不是所有文档都可以创建地理位置索引,只有拥有特定格式的文档才可以创建。
如果我们用的是 2dsphere 索引,那么插入的应该是 GeoJson 数据。
GeoJson 的格式如是:
{ type: 'GeoJSON type' , coordinates: 'coordinates' }
type
:指的是类型,可以是 Point (本例中用的)、LineString、 Polygon 等;coordinates
:指的是一个坐标数组。
我们有如下几个位置的坐标信息(具体详情如图所示),先把它们写入集合 locations 中。
db.locations.insert({_id:1,name:'长沙站',location:{type:'Point',coordinates:[113.018987,28.201215]}})
db.locations.insert({_id:2,name:'湖南师范大学',location:{type:'Point',coordinates:[112.946045,28.170968]}})
db.locations.insert({_id:3,name:'中南大学',location:{type:'Point',coordinates:[112.932175,28.178291]}})
db.locations.insert({_id:4,name:'湖南女子学院',location:{type:'Point',coordinates:[113.014675,28.121163]}})
db.locations.insert({_id:5,name:"湖南农业大学",location:{type:'Point',coordinates:[113.090852,28.187461]}})
location
:索引名
2.5.1 创建地理位置索引
有了规范的文档后,我们就可以使用以下命令,在 location 键上创建一个地理位置索引:
db.locations.createIndex({location:'2dsphere'})
location
:索引名
2d
:平面坐标索引,适用于基于平面的坐标计算,也支持球面距离计算,不过官方推荐使用 2dsphere 索引;
2dsphere
:几何球体索引,适用于球面几何运算;默认情况下,地理位置索引会假设值的范围是从−180到180(根据经纬度设置)。
如索引创建成功,效果如图所示:
2.5.2 地理位置索引的使用
-
查询命令:
db.runCommand({ geoNear:'locations', near:{type:'Point',coordinates:[113.018987,28.201215]}, spherical:true, minDistance:1000, maxDistance:8000 })
-
geoNear
:我们要查询的集合名称; -
near
:就是基于哪个点进行搜索,这里是我们的搜索点“长沙站”; -
spherical
:是个布尔值,如果为 true,表示将计算实际的物理距离,比如两点之间有多少 km,若为 false,则会基于点的单位进行计算 ; -
minDistance
:搜索的最小距离,这里的单位是米 ; -
maxDistance
:搜索的最大距离。
-
-
查询结果分析:
在
result
中,查到了“湖南农业大学”和“湖南师范大学”,每个文档都加上了一个dis
字段,它表示这个点离你搜索点的距离。比如说,在结果中 name 为“湖南农业大学”的点的 dis 为7215.061630510019。表示“湖南农业大学”距离搜索点“长沙站”的距离是7215米。这个结果对于 LBS 应用是非常有用的。
具体详情如图所示:
3-3 MongoDB 文档的高级查询操作
3.1 数据的导入导出
3.1.1 向数据库导入数据
数据导入工具:mongoimport
;
这是 MongoDB 自带的数据导入工具,我们在未连接客户端时使用(前提要启动服务)。
mongoimport -d Testdb1 -c score --type csv --headerline --ignoreBlanks --file test.csv
-d Testdb1
:指定将数据导入到 Testdb1 数据库;
-c score
:将数据导入到集合 score ,如果这个集合之前不存在,会自动创建一个(如果省略 --collection 这个参数,那么会自动新建一个以 CSV 文件名为名的集合);
--type csv
:文件类型,这里是 CSV;
--headerline
:这个参数很重要,加上这个参数后创建完成后的内容会以 CSV 文件第一行的内容为字段名(导入json文件不需要这个参数);
--ignoreBlanks
:这个参数可以忽略掉 CSV 文件中的空缺值(导入json文件不需要这个参数);
--file 1.csv
:这里就是 CSV 文件的路径了,需要使用绝对路径。
3.1.2 从数据库导出数据
数据导出工具: mongoexport
;
-
导出
json
格式文件:mongoexport -d Testdb1 -c score -o /file.json --type json
-o /file.json
:输出的文件路径/(根目录下)和文件名;--type json
:输出的格式,默认为 json。
-
导出
csv
格式的文件:mongoexport -d Testdb1 -c score -o /file.json --type csv -f "_id,name,age,sex,major"
-f
:当输出格式为 csv 时,需要指定输出的字段名。
3.2 高级查询
假设有集合 hobbies 内容如下:
_id name sex hobbies 1 小红 女 唱歌,跳舞,羽毛球 2 小明 男 唱歌,乒乓球,羽毛球 3 小亮 男 乒乓球,羽毛球 有集合 student 如下:
_id name age sex major 1 王晓丽 19 女 计算机 2 张明 21 男 计算机 3 秋雅 20 女 播音主持 4 张欣欣 18 女 表演
3.2.1 $all
匹配所有
查询集合 hobbies 中所有喜欢“唱歌”和“羽毛球”的人:
db.hobbies.find({hobbies:{$all:["唱歌","羽毛球"]})
查询结果如图所示:
$all
会查询满足方括号中所有条件的文档,如果只有其中一项满足是不会被查询出来的。
3.2.2 $exists
判断字段是否存在
在集合 hobbies 中更新文档:
db.hobbies.save({_id:1,name:"小红",age:18,sex:"女",hobbies:["唱歌","跳舞","羽毛球"]}) #添加小红年龄18
- 查询 hobbies 集合中存在 age 字段的文档,如图所示:
- hobbies 集合中不存在 age 字段的文档,如图所示:
3.2.3 $mod
取模运算
在集合 hobbies 中更新文档:
db.hobbies.update({_id:2},{$set:{age:20}}) #添加小明年龄20
db.hobbies.update({_id:3},{$set:{age:22}}) #添加小亮年龄22
查询 age 取模7等于4的文档:
db.hobbies.find({age:{$mod:[7,4]}})
查询结果如如图所示:
3.2.4 $in
包含
查询 age =17或 age =20的文档 :
db.hobbies.find({age:{$in:[17,20]}})
查询结果如图所示:
3.2.5 $nin
不包含
查询 age !=17且 age !=20的文档:
db.hobbies.find({age:{$nin:[17,20]}})
查询结果如图所示:
3.2.6 $size
数组元素个数
可以查询特定长度的数组,比如 hobbies 这一字段,查询有两个爱好的文档:
db.hobbies.find({hobbies:{$size:2}})
查询结果如图所示:
3.2.7 查询结果排序
查询结果排序语法如下:
db.collection.find().sort({_id:1}) #将查询结果按照_id升序排序
db.collection.find().sort({_id:-1}) #将查询结果按照_id降序排序
collection
:需进行排序操作的集合名
3.2.8 $or
条件之间的或查询
$or
表示多个查询条件之间是或的关系,比如查询性别 sex 为 男 或年龄 age 为18的文档信息:
db.student.find({$or:[{sex:"男"},{age:18}]})
查询结果,如图所示:
3.2.9 $and
条件之间的且查询
$and
表示多个查询条件之间是且的关系,比如查询年龄 age 大于18且小于21(18 < age < 21)的信息:
db.student.find({$and:[{age:{$gt:18}},{age:{$lt:21}}]})
查询结果,如图所示:
gt
: greater than 大于
gte
: greater than or equal 大于等于
lt
: less than 小于
lte
: less than or equal 小于等于
3.2.10 $not
条件取反查询
$not
用来执行取反操作,比如查询年龄 age 大于等于20岁,然后进行取反(即查询年龄小于20岁的文档):
db.student.find({age:{$not:{$gte:20}}})
查询结果,如图所示:
3.2.11 正则表达式匹配查询
查询不符合 major=计* 开头文档:
db.student.find({major:{$not:/^计.*/}})
查询结果,如图所示:
3.2.12 count()
返回结果集总数
比如返回上一步正则查询到的结果集有几条:
db.student.find({major:{$not:/^计.*/}}).count()
查询结果,如图所示:
3.3 游标
3.3.1 什么是游标
通俗的说,游标不是查询结果,而是查询的返回资源,或者接口。通过这个接口,你可以逐条读取。就像 fopen 打开文件,得到一个资源一样,通过资源,可以一行一行的读文件。
3.3.2 使用循环插入数据
我们首先插入10000条数据到集合 items,因为 mongodb 底层是 javascript 引擎,所以我们可以使用 js
的语法来插入数据:
for(var i=0;i<10000;i++)db.items.insert({_id:i,text:"Hello MongoDB"+i})
插入后查看效果,如图所示:
3.3.3 声明游标
定义一个变量来保存这个游标,find 的查询结果(_id<=5)赋值给了游标 cursor 变量,代码如下:
var cursor=db.items.find({_id:{$lte:5}})
3.3.4 打印游标中的数据信息
-
printjson(cursor.next())
打印下一条数据,如图所示:
注意:当取完游标中的数据,又进行打印,会报错。
-
使用
js
的while
语法来循环打印,具体步骤如图所示:
-
使用
for
循环打印,具体步骤如图所示:
-
使用
forEach
打印,具体步骤如图所示:
3.3.5 游标的使用场景
我们可以在分页的情况下使用游标。
假设每页有10行,我们查询第701页,可以配合 skip()
和 limit()
来实现,具体步骤如图所示:
如果不想全部取出,只取出某一个,可以使用如下方法,取出数组下标,具体步骤如图所示:
4-1 MongoDB 实验——java 和 MongoDB
1.1 Java 连接数据库
1.1.1 获取 Java 的 Mongodb 驱动包
在 Java 的 MongoDB 驱动包获取网址 下载获取最新版本的驱动包,放在响应环境目录下。
1.1.2 Java 代码连接无密码数据库
通过以下两条代码,进行数据库的连接:
MongoClient mongoClient = new MongoClient("localhost",27020); //启动本地服务,端口号为27020
MongoDatabase mongoDatabase = mongoClient.getDatabase("databaseName"); //连接名为databaseName数据库
1.1.3 编译代码进行数据库连接
此步骤无需操作,平台会完成编译:
javac -cp .:mongo-java-driver-3.9.1.jar Mongoconnect.java
java -cp .:mongo-java-driver-3.9.1.jar Mongoconnect
1.2 创建集合
我们可以使用驱动 com.mongodb.client.MongoDatabase
类中的 createCollection()
来创建集合:
mongoDatabase.createCollection("test"); //创建集合test
1.3 获取集合
使用驱动 com.mongodb.client.MongoDatabase
类的 getCollection()
方法来获取集合:
MongoCollection<Document> collection = mongoDatabase.getCollection("test"); //获取集合test
1.4 插入文档
使用驱动 com.mongodb.client.MongoCollection
类的 insertMany()
方法来插入一个文档:
//注意,在插入文档前,先选好集合再进行操作,选择test集合
MongoCollection<Document> collection = mongoDatabase.getCollection("test");
Document document1 = new Document(); //创建一条文档 document1,以下代码为向文档 document1 中追加数据
document1.append("title", "MongoDB");
document1.append("content", "I love MongoDB").
document1.append("nums", 90).
document1.append("tags", "database");
Document document2 = new Document(); //创建一条文档 document2,以下代码为向文档 document1 中追加数据
document2.append("title", "Java");
document2.append("content", "I love Java").
document2.append("nums", 30).
document2.append("tags", "language");
List<Document> documents = new ArrayList<Document>(); //将以上文档打包存放,为文档插入做准备
documents.add(document1);
documents.add(document2);
collection.insertMany(documents); //插入多条文档到集合中
MongoCollection<Document>
是 MongoDB Java 驱动中的一种类型,表示一个 MongoDB 集合,其中的每个文档以Document
类型存储- 插入一条文档时应使用
insertOne()
方法
1.5 检索文档
使用驱动 com.mongodb.client.MongoCollection
类中的 find()
方法来获取集合中的所有文档。
此方法返回一个游标,所以你需要遍历这个游标。
//检索所有文档
/**
* 1. 获取迭代器FindIterable<Document>
* 2. 获取游标MongoCursor<Document>
* 3. 通过游标遍历检索出的文档集合
**/
FindIterable<Document> findIterable = collection.find();
MongoCursor<Document> mongoCursor = findIterable.iterator();
while(mongoCursor.hasNext()){
System.out.println(mongoCursor.next());
}
1.6 更新文档
使用 com.mongodb.client.MongoCollection
类中的 updateMany()
方法来更新集合中的文档:
//更新文档:将第一关例子文档中nums=90的文档修改为nums=100
collection.updateMany(Filters.eq("nums", 90), new Document("$set",new Document("nums",100)));
updateMany
:更新符合条件的所有记录updateOne
:更新符合条件的第一条记录
1.7 删除文档
要删除集合中的第一个文档,首先你需要使用 com.mongodb.DBCollection
类中的 findOne()
方法来获取第一个文档,然后使用 remove()
方法删除。
//删除符合条件的第一个文档
collection.deleteOne(Filters.eq("nums", 100));
//删除所有符合条件的文档
collection.deleteMany (Filters.eq("nums", 100));
5-1 MongoDB 实验——数据库优化
1.1 查询优化原则
- 在查询条件、排序条件、统计条件的字段上选择创建索引,可以显著提高查询效率;
- 用
$or
时把匹配最多结果的条件放在最前面,用$and
时把匹配最少结果的条件放在最前面; - 使用
limit()
限定返回结果集的大小,减少数据库服务器的资源消耗,以及网络传输的数据量; - 尽量少用
$in
,而是分解成一个一个的单一查询。尤其是在分片上,$in
会让你的查询去每一个分片上查一次,如果实在要用的话,先在每个分片上建索引; - 尽量不用模糊匹配查询,用其它精确匹配查询代替,比如
$in
、$nin
; - 查询量大、并发大的情况,通过前端加缓存解决;
- 尽量不用安全模式,这样客户端没必要等待数据库返回查询结果以及处理异常,快了一个数量级;
- MongoDB 的智能查询优化,判断粒度为
query
条件,而skip
和limit
都不在其判断之中,当分页查询最后几页时,先用order
反向排序; - 尽量减少跨分片查询,
balance
均衡次数少; - 只查询要使用的字段,而不查询所有字段;
- 更新字段的值时,使用
$inc
比update
效率高; apped collections
比普通collections
的读写效率高;server-side processing
类似于 SQL 查询的存储过程,可以减少网络通讯的开销;- 必要时使用
hint()
强制使用某个索引查询; - 如果有自己的主键列,则使用自己的主键列作为 id,这样可以节约空间,也不需要创建额外的索引;
- 使用
explain
,根据exlpain plan
进行优化; - 范围查询的时候尽量用
$in
、$nin
代替; - 查看数据库查询日志,具体分析的效率低的操作;
- mongodb 有一个数据库优化工具
database profiler
,能够检测数据库操作的性能。可以发现query
或者write
操作中执行效率低的,从而针对这些操作进行优化; - 尽量把更多的操作放在客户端,当然这就是 mongodb 设计的理念之一。
1.2 MongoDB 的 Profiling 工具
1.2.1 Profiling 工具
在很多情况下, DBA(数据库管理员)都要对数据库的性能进行分析处理,找出降低性能的根源。而 Profiling 就是 Mongo 自带的一种分析工具来检测并追踪影响性能的慢查询。
慢查询日志一般作为优化步骤里的第一步。通过慢查询日志,定位每一条语句的查询时间。比如超过了50ms,那么查询超过50ms 的语句需要优化。然后它通过 .explain()
解析影响行数是不是过多,所以导致查询语句超过50ms。
所以优化步骤一般就是:
-
用慢查询日志(system.profile)找到超过50ms 的语句;
-
然后再通过
.explain()
解析影响行数,分析为什么超过50ms; -
决定是不是需要添加索引。
Profiling 级别说明:
-
0:关闭,不收集任何数据;
-
1:收集慢查询数据,默认是100毫秒;
-
2:收集所有数据。
1.2.2 启用 Profiling 工具
Profiling 有两种开启方式,一种是启动服务时配置启动,一种是 mongoshell 中进行实时配置。
1.2.2.1 mongo shell 中启动配置
- 查看状态,如图所示:级别和时间;
- 查看级别,如图所示:
- 设置级别,如图所示:
- 设置级别和时间,如图所示:
注意:
以上要操作要是在 test 集合下面的话,只对该集合里的操作有效,要是需要对整个实例有效,则需要在所有的集合下设置或则在开启的时候开启参数;
每次设置之后返回给你的结果是修改之前的状态(包括级别、时间参数)。
1.2.2.2 全局开启 Profiling
-
可以在 mongod 启动时加上以下参数:
mongod --profile=1 --slowms=50
-
或在配置文件里添加两行,如下所示:
profile = 1 slowms = 50
1.2.3 关闭 Profiling 工具
只需要将收集慢查询数据的时间设置为0就可以关闭,如图所示:
1.2.4 慢查询分析
要进行慢查询分析,首先,要启用 Profiling 工具,以下例子 Profiling 级别设置为1,时间设置为50ms。其次,要进行过超过 50ms 的操作才会记录到慢查询日志中,存在记录结果。
-
首先,我们在 test 数据库启用 Profiling 工具:
use test db.setProfilingLevel(1,50) # 设置级别为1,时间为50ms,意味着只有超过50ms的操作才会记录到慢查询日志中
-
然后在 test 数据库的 items 集合中循环插入100万条数据:
for(var i=0;i<1000000;i++)db.items.insert({_id:i,text:"Hello MongoDB"+i})
-
返回所有结果:
db.system.profile.find().pretty()
{
"op" : "insert", #操作类型,有insert、query、update、remove、getmore、command
"ns" : "test.items", #操作的集合
"command" : {
"insert" : "items",
"ordered" : true,
"$db" : "test"
},
"ninserted" : 1,
"keysInserted" : 1,
"numYield": 0, #该操作为了使其他操作完成而放弃的次数。通常来说,当他们需要访问还没有完全读入内存中的数据时,操作将放弃。这使得在MongoDB为了放弃操作进行数据读取的同时,还有数据在内存中的其他操作可以完成
"locks": { #锁信息,R:全局读锁;W:全局写锁;r:特定数据库的读锁;w:特定数据库的写锁
"Global" : {
"acquireCount" : {
"r" : NumberLong(1),
"w" : NumberLong(1)
}
},
"Database" : {
"acquireCount" : {
"w" : NumberLong(1)
}
},
"Collection" : {
"acquireCount" : {
"w" : NumberLong(1)
}
}
},
"responseLength" : 45, #返回字节长度,如果这个数字很大,考虑值返回所需字段
"protocol" : "op_msg",
"millis" : 60, #消耗的时间(毫秒)
"ts" : ISODate("2018-12-07T08:19:11.997Z"), #该命令在何时执行
"client" : "127.0.0.1", #链接ip或则主机
"appName" : "MongoDB Shell",
"allUsers" : [ ],
"user" : ""
}
profile 部分字段解释:
op
:操作类型;
ns
:被查的集合;
commond
:命令的内容;
docsExamined
:扫描文档数;
nreturned
:返回记录数;
millis
:耗时时间,单位毫秒;
ts
:命令执行时间;
responseLength
:返回内容长度。
1.2.5 常用的慢日志查询命令
-
返回最近的10条记录:
db.system.profile.find().limit(10).sort({ ts : -1 }).pretty()
-
返回所有的操作,除 command 类型的:
db.system.profile.find( { op: { $ne : 'command'} }).pretty()
-
返回特定集合:
db.system.profile.find( { ns : 'test.items' } ).pretty()
-
返回大于5毫秒慢的操作:
db.system.profile.find({ millis : { $gt : 5 } } ).pretty()
-
从一个特定的时间范围内返回信息:
db.system.profile.find( { ts : { $gt : new ISODate("2018-12-09T08:00:00Z"), $lt : new ISODate("2018-12-10T03:40:00Z") } } ).pretty()
-
特定时间,限制用户,按照消耗时间排序:
db.system.profile.find( { ts : { $gt : new ISODate("2018-12-09T08:00:00Z") , $lt : new ISODate("2018-12-10T03:40:00Z") } }, { user : 0 } ).sort( { millis : -1 } ).pretty()
-
查看最新的 Profile 记录:
db.system.profile.find().sort({$natural:-1}).limit(1).pretty()
-
显示5个最近的事件:
show profile
6-1 MongoDB实验——数据备份和恢复
1.1 数据备份
1.1.1 mongodump 备份工具
mongodump 的参数与 mongoexport(数据导出)的参数基本一致:
参数 | 参数说明 |
---|---|
-h | 指明数据库宿主机的IP |
-u | 指明数据库的用户名 |
-p | 指明数据库的密码 |
-d | 指明数据库的名字 |
-c | 指明collection 的名字 |
-o | 指明到要导出的文件名 |
-q | 指明导出数据的过滤条件 |
--authenticationDatabase | 验证数据的名称 |
--gzip | 备份时压缩 |
--oplog | use oplog for taking a point-in-time snapshot |
1.1.2 使用 mongodump 备份数据
备份工具同导入导出工具类似,都是在命令行进行操作,无需进入客户端。
-
全库备份(如果数据库未设置用户和密码,可以省略 -uroot -proot 参数)
mongodump -h 127.0.0.1:27300 -uroot -proot --authenticationDatabase admin -o /home/mongod #备份本地27300端口中root用户的所有数据库到/home/mongod目录下
-
单个数据库备份
mongodump -h 127.0.0.1:27300 -uroot -proot --authenticationDatabase admin -d test -o /home/mongod/test #备份本地27300端口中root用户的test数据库到/home/mongod/test目录下
-
集合备份
mongodump -h 127.0.0.1:27300 -uroot -proot --authenticationDatabase admin -d test -c haha -o /home/mongod/test/haha #备份27300端口中root用户的test数据库的haha集合到/home/mongod/test/haha目录下
-
压缩备份库
mongodump -h 127.0.0.1:27300 -uroot -proot --authenticationDatabase admin -d test -o /home/mongod/test1 --gzip #压缩备份本地27300端口中root用户的test数据库到/home/mongod/test1目录下
-
压缩备份集合
mongodump -h 127.0.0.1:27300 -uroot -proot --authenticationDatabase admin -d test -c haha -o /home/mongod/test1/haha --gzip #压缩备份27300端口中root用户的test数据库的haha集合到/home/mongod/test1/haha目录下
1.2 数据恢复
1.2.1 mongorestore 恢复工具
参数 | 参数说明 |
---|---|
-h | 指明数据库宿主机的IP |
-u | 指明数据库的用户名 |
-p | 指明数据库的密码 |
-d | 指明数据库的名字 |
-c | 指明collection 的名字 |
-o | 指明到要导出的文件名 |
-q | 指明导出数据的过滤条件 |
--authenticationDatabase | 验证数据的名称 |
--gzip | 备份时压缩 |
--oplog | use oplog for taking a point-in-time snapshot |
--drop | 恢复的时候把之前的集合 drop 掉 |
使用 mongorestore 恢复数据
-
全库备份中恢复单库(基于之前的全库备份)
mongorestore -h 127.0.0.1:27017 -uroot -proot --authenticationDatabase admin -d test --drop /home/mongod #从/home/mongod目录下恢复全部数据库的数据到本地27300端口中root用户中(基于第一关的备份,下同)
-
恢复 test 库
mongorestore -h 127.0.0.1:27017 -uroot -proot --authenticationDatabase admin -d test /home/mongod/test #从/home/mongod/test目录下恢复名为test的单个数据库的数据到本地27300端口中root用户中的test数据库
-
恢复 test 库下的 haha 集合
mongorestore -h 127.0.0.1:27017 -uroot -proot --authenticationDatabase admin -d test -c haha /home/mongod/test/haha/haha.bson #从/home/mongod/test/haha目录下恢复集合的数据到本地27300端口中root用户的test数据库的haha集合中
-
–drop 参数实践恢复
# 恢复单库 mongorestore -h 127.0.0.1:27017 -uroot -proot --authenticationDatabase admin -d test --drop /home/mongod/test # 恢复单表 mongorestore -h 127.0.0.1:27017 -uroot -proot --authenticationDatabase admin -d test -c vast --drop /home/mongod/test/haha/haha.bson
- 集合备份文件后缀 .bson
- 压缩备份文件后缀**.gz**
- 例:压缩备份student集合文件:student.bson.gz
7-1 MongoDB 复制集 &分片
1.1 MongoDB 架构
1.1.1 复制集
复制集对于新手来说不是一个好理解的概念,那么我们就从为什么要使用复制集来理解它吧。
为什么使用复制集:
-
备份数据
数据库的数据只有一份的话是极不安全的,一旦数据所在的电脑坏掉,我们的数据就彻底丢失了,所以要有一个备份数据的机制。 -
故障自动转移
部署了复制集,当主节点挂了后,集群会自动投票再从节点中选举出一个新的主节点,继续提供服务。而且这一切都是自动完成的,对运维人员和开发人员是透明的。当然,发生故障了还是得人工及时处理,不要过度依赖复制集,万一都挂了,那就连喘息的时间都没有了。 -
在某些特定的场景下提高读性能
默认情况下,读和写都只能在主节点上进行。
下面是 MongoDB 的客户端支持5种复制集读选项: -
primary :默认模式,所有的读操作都在复制集的主节点进行的;
-
primaryPreferred :在大多数情况时,读操作在主节点上进行,但是如果主节点不可用了,读操作就会转移到从节点上执行;
-
secondary :所有的读操作都在复制集的从节点上执行;
-
secondaryPreferred :在大多数情况下,读操作都是在从节点上进行的,但是当从节点不可用了,读操作会转移到主节点上进行;
-
nearest :读操作会在复制集中网络延时最小的节点上进行,与节点类型无关。
复制集结构,如图所示:
1.1.2 分片
**分片(sharding)**是指将数据库拆分,使其分散在不同的机器上的过程。将数据分散到不同的机器上,不需要功能强大的服务器就可以存储更多的数据和处理更大的负载。基本思想就是将集合切成小块,这些块分散到若干片里,每个片只负责总数据的一部分,最后通过一个均衡器来对各个分片进行均衡(数据迁移)。通过一个名为 mongos 的路由进程进行操作,mongos 知道数据和片的对应关系(通过配置服务器)。
什么时候使用分片:
- 机器的磁盘不够用了,使用分片解决磁盘空间的问题;
- 单个 mongod 已经不能满足写数据的性能要求,通过分片让写压力分散到各个分片上面,使用分片服务器自身的资源;
- 想把大量数据放到内存里提高性能,通过分片使用分片服务器自身的资源。
分片结构,如图所示:
上图中主要有如下所述三个主要组件:
-
Shard :用于存储实际的数据块,实际生产环境中一个 shard server 角色可由几台机器组个一个 replica set 承担,防止主机单点故障;
-
Config Server :mongod 实例,存储了整个 ClusterMetadata,其中包括 chunk 信息;
-
Query Routers :前端路由,客户端由此接入,且让整个集群看上去像单一数据库,前端应用可以透明使用。
1.2 MongoDB 复制集搭建
我们在一台虚拟机中启动三个 mongodb 服务,来模拟多台服务器协同工作的情况。
选用三个端口连接数据库:27018、27019、27020。
1.2.1 配置文件设置
因为我们要启动三个 mongodb 服务,所以要准备三个数据存放位置,三个日志文件,三个配置文件。
-
数据存放位置
在 /data 路径下创建文件夹 db1、db2 和 db3 来存放三个服务的数据。
-
日志文件
在 /logs 路径下创建文件夹 mongo 存放日志文件 mongod1.log、mongod2.log 和 mongod3.log(文件不用创建,到时候会自动生成,但路径即文件夹必须提前创建好)。
-
配置文件
在 /etc/mongod 路径下新建三个配置文件,使用配置文件启动 mongod 服务(在之前的实训中我们都是用命令启动的)。
-
mongod1.conf 内容如下:
port=27018 #配置端口号 dbpath=/data/db1 #配置数据存放的位置 logpath=/logs/mongo/mongod1.log #配置日志存放的位置 logappend=true #日志使用追加的方式 fork=true #设置在后台运行 replSet=YOURMONGO #配置复制集名称,该名称要在所有的服务器一致
-
mongod2.conf 内容如下:
port=27019 #配置端口号 dbpath=/data/db2 #配置数据存放的位置 logpath=/logs/mongo/mongod2.log #配置日志存放的位置 logappend=true #日志使用追加的方式 fork=true #设置在后台运行 replSet=YOURMONGO #配置复制集名称,该名称要在所有的服务器一致
-
mongod3.conf 内容如下:
port=27020 #配置端口号 dbpath=/data/db3 #配置数据存放的位置 logpath=/logs/mongo/mongod3.log #配置日志存放的位置 logappend=true #日志使用追加的方式 fork=true #设置在后台运行 replSet=YOURMONGO #配置复制集名称,该名称要在所有的服务器一致
-
以上工作准备完成,便可以开启服务了,配置文件启动命令(在命令行中输入):
mongod -f /etc/mongod/mongod1.conf
mongod -f /etc/mongod/mongod2.conf
mongod -f /etc/mongod/mongod3.conf
具体效果如图所示:
1.2.2 配置主从节点
三个端口的服务全部启动成功后,需要进入其中一个进行配置节点。
设置27019为 arbiter 节点。
-
进入端口号为27018的进行配置,连接数据库:
mongo --port 27018
; -
选择数据库
admin
; -
输入配置要求如下:
config = { _id:"YOURMONGO", members:[ {_id:0,host:'127.0.0.1:27018'}, {_id:1,host:'127.0.0.1:27019',arbiterOnly:true}, {_id:2,host:'127.0.0.1:27020'}, ] }
-
使用
rs.initiate(config)
进行初始化:rs.initiate(config)
具体效果如图所示:
如上所示配置成功后,不同端口的数据库登陆后左边的>可能变为如下两种状态:
- 说明该端口为主节点,如图所示:
- 说明该端口为从节点,如图所示:
- 使用
rs.status()
查看状态。
1.2.3 验证复制集同步
在主数据库插入数据,然后去从数据库查看数据是否一致。
连接主数据库:mongo --port 27018
;
use test
db.person.insert({name:'王小明',age:20})
具体步骤如图所示:
连接从数据库:mongo --port 27020
;
从库查询数据需要设置 slaveOk
为 true
;
use test
rs.slaveOk(true)
db.person.find()
具体步骤如图所示:
至此复制集搭建完成,主数据库的数据的更新后,从数据库的数据也会同步更改,一旦主数据库挂掉,从数据库可自动变为主数据库,极大地保障了数据的安全,这也是搭建复制集的必要性。
1.2.4 切换 Primary 节点到指定的节点
在实际应用中,如果想指定某服务器或端口作为主节点,而不是随机选举一个主节点,可以通过以下方法改变 Primary 节点:
-
先进入主节点中进行操作:
mongo --port 27018
-
查看目前的节点状态:
rs.conf() #查看配置 rs.status() #查看状态
其中
priority
: 是优先级,默认为 1,优先级 0 为被动节点,不能成为活跃节点。优先级不为 0 则按照由大到小选出活跃节点。
因为默认的都是1,所以只需要把给定的服务器的 priority 加到最大即可。让27020成为主节点,操作如下:
现进入目前的主节点进行操作如下:
cfg=rs.conf()
cfg.members[2].priority=2 #修改priority,members[2]即对应27020端口
rs.reconfig(cfg) #重新加载配置文件,强制了副本集进行一次选举,优先级高的成为Primary。在这之间整个集群的所有节点都是secondary
rs.status()
这样,给定的服务器或端口就成为了主节点。
1.3 MongoDB 分片集搭建
1.3.1 配置文件设置
-
同复制集一样,我们要准备目录存放我们的数据和日志:
mkdir -p /data/shard1/db mkdir -p /logs/shard1/log mkdir -p /data/shard2/db mkdir -p /logs/shard2/log mkdir -p /data/shard3/db mkdir -p /logs/shard3/log mkdir -p /data/config/db mkdir -p /logs/config/log mkdir -p /logs/mongs/log
-
配置文件 (新建在 /etc/mongo 目录下);
-
mongod1.conf 内容如下:
dbpath=/data/shard1/db logpath=/logs/shard1/log/mongodb.log port=10001 shardsvr=true fork=true
-
mongod2.conf 内容如下:
dbpath=/data/shard2/db logpath=/logs/shard2/log/mongodb.log port=10002 shardsvr=true fork=true
-
mongod3.conf 内容如下:
dbpath=/data/shard3/db logpath=/logs/shard3/log/mongodb.log port=10003 shardsvr=true fork=true
其中 shardsvr 是用来开启分片的。
-
-
从配置文件启动 mongod 服务:
mongod -f /etc/mongo/mongod1.conf mongod -f /etc/mongo/mongod2.conf mongod -f /etc/mongo/mongod3.conf
1.3.2 config 节点
-
配置启动节点服务:
mongod --dbpath /data/config/db --logpath /logs/config/log/mongodb.log --port 10004 --configsvr --replSet cs --fork
-
连接 route 节点:
mongo localhost:10004
-
输入以下命令:
use admin cfg = { _id:'cs', configsvr:true, members:[ {_id:0,host:'localhost:10004'} ] } rs.initiate(cfg)
运行效果如图所示,说明设置成功:
1.3.3 route 节点
-
配置启动节点服务:
mongos --configdb cs/localhost:10004 --logpath /logs/mongs/log/mongodb.log --port 10005 --fork
-
连接上 route 节点:
mongo localhost:10005
-
添加分片:
sh.addShard('localhost:10001') sh.addShard('localhost:10002') sh.addShard('localhost:10003')
-
查看集群的状态:分片摘要信息、数据库摘要信息、集合摘要信息等;
sh.status()
运行效果如图所示,说明设置成功:
1.3.4 分片验证
-
连接 route 节点:
mongo localhost:10005
;数据量太小可能导致分片失败,这是因为 chunksize 默认的大小是 64MB( chunkSize 来制定块的大小,单位是 MB ),使用以下代码把 chunksize 改为 1MB 后,插入数据,便可以分片成功。
use config db.settings.save( { _id:"chunksize", value: 1 } )
-
对集合使用的数据库启用分片:
sh.enableSharding("test")
-
添加索引:
db.user.ensureIndex({ "uid" : 1})
-
分片:
sh.shardCollection("test.user",{"uid" : 1})
-
插入10万条数据(大概需要40s 左右):
use test for(i=0;i<100000;i++){db.user.insert({uid:i,username:'test-'+i})}
-
再次运行
sh.status()
会多出如图 3 内容,显示了分片的情况。
-
去各个节点查看数据分布情况:
以下分别是在端口10001(图4)、10002(图5)和10003(图6)查询的文档条数,加起来正好10万条。