mongoDB

153 阅读12分钟

微信技术群:Day9884125

1. 大纲

  1. MongDB的概念
  2. MongDB安装配置与基础命令
  3. MongoDB CRUD

2. MongDB的体系结构

2.1 摘要

  1. Nosql的概念
  2. Nosql的应用场景
  3. MongoDB的逻辑组成

2.2 NoSql的概念

   NoSql它不需要预先定义模式,没有主键关联、支持分片、支持副本

2.2.1 NoSql的分类

2.2.1.1 键值(key-value)存储数据库

   这一类数据库主要会使用到一个哈希表,这个表中有一个特定的键和一个指针指向特定的数据。Key/Value模型对于IT系统来说的优势在于简单,易部署。但是如果DBA只对部分值进行查询或更新的时候,Key/Value就显得效率低下了。例如tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB

2.2.1.2 列存储数据库

   这部分数据库通常是用来应对分布式存储的海量数据。键仍然存在,但是他们的特点指向了多个列。这些列是由列家族来安排的。例如:Cassandra、HBase、Riak

2.2.1.3 文档型数据库

   它同第一种键值存储相类似。该类型的数据模型是版本化的文档,板结构化的文档以特定的格式存储,比如josn,文档型数据库可以看作是键值数据库的升级版,允许之间嵌套键值。而且文档型数据库比键值数据库的查询效率更高。例如:CouchDB,MongoDB,SequoialDB

2.2.1.4 图形数据库

   图形结构的数据库同其他行列以及刚性结构的sql数据库不同,它是使用灵活的图形模型,并且能够扩展到多个服务器上。NoSql数据库没有标准的查询语句,因此进行数据库查询需要制定数据模型。许多NoSql数据库都有REST式的数据接口或者查询API。例如:Neo4J、InfoGrid、Infinite Graph

2.3 NoSql的应用场景

NoSql数据库在以下场景比较适用

  1. 数据模型比较简单
  2. 需要灵活性更强的IT系统
  3. 对数据库性能要求比较高
  4. 不需要高度的数据一致性 基于豆瓣电影举例说明NoSql的应用场景
  5. 电影基本信息分析
  6. 电影与明星关系存储

2.4 MongoDB的逻辑组成

2.4.1 体系结构

image.png

2.4.2 逻辑结构与关系数据库的对比

关系型数据库MongoDB
database(数据库)database(数据库)
table(表)collection(集合)
row(行)document(BSON文档)
column(列)fileId(字段)
index(唯一索引、主键索引)index(全文索引)
join(主外键关联)embedded Document(嵌套文档)
primary key(指定1至N个列做主键)primary key(指定_id field做主键)
aggreation(groupy)aggreation(pipeline mapReduce)

3 MongoDB安装配置与基础命令

3.1 介绍

  1. mongodb版本说明
  2. mongodb启动参数说明
  3. 客户端shell的使用及参数说明
  4. 数据库与集合的基础操作

3.2 mongodb社区版说明

下载地址:www.mongodb.com/download-ce…

#下载
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-4.0.5.tgz
# 解压
tar -zxvf mongodb-linux-x86_64-4.0.5.tgz

3.3 mongodb启动参数说明

   mongodb由C++编写,下载下来可以直接启动

#创建数据库目录
mkdir -p /data/mongo
# 启动mongo
./bin/mongod --dbpath=/data/mongo/

3.3.1 常规参数

参数说明
dbpath数据库⽬录,默认/data/db
bind_ip监听IP地址,默认全部可以访问
port监听的端口,默认27017
logpath日志路径
logappend是否追加日志
auth是否开启用户密码登录
fork是否已后台启动的方式登录
config指定配置文件

3.3.2 配置文件示例

vim mongo.conf

内容

dbpath=/data/mongo/
port=27017
bind_ip=0.0.0.0
fork=true
logpath = /data/mongo/mongodb.log
logappend = true
auth=false

已配置文件方式启动

./bin/mongod -f mongo.conf

3.3.3 客户端shell的使用及参数说明

#启动客户端 连接 本机的地的默认端口
./bin/mongo
# 指定IP和端口
./bin/mongo --host=127.0.0.1 --port=27017

mongo shell 是一个js控台,可以执行js相关运算如:

image.png

3.4 数据库与集合的基础操作

# 查看数据库
show dbs;
# 切换数据库
use luban;
# 创建数据库与集合,在插入数据时会自动 创建数据库与集和
db.friend.insertOne({name:"wukong",sex:"man"});
# 查看集合
show tables;
show collections;
# 删除集合
db.friend.drop();
# 删除数据库
db.dropDatabase();

4 MongoDB CRUD

4.1 概要

  1. 数据的新增方式
  2. 数据的查询
  3. 数据的修改删除
  4. 全文索引查询

4.2 数据的新增的方式

关于mongodb数据插入的说明

  1. 数据库的新增不需要事先设计模型结构,插入数据时会自动创建
  2. 同一个集合中不同数据字段结构不一样

4.2.1 插入相关方法

//插入单条
db.friend.insertOne({name:"wukong",sex:"man"});
// 插入多条
db.friend.insertMany([
{name:"wukong",sex:"man"},{name:"diaocan",sex:"woman",age:18,birthday:new
Date("1995-11-02")},{name:"zixiao",sex:"woman"}
]);

// 指定ID 8 db.friend.insert([ 9
{_id:1,name:"wokong",sex:"man",age:1}, 
{_id:2,name:"diaocan",sex:"women",birthday:new Date("1988-11-11")}
])

4.3 数据的查询

4.3.1 介绍

a. 基于条件的基础查询
b. andand、or、inin、gt、gtegte、lt、$lte 运算符
c. 基于 sort skip limit ⽅法实现排序与分⻚
d. 嵌套查询
e. 数组查询
f. 数组嵌套查询

  • 逻辑运算:andand、or    其值为多个逻辑的组合运算,后⾯跟中括号,中括号包括多个⼤括号。
  • 值运算:inin、gt、gtegte、lt、$lte

4.3.2 基础查询

# 基于ID查找
db.emp.find({_id:1101})
# 基于属性查找
db.emp.find({"name":"鲁班"})
# && 运算 与大于 运算
db.emp.find({"job":"讲师","salary":{$gt:8000}})
# in 运算
db.emp.find({"job":{$in:["讲师","客服部"]}})
# or 运算
db.emp.find({$or:[{job:"讲师" },{job:"客服部"}] })
db.friend.find({$or:[{"name":"diaocan"},{age:{$gte:18}}]});

4.3.3 排序与分页

// sort skip limit
db.emp.find().sort({dep:1,salary:-1}).skip(5).limit(2)

4.3.4 嵌套查询

# 错误示例:无结果
db.student.find({grade:{redis:87,dubbo:90 });
# 错误示例:无结果
db.student.find({grade:{redis:87,dubbo:90,zookeper:85} })

# 基于复合属性查找 时必须包含其所有的值 并且顺序一至
db.student.find({grade:{redis:87,zookeper:85,dubbo:90} })

# 基于复合属性当中的指定值 查找。注:名称必须用双引号
db.student.find({"grade.redis":87});

db.student.find({"grade.redis":{"$gt":80}});

4.3.5 数组查询

db.subject.insertMany([
{_id:"001",name:"陈霸天",subjects:["redis","zookeper","dubbo"]},
{_id:"002",name:"张明明",subjects:["redis","Java","mySql"]},
{_id:"003",name:"肖炎炎",subjects:["mySql","zookeper","bootstrap"]},
{_id:"004",name:"李鬼才",subjects:["Java","dubbo","Java"]},
])

#无结果
db.subject.find({subjects:["redis","zookeper"]})
#无结果
db.subject.find({subjects:["zookeper","redis","dubbo"]})
# 与嵌套查询一样,必须是所有的值 并且顺序一至
db.subject.find({subjects:["redis","zookeper","dubbo"]})

# $all 匹配数组中包含该两项的值。注:顺序不作要求
db.subject.find({subjects:{"$all": ["redis","zookeper"]}})
注:
# 简化数组查询
db.subject.find({subjects:"redis"})
# 简化数组查询 ,匹配数组中存在任意一值。与$all相对应
db.subject.find({subjects:{$in: ["redis","zookeper"]}})

4.3.6 数组嵌套查询

#基础查询 ,必须查询全部,且顺序一至
db.subject2.find({subjects:{name:"redis",hour:12} })
#指定查询第一个数组 课时大于12
db.subject2.find({"subjects.0.hour":{$gt:12}})
#查询任科目 课时大于12
db.subject2.find({"subjects.hour":{$gt:12}})
# $elemMatch 元素匹配,指定属性满足,且不要求顺序一至
db.subject2.find({subjects:{$elemMatch:{name:"redis",hour:12}}})

# 数组中任意元素匹配 不限定在同一个对象当中
db.subject2.find({"subjects.name":"mysql","subjects.hour":120})

4.3.7 数组的删除与修改

修改

#设置值
db.emp.update({_id:1101} ,{ $set:{salary:10300} })
#自增
db.emp.update({_id:1101} ,{ $inc:{salary:200}})

#基于条件 更新多条数据
# 只会更新第一条
db.emp.update({"dep":"客服部"},{$inc:{salary:100}})
# 更新所有 匹配的条件
db.emp.updateMany({"dep":"客服部"},{$inc:{salary:100}})

删除

 // 基于查找删除
db.emp.deleteOne({_id:1101})
// 删除整个集合
db.project.drop()
// 删除库
db.dropDatabase()

5 应用操作

  1. mongoDB的聚合操作
  2. mongoDB的索引特性

5.1 mongoDB的聚合操作

5.1.1 知识点

  1. pipline聚合
  2. mapRedurce聚合

5.1.2 pipline与mapRedurce比较

   pipline速度快,但只能运行在单机上,适合数据量小的实时聚合操作
   mapRedurce可以运行在分布式节点,适合大数据量并且复杂的聚合分析操作

5.1.3 mpipline聚合

   mpipline聚合其特性是运行速度快,只能运行在单机上,并且对资源的使用有一定限制如下:

  • 单个的聚合操作耗费的内存不能超过20%
  • 返回的结果集大小在16M以内

5.1.4 语法说明

   aggredate方法接收任意多个参数,每个参数都是一个具体类别的聚合操作,通过参数的顺序组成一个执行链。每个操作执行完后将返回结果交给下一个操作。直到最后产生结果。

5.1.5 pipline相关运算符

match:匹配过滤聚合的数据<br>match:匹配过滤聚合的数据 <br> project:返回需要聚合的字段
$group:统计聚合数据,必须指定_id列

  • $max:求出最大值
  • $sum:求和
  • $avg:求平均值
  • $push:将结果插入至一个数组当中
  • $addToSet:将结果插入至一个数组当中,并去重
  • $first:取第一个值
  • last:取最后一个值last:取最后一个值 limit:用来限制MongoDB聚合管道返回的文档数
    skip:在聚合管道中跳过指定数量的文档,并返回余下的文档<br>skip:在聚合管道中跳过指定数量的文档,并返回余下的文档 <br> unwind:(flatmap)将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值
    $sort:将输入文档排序后输出

5.1.5.1 示例

$match 条件过滤

db.emp.aggregate({$match:{"job":"讲师"}})

$project 指定列返回

# 返回指定列_id自动带上
db.emp.aggregate({$match:{"job":"讲师"}},{$project:{"job":1,"salary":1}})

# 返回指定列,并修改列名
db.emp.aggregate({$match:{"job":"讲师"}},{$project:{"工作":"$job","薪水":"$salary"}})

$group操作(必须指定_id列)

# 基于工作分组,并求出薪水总和
db.emp.aggregate({$group:{_id:"$job",total:{$sum:"$salary"}}})
#求出薪水最大值
db.emp.aggregate({$group:{_id:"$job",total:{$max:"$salary"}}})
# 将所有薪水添加列表
db.emp.aggregate({$group:{_id:"$job",total:{$push:"$salary"}}})
# 将所有薪水添加列表 并去重
db.emp.aggregate({$group:{_id:"$job",total:{$addToSet:"$salary"}}})

聚合操作可以任意个数和顺序的组合

# 二次过滤
db.emp.aggregate({$match:{"job":"讲师"}},{$project:{"工作":"$job","薪水":"$salary"}},{$match:{"薪水":{$gt:8000}}})

$skip 与 $limit 跳转 并限制返回数量   
db.emp.aggregate({$group:{_id:"$job",total:{$push:"$salary"}}},{$limit:4},{$skip:2});

#sort 排序 
db.emp.aggregate( {$project:{"工作":"$job","salary":1}},{$sort:{"salary":1,"工作":1}});

#unwind 操作,将数组拆分成多条记录
db.emp.aggregate({$group:{_id:"$job",total:{$push:"$salary"}}},{$unwind:"$total"});

5.1.4 mapRedurce聚合

   mapRedurce非常适合实现非常复杂,并且数量大的聚合计算,其可运行在多台节点上实行分布式计算。

5.1.4.1 mapRedurce概念

   MapReduce 现大量运用于hadoop大数据计算当中,其最早来自于google 的一遍论,解决大PageRank搜索结果排序的问题。其大至原理如下:

5.1.4.2 mongodb中mapRedurce的使用流程

  1. 创建Map函数,
  2. 创建Redurce函数
  3. 将map、Redurce 函数添加至集合中,并返回新的结果集
  4. 查询新的结果集

5.1.4.3 示例

5.1.4.3.1 基础示例
//创建map对象
var map1 = function(){
    emit(this.job,this.name); // 内置函数 key,value
}

//创建reduce对象
 var reduce1=function(job,count){
     return Array.sum(count);
 }
 
// 执行mapReduce 任务 并将结果放到新的集合 result 当中
db.emp.mapReduce(map1,reduce1,{out:"result"}).find()

//查询新的集合
db.result.find()

使用复合对象作为key

# 使用复合对象作为key
var map2=function (){
	emit({"job":this.job,"dep":this.dep},{"name":this.name,"dep":this.dep});
 }
 
var reduce2=function(key,values){
    return values.length;
} 

db.emp.mapReduce(map2,reduce2,{out:"result2"}).find()

调式mapReduce执行
var emit=function(key,value){
    print(key+":"+value);
}

六 mongoDB的索引特性

6.1 知识点

  1. 索引的基础概念
  2. 单键索引
  3. 多键索引
  4. 复合索引
  5. 过期索引
  6. 全文索引

6.2 索引的基础概念

查看执行计划:
db.emp.find({"salary":{$gt:500}}).explain()

创建简单索引
#创建索引
db.emp.createIndex({salary:1})
#查看索引
db.emp.getIndexes()
#查看执行计划
db.emp.find({"salary":{$gt:500}}).explain()

6.2.1 单键索引

单个例上创建索引
db.subject.createIndex({"name":1})

# 嵌套文档中的列创建索引
db.subject.createIndex({"grade.redis":1})

#整个文档创建索引
db.subject.createIndex({"grade":1})

6.2.2 多键索引

创建多键索引
db.subject.createIndex({"subjects":1})

6.2.3 复合索引(组合索引)

创建复合索引
db.emp.createIndex( { "job":1,"salary":-1   }  )

查看执行计划:
db.emp.find({"job":"讲师", "salary":{$gt:500}}).explain()
db.emp.find({"job":"讲师"}).explain()
db.emp.find({"salary":{$gt:500}}).explain()

6.2.4 复合索引在排序中的应用

db.emp.find({}).sort({"job":1, "salary":-1}).explain()

db.emp.find({}).sort({"job":-1, "salary":1}).explain()

db.emp.find({}).sort({"job":-1, "salary":-1}).explain()

db.emp.find({}).sort({"job":1, "salary":1}).explain()

job_1_salary_-1

db.emp.find({"job":"讲师","salary":{$gt:5000}}).explain() // 走索引
db.emp.find({"salary":{$gt:5000},"job":"讲师"}).explain()  // 走索引
db.emp.find({"job":"讲师"}).explain()    // 走索引
db.emp.find({"salary":{$gt:5000}}).explain()

6.2.5 排序 场景

db.emp.find({}).sort({"job":1,"salary":-1}).explain()// 完全匹配  ==>走索引
db.emp.find({}).sort({"job":-1,"salary":1}).explain()//完全不匹配 ==>走索引
db.emp.find({}).sort({"job":1,"salary":1}).explain()// 一半匹配 ==>不走索引
db.emp.find({}).sort({"job":-1,"salary":-1}).explain()// 一半匹配 ==>不走索引
db.emp.find({}).sort({"job":-1}).explain()	 //  ==>走索引
db.emp.find({}).sort({"salary":-1}).explain()      // ==>不走索引

6.2.4 过期索引

   过期索引存在一个过期的时间,如果时间过期,相应的数据会被自动删除

6.2.4.1 示例

#插入数据
db.log.insert({"title":"this is logger info","createTime":new Date()})
#创建过期索引
db.log.createIndex({"createTime":1},{expireAfterSeconds:10})

6.2.5 全文索引

创建全文索引
db.project.createIndex( {"name":"text","description":"text"})

使用全文索引进行查询
db.project.find({$text:{$search:"java dubbo"}})
-用于屏蔽关键字 
db.project.find({$text:{$search:"java -dubbo"}})

短语查询,\" 包含即可
db.project.find({$text:{$search:"\"Apache Dubbo\""}})

中文查询
db.project.find({$text:{$search:"阿里 开源"}})

7 MongoDB复制集特性

7.1 知识点

  1. 复制集群的架构
  2. 复制集群搭建
  3. 复制集群的选举配置

7.2 复制集群的架构

image.png

7.3 复制集群搭建基础示例

7.3.1 主节点配置

dbpath=/data/mongo/master
port=27017
fork=true
logpath=master.log
replSet=tulingCluster

7.3.2 从节点配置

dbpath=/data/mongo/slave
port=27018
fork=true
logpath=slave.log
replSet=tulingCluster

#子节点配置2
dbpath=/data/mongo/slave2
port=27019
fork=true
logpath=slave2.log
replSet=tulingCluster
  • 分别启动三个节点
  • 进入其中一个节点

7.3.3 集群复制配置管理

#查看复制集群的帮助方法
rs.help()

7.3.4 添加配置

// 声明配置变量
var cfg ={"_id":"tuling",
    "members":[
            {"_id":1,"host":"127.0.0.1:27017"},
            {"_id":2,"host":"127.0.0.1:27018"}			]
}

// 初始化配置
rs.initiate(cfg)
// 查看集群状态
rs.status()

7.3.5 变更节点示例

// 插入新的复制节点
rs.add("127.0.0.1:27019")
// 删除slave 节点
rs.remove("127.0.0.1:27019")

7.4 演示复制状态

  1. 进入主节点客户端
  2. 插入数据
  3. 进入从节点查看数据
  4. 尝试在从节点查看数据 注:默认节点下从节点不能读取数据。调用 rs.slaveOk() 解决。

7.5 复制集群选举操作

   为了保证高可用,在集群当中如果主节点挂掉后,会自动 在从节点中选举一个 重新做为主节点。

  • 演示节点的切换操作
  • kill主节点
  • 进入从节点查看集群状态

7.5.1 选举的原理

   在mongodb中通过在集群配置中的rs.属性值大小来决定选举谁作为主节点,通时也可以设置arbiterOnly为true表示作为裁判节点用于执行选举操作,该配置下的节点永远不会被选举为主节点和从节点

7.5.1.1 示例

重新配置节点

var cfg ={"_id":"tuling",
  "protocolVersion" : 1,
  "members":[
        {"_id":1,"host":"127.0.0.1:27017","priority":10},
        {"_id":2,"host":"127.0.0.1:27018","priority":0},
        {"_id":3,"host":"127.0.0.1:27019","priority":5},
        {"_id":4,"host":"127.0.0.1:27020","arbiterOnly":true}
   ]
}

// 重新装载配置,并重新生成集群节点。
rs.reconfig(cfg)
//重新查看集群状态
rs.status()

节点说明 primary节点:可以查询和新增数据 secondary节点:只能查询不能新增,基于priority权重可以被选为主节点 rbiter节点:不能查询数据和新增数据,不能变成主节点

8 MongoDB分片操作

8.1 知识点

  1. 分片的概念
  2. mongodb中的分片架构
  3. 分片示例

8.2 为什么需要分片

   随着数据的增长,单机实例的瓶颈是很明显的。可以通过复制的机制应对压力,但mongodb中单个集群的 节点数量限制到了12个以内,所以需要通过分片进一步横向扩展。此外分片也可节约磁盘的存储。

8.3 mongodb中的分片架构

image.png

8.4 分片中的节点说明

  • 路由节点(mongos):用于分发用户的请求,起到反向代理的作用。
  • 配置节点(config):用于存储分片的元数据信息,路由节基于元数据信息 决定把请求发给哪个分片。(3.4版本之后,该节点,必须使用复制集。)
  • 分片节点(shard):用于实际存储的节点,其每个数据块默认为64M,满了之后就会产生新的数据库。

8.5 分片示例流程

1.配置 并启动config 节点集群
   a.配置集群信息
2.配置并启动2个shard 节点
3.配置并启动路由节点
   a.添加shard 节点
   b.添加shard 数据库
   c.添加shard 集合
4.插入测试数据
   a.检查数据的分布
5.插入大批量数据查看shard 分布
   a.设置shard 数据块为一M
   b.插入10万条数据

8.6 配置 并启动config节点集群

# 节点1 config1-37017.conf
dbpath=/data/mongo/config1
port=37017
fork=true
logpath=logs/config1.log
replSet=configCluster
configsvr=true

# 节点2 config2-37018.conf
dbpath=/data/mongo/config2
port=37018
fork=true
logpath=logs/config2.log
replSet=configCluster
configsvr=true

8.7 进入shell并添加config集群配置

var cfg ={"_id":"configCluster",
  "protocolVersion" : 1,
  "members":[
        {"_id":0,"host":"127.0.0.1:37017"},
        {"_id":1,"host":"127.0.0.1:37018"}
  ]
}

// 重新装载配置,并重新生成集群。
rs.initiate(cfg)

# 配置 shard 节点集群==============
# 节点1 shard1-47017.conf
dbpath=/data/mongo/shard1
port=47017
fork=true
logpath=logs/shard1.log
shardsvr=true

# 节点2 shard2-47018.conf
dbpath=/data/mongo/shard2
port=47018
fork=true
logpath=logs/shard2.log
shardsvr=true

配置 路由节点 mongos ==============
# 节点 route-27017.conf
port=27017
bind_ip=0.0.0.0
fork=true
logpath=logs/route.log
configdb=conf/127.0.0.1:37017,127.0.0.1:37018

// 添加分片节点
sh.status()
sh.addShard("127.0.0.1:47017");
sh.addShard("127.0.0.1:47018");

为数据库开启分片功能
sh.enableSharding("tuling")
为指定集合开启分片功能
sh.shardCollection("tuling.emp",{"_id":1})
 
修改分片大小
use config
db.settings.find()
db.settings.save({_id:"chunksize",value:1})

尝试插入1万条数据:
for(var i=1;i<=100000;i++){
    db.emp.insert({"_id":i,"name":"copy"+i});
}

db.emp.createIndex({_id: 'hashed'})

9 MongoDB用户权限管理

// 创建管理员用户
use admin;
db.createUser({"user":"admin","pwd":"123456","roles":["root"]})
#验证用户信息
db.auth("admin","123456")
#查看用户信息
db.getUsers() 
# 修改密码
db.changeUserPassword("admin","123456")

   以auth 方式启动mongod,需要添加auth=true 参数 ,mongdb 的权限体系才会起作用:

#以auth 方向启动mongod (也可以在mongo.conf 中添加auth=true 参数)
./bin/mongod -f conf/mongo.conf --auth
# 验证用户
use admin;
db.auth("admin","123456")

创建只读用户
db.createUser({"user":"dev","pwd":"123456","roles":["read"]})

重新登陆 验证用户权限
use luban  ;
db.auth("dev","123456")