MongoDB深入学习 - 一

一、MongoDB体系结构

1.1.NoSQL和MongoDB

NoSQL=Not Only SQL,支持类似SQL的功能,性能较高

NoSQL数据库四大家族:

  • 列存储 Hbase,

  • 键值(Key-Value)存储 Redis,

  • 图像存储 Neo4j,

  • 文档存储MongoDB

MongoDB 是一个基于分布式文件存储的数据库,是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库中功能最丰富、最像关系数据库的。可以为 WEB 应用提供可扩展、高性能、易部署的数据存储解决方案。

1.2.MongoDB体系结构

MongoDB体系结构

1.3.MongoDB 和RDBMS(关系型数据库)对比
RDBMS MongoDB
database(数据库) database(数据库)
table (表) collection( 集合)
row( 行) document( BSON 文档)
column (列) field (字段)
index(唯一索引、主键索引) index (支持地理位置索引、全文索引 、哈希索引)
join (主外键关联) embedded Document (嵌套文档)
primary key(指定1至N个列做主键) primary key (指定_id field做为主键)
1.4.BSON

BSON是一种类json的一种二进制形式的存储格式,简称Binary JSON,它和JSON一样,支持内嵌的文档对象和数组对象,但是BSON有JSON没有的一些数据类型,如Date和Binary Data类型。BSON可以做为网络数据交换的一种存储形式,是一种schema-less的存储形式。

优点:灵活性高

缺点:空间利用率不是很理想

BSON有三个特点:轻量性、可遍历性、高效性

1.5.BSON在MongoDB中的使用

MongoDB使用了BSON这种结构来存储数据和网络数据交换。

MongoDB中Document 中 可以出现的数据类型,如下图所示:

BSON

1.6.MongoDB在Linux的安装
1.下载社区版 MongoDB 4.1.3
	去官网下载对应的MongoDB 然后上传到Linux虚拟机
2.将压缩包解压即可
	tar -zxvf MongoDB-linux-x86_64-4.1.3.tgz
3.启动
	./bin/mongod
4.指定配置文件方式的启动
	./bin/mongod -f mongo.conf
配置文件样例:
dbpath=/data/mongo/
port=27017
bind_ip=0.0.0.0
fork=true
logpath = /data/mongo/MongoDB.log
logappend = true
auth=false
1.7.MongoDB启动和参数说明
参数  说明
dbpath 数据库目录,默认/data/db
port 监听的端口,默认27017
bind_ip 监听IP地址,默认全部可以访问
fork 是否已后台启动的方式登陆
logpath 日志路径
logappend 是否追加日志
auth 是开启用户密码登陆
config 指定配置文件
1.8.mongo shell 的启动
启动mongo shell
	./bin/mongo 
指定主机和端口的方式启动
	./bin/mongo --host=主机IP --port=端口

二、MongoDB命令

2.1.MongoDB的基本操作
查看数据库
 	show dbs;
切换数据库 如果没有对应的数据库则创建
 	use 数据库名;
创建集合 
 	db.createCollection("集合名")
查看集合
 	show tables;
 	show collections;
删除集合
 	db.集合名.drop();
删除当前数据库
 	db.dropDatabase();
2.2.MongoDB集合数据操作(CURD)

2.2.1.数据添加

1. 插入单条数据 db.集合名.insert(文档)
	文档的数据结构和JSON基本一样。
	所有存储在集合中的数据都是BSON格式。
	BSON是一种类json的一种二进制形式的存储格式,简称Binary JSON。
2. 例如:
	db.lg_resume_preview.insert({name:"张晓峰",birthday:new ISODate("2000-07-01"),expectSalary:15000,gender:0,city:"bj"})
	没有指定 _id 这个字段 系统会自动生成 当然我们也可以指定 _id
	( _id 类型是ObjectId 类型是一个12字节 BSON 类型数据,有以下格式:
	前4个字节表示时间戳 ObjectId("对象Id字符串").getTimestamp() 来获取
	接下来的3个字节是机器标识码紧接的两个字节由进程id组成(PID)最后三个字节是随机		数。)
3. 插入多条数据
	db.集合名.insert([文档,文档])

2.2.2.数据查询

比较条件查询

db.集合名.find(条件)

条件查询

逻辑条件查询

and 条件
MongoDB 的 find() 方法可以传入多个键(key),每个键(key)以逗号隔开,即常规 SQL 的 AND 条件
 	db.集合名.find({key1:value1, key2:value2}).pretty()
  
or 条件
 	db.集合名.find({$or:[{key1:value1}, {key2:value2}]}).pretty()
not 条件 
 	db.集合名.find({key:{$not:{$操作符:value}}).pretty()

分页查询

db.集合名.find({条件}).sort({排序字段:排序方式})).skip(跳过的行数).limit(一页显示多少数据)

2.2.3.数据更新 调用update

$set :设置字段值
$unset :删除指定字段
$inc:对修改的值进行自增
db.集合名.update(
 	<query>,
 	<update>,
 	{
  		upsert: <boolean>,
  		multi: <boolean>,
  		writeConcern: <document>
 	}
)

参数说明:
query : update的查询条件,类似sql update查询内where后面的。
update : update的对象和一些更新的操作符(如$set,$inc...)等,也可以理解为sql update中set后面的
upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。
multi : 可选,MongoDB 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。
writeConcern :可选,用来指定mongod对写操作的回执行为比如写的行为是否需要确认。

举例:
db.集合名.update({条件},{$set:{字段名:值}},{multi:true})
writeConcern 包括以下字段:
{ w: <value>, j: <boolean>, wtimeout: <number> }
 w:指定写操作传播到的成员数量
比如:
w=1(默认):则要求得到写操作已经传播到独立的Mongod实例或副本集的primary成员的确认
w=0:则不要求确认写操作,可能会返回socket exceptions和 networking errors
w="majority":要求得到写操作已经传播到大多数具有存储数据具有投票的(data-bearing voting
)成员(也就是 members[n].votes 值大于0的成员)的确认
 j:要求得到Mongodb的写操作已经写到硬盘日志的确认
比如:
j=true:要求得到Mongodb(w指定的实例个数)的写操作已经写到硬盘日志的确认。j=true本身并不保证
因为副本集故障而不会回滚。
 wtimeout:指定write concern的时间限制,只适用于w>1的情况
wtimeout在超过指定时间后写操作会返回error,即使写操作最后执行成功,当这些写操作返回时,
MongoDB不会撤消在wtimeout时间限制之前执行成功的数据修改。
如果未指定wtimeout选项且未指定write concern级别,则写入操作将无限期阻止。 指定wtimeout值
为0等同于没有wtimeout选项。

2.2.4.数据删除

db.collection.remove(
 <query>,
 {
  	justOne: <boolean>,
  	writeConcern: <document>
 }
)

参数说明:
query :(可选)删除的文档的条件。
justOne : (可选)如果设为 true 或 1,则只删除一个文档,如果不设置该参数,或使用默认值
false,则删除所有匹配条件的文档。
writeConcern :(可选)用来指定mongod对写操作的回执行为。
2.3.MongoDB 聚合操作

2.3.1.聚合操作简介

聚合是MongoDB的高级查询语言,它允许我们通过转化合并由多个文档的数据来生成新的在单个文档里不存在的文档信息。一般都是将记录按条件分组之后进行一系列求最大值,最小值,平均值的简单操作,也可以对记录进行复杂数据统计,数据挖掘的操作。

2.3.2.MongoDB 聚合操作分类

  • 单目的聚合操作(Single Purpose Aggregation Operation)

    单目的聚合命令常用的有:count() 和 distinct()

    db.lg_resume_preview.find({}).count()
    
  • 聚合管道(Aggregation Pipeline)

    db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)
    如:
    db.lg_resume_preview.aggregate([{$group:{_id:"$city",city_count:{$sum:1}}}])
    

    MongoDB中聚合(aggregate)主要用于统计数据(诸如统计平均值,求和等),并返回计算后的数据结果。

    表达式:处理输入文档并输出。表达式只能用于计算当前聚合管道的文档,不能处理其它的文档。

    表达式

    MongoDB 中使用 db.COLLECTION_NAME.aggregate([{},...]) 方法来构建和使用聚合管道,每个文档通过一个由一个或者多个阶段(stage)组成的管道,经过一系列的处理,输出相应的结果。

    MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。

    常用操作:

    • $group:将集合中的文档分组,可用于统计结果。
    • $project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。
    • $match:用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作。
    • $limit:用来限制MongoDB聚合管道返回的文档数。
    • $skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
    • $sort:将输入文档排序后输出。
    • $geoNear:输出接近某一地理位置的有序文档。
    db.lg_resume_preview.aggregate(
    [{$group : {_id: "$city", avgSal:{$avg:"$expectSalary"}}},
    {$project : {city: "$city", salary : "$avgSal"}}
    ])  
    
    db.lg_resume_preview.aggregate(
    [{$group:{_id: "$city",count:{$sum : 1}}},
    {$match:{count:{$gt:1}}}
    ])
    
  • MapReduce 编程模型

    Pipeline查询速度快于MapReduce,但是MapReduce的强大之处在于能够在多台Server上并行执行复杂的聚合逻辑。不允许Pipeline的单个聚合操作占用过多的系统内存,如果一个聚合操作消耗20%以上的内存,那么MongoDB直接停止操作,并向客户端输出错误消息。

    MapReduce是一种计算模型,简单的说就是将大批量的工作(数据)分解(MAP)执行,然后再将结果合并成最终结果(REDUCE)。

    >db.collection.mapReduce(
     	function() {emit(key,value);}, //map 函数
     	function(key,values) {return reduceFunction},  //reduce 函数
     	{
      		out: collection,
      		query: document,
      		sort: document,
      		limit: number,
      		finalize: <function>,
      		verbose: <boolean>
     	}
    )
    

    参数说明:

    • map:是JavaScript 函数,负责将每一个输入文档转换为零或多个文档,生成键值对序列,作为reduce 函数参数

    • reduce:是JavaScript 函数,对map操作的输出做合并的化简的操作(将key-value变成key-values,也就是把values数组变成一个单一的值value)

    • out:统计结果存放集合

    • query: 一个筛选条件,只有满足条件的文档才会调用map函数。

    • sort: 和limit结合的sort排序参数(也是在发往map函数前给文档排序),可以优化分组机制

    • limit: 发往map函数的文档数量的上限(要是没有limit,单独使用sort的用处不大)

    • finalize:可以对reduce输出结果再一次修改

    • verbose:是否包括结果信息中的时间信息,默认为fasle

    db.lg_resume_preview.mapReduce(
     function() { emit(this.city,this.expectSalary); },
     function(key, value) {return Array.avg(value)},
     { 
      query:{expectSalary:{$gt: 15000}},
      out:"cityAvgSal"
     }
    )
    

三、MongoDB索引Index

3.1.索引介绍

索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。

索引的作用在于快速找到所需的内容。索引目标是提高数据库的查询效率,没有索引的话,查询会进行全表扫描(scan every document in a collection),数据量大时严重降低了查询效率。

3.2.索引类型

3.2.1.单键索引 (Single Field)

MongoDB支持所有数据类型中的单个字段索引,并且可以在文档的任何字段上定义。

单个例上创建索引:
db.集合名.createIndex({"字段名":排序方式})

特殊的单键索引 过期索引 TTL ( Time To Live)

TTL索引是MongoDB中一种特殊的索引, 可以支持文档在一定时间之后自动过期删除,目前TTL索引只能在单字段上建立,并且字段类型必须是日期类型。

db.集合名.createIndex({"日期字段":排序方式}, {expireAfterSeconds: 秒数})

3.2.2.复合索引(Compound Index)

复合索引支持基于多个字段的索引,这扩展了索引的概念并将它们扩展到索引中的更大域。

制作复合索引时要注意的重要事项包括:字段顺序与索引方向。

db.集合名.createIndex( { "字段名1" : 排序方式, "字段名2" : 排序方式 } )

3.2.3.多键索引(Multikey indexes)

针对属性包含数组数据的情况,MongoDB支持针对数组中每一个element创建索引,Multikey indexes支持strings,numbers和nested documents

3.2.4.地理空间索引(Geospatial Index)

针对地理空间坐标数据创建索引。

2dsphere索引,用于存储和查找球面上的点

2d索引,用于存储和查找平面上的点

db.company.insert(
 {
  loc : { type: "Point", coordinates: [ 116.482451, 39.914176 ] },
  name: "大望路地铁",
  category : "Parks"
 }
)
db.company.ensureIndex( { loc : "2dsphere" } )
参数不是1或-1,为2dsphere 或者 2d。还可以建立组合索引。
db.company.find({
 "loc" : {
   "$geoWithin" : {
    "$center":[[116.482451,39.914176],0.05]
   }
 }
})

3.2.5.全文索引

MongoDB提供了针对string内容的文本查询,Text Index支持任意属性值为string或string数组元素的索引查询。

注意:一个集合仅支持最多一个Text Index,中文分词不理想 推荐ES。

db.集合.createIndex({"字段": "text"})
db.集合.find({"$text": {"$search": "coffee"}})

3.2.6 哈希索引 Hashed Index

针对属性的哈希值进行索引查询,当要使用Hashed index时,MongoDB能够自动的计算hash值,无需程序计算hash值。

注:hash index仅支持等于查询,不支持范围查询。

db.集合.createIndex({"字段": "hashed"})
3.3.索引和explain 分析

3.3.1.索引管理

创建索引并在后台运行

db.COLLECTION_NAME.createIndex({"字段":排序方式}, {background: true});

获取针对某个集合的索引

db.COLLECTION_NAME.getIndexes()

索引的大小

db.COLLECTION_NAME.totalIndexSize()

索引的重建

db.COLLECTION_NAME.reIndex()

索引的删除

db.COLLECTION_NAME.dropIndex("INDEX-NAME")
db.COLLECTION_NAME.dropIndexes()
注意: _id 对应的索引是删除不了的

3.3.2.explain 分析

explain()参数设置:

  • queryPlanner:queryPlanner是默认参数,具体执行计划信息参考下面的表格。

  • executionStats:executionStats会返回执行计划的一些统计信息(有些版本中和
    allPlansExecution等同)。

  • allPlansExecution:allPlansExecution用来获取所有执行计划,结果参数基本与上文相同。

    queryPlanner 参数和executionStats的拼接
    
3.4.慢查询分析
  • 开启内置的查询分析器,记录读写操作效率

    db.setProfilingLevel(n,m),n的取值可选0,1,2

    • 0表示不记录
    • 1表示记录慢速操作,如果值为1,m必须赋值单位为ms,用于定义慢速查询时间的阈值
    • 2表示记录所有的读写操作
  • 查询监控结果

    db.system.profile.find().sort({millis:-1}).limit(3)

  • 分析慢速查询

    应用程序设计不合理、不正确的数据模型、硬件配置问题,缺少索引等

  • 解读explain结果 确定是否缺少索引

3.5.MongoDB 索引底层实现原理分析

MongoDB使用B-树,所有节点都有Data域,只要找到指定索引就可以进行访问,
单次查询从结构上来看要快于MySql。

B-树是一种自平衡的搜索树,形式很简单:

B-树

B-树的特点:

  • 多路 非二叉树
  • 每个节点 既保存数据 又保存索引
  • 搜索时 相当于二分查找

B+树是B-树的变种

B+树

B+ 树的特点:

  • 多路非二叉
  • 只有叶子节点保存数据
  • 搜索时 也相当于二分查找
  • 增加了 相邻节点指针

从上面可看出最核心的区别主要有俩,一个是数据的保存位置,一个是相邻节点的指向。就是这俩造成了MongoDB和MySql的差别。

(1)B+树相邻接点的指针可以大大增加区间访问性,可使用在范围查询等,而B-树每个节点 key 和data 在一起 适合随机读写 ,而区间查找效率很差。
(2)B+树更适合外部存储,也就是磁盘存储,使用B-结构的话,每次磁盘预读中的很多数据是用不上的数据。因此,它没能利用好磁盘预读的提供的数据。由于节点内无 data 域,每个节点能索引的范围更大更精确。
(3)注意这个区别相当重要,是基于(1)(2)的,B-树每个节点即保存数据又保存索引 树的深度小,所以磁盘IO的次数很少,B+树只有叶子节点保存,较B树而言深度大磁盘IO多,但是区间访问比较好。

四、MongoDB 应用实战
4.1.MongoDB的适用场景
  • 网站数据:Mongo 非常适合实时的插入,更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性。
  • 缓存:由于性能很高,Mongo 也适合作为信息基础设施的缓存层。在系统重启之后,由Mongo搭建的持久化缓存层可以避免下层的数据源过载。
  • 大尺寸、低价值的数据:使用传统的关系型数据库存储一些大尺寸低价值数据时会比较浪费,在此之前,很多时候程序员往往会选择传统的文件进行存储。
  • 高伸缩性的场景:Mongo 非常适合由数十或数百台服务器组成的数据库,Mongo 的路线图中已经包含对MapReduce 引擎的内置支持以及集群高可用的解决方案。
  • 用于对象及JSON 数据的存储:Mongo 的BSON 数据格式非常适合文档化格式的存储及查询。
4.2.MongoDB的行业具体应用场景
  • 游戏场景,使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、更新。
  • 物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以 MongoDB 内嵌数组的形式来存储,一次查询就能将订单所有的变更读取出来。
  • 社交场景,使用 MongoDB 存储存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附近的人、地点等功能。
  • 物联网场景,使用 MongoDB 存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析。
  • 直播,使用 MongoDB 存储用户信息、礼物信息等。
4.3.如何抉择是否使用MongoDB

4.4.Java 访问MongoDB
  • maven 依赖

    <dependency>
      <groupId>org.mongodb</groupId>
      <artifactId>mongo-java-driver</artifactId>
      <version>3.10.1</version>
    </dependency>
    
  • 文档添加

    MongoClient mongoClient = new MongoClient("192.168.211.133", 37017);
    MongoDatabase database = mongoClient.getDatabase("lg_resume");
    MongoCollection<Document> collection =
    database.getCollection("lg_resume_preview");
    Document document = Document.parse(
      "{name:'lisi',city:'bj',birth_day:new ISODate('2001-08-
    01'),expectSalary:18000}");
    collection.insertOne(document );
    mongoClient.close();
    
  • 文档查询

    MongoClient mongoClient = new MongoClient("192.168.211.133", 37017);
    MongoDatabase database = mongoClient.getDatabase("lg_resume");
    MongoCollection<Document> collection =
    database.getCollection("lg_resume_preview");
    Document sdoc=new Document();
    //按expectSalary倒序
    sdoc.append("expectSalary", -1);
    FindIterable<Document> findIterable = collection.find().sort(sdoc);
    for (Document document : findIterable) {
    System.out.println(document);
    }
    mongoClient.close();
    
  • 文档查询过滤

    MongoClient mongoClient = new MongoClient("192.168.211.133", 37017);
    MongoDatabase database = mongoClient.getDatabase("lg_resume");
    MongoCollection<Document> collection =
    database.getCollection("lg_resume_preview");
    Document sdoc=new Document();
    //按expectSalary倒序
    sdoc.append("expectSalary", -1);
    FindIterable<Document> findIterable =
    collection.find(Filters.gt("expectSalary",21000)).sort(sdoc);
    for (Document document : findIterable) {
    System.out.println(document);
    }
    mongoClient.close();
    
4.5.Spring 访问MongoDB

第1步:基于maven新建工程 导入依赖的包

<dependency>
  <groupId>org.springframework.data</groupId>
  <artifactId>spring-data-mongodb</artifactId>
  <version>2.0.9.RELEASE</version>
</dependency>

第2步:在配置文件中配置 MongoTemplate

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:context="http://www.springframework.org/schema/context"
   xmlns:mongo="http://www.springframework.org/schema/data/mongo"
   xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/data/mongo
http://www.springframework.org/schema/data/mongo/spring-mongo.xsd">
  <!-- 构建MongoDb工厂对象 -->
  <mongo:db-factory id="mongoDbFactory"
    client-uri="mongodb://192.168.211.133:37017/lg_resume">
  </mongo:db-factory>
  <!-- 构建 MongoTemplate 类型的对象 -->
  <bean id="mongoTemplate"
class="org.springframework.data.mongodb.core.MongoTemplate">
    <constructor-arg index="0" ref="mongoDbFactory"></constructor-arg>
  </bean>
  <!-- 开启组件扫描 -->
  <context:component-scan base-package="com.lagou"></context:component-scan>
</beans>

第3步:DAO 实现类注入 MongoTemplate 完成增删改查

@Autowired
protected MongoTemplate mongoTemplate;

第4步: 从Spring容器中获取DAO对象 进行测试 (注意:要开启组件扫描)

4.6.Spring Boot 访问 MongoDB

4.6.1.MongoTemplate 的方式

第1步:基于maven新建springboot工程

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-mongodb</artifactId>
  <version>2.2.2.RELEASE</version>
</dependency>

第2步: 配置文件application.properties

spring.data.mongodb.host=192.168.211.133
spring.data.mongodb.port=37017
spring.data.mongodb.database=lg_resume

第3步: DAO 实现类 注入 MongoTemplate 完成增删改查

@Autowired
protected MongoTemplate mongoTemplate;

第4步: 从Spring容器中获取DAO对象 进行测试

4.6.2.MongoRepository 的方式

第1步:基于maven新建springboot工程

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-mongodb</artifactId>
  <version>2.2.2.RELEASE</version>
</dependency>

第2步: 配置文件application.properties

spring.data.mongodb.host=192.168.211.133
spring.data.mongodb.port=37017
spring.data.mongodb.database=lg_resume

第3步:编写实体类 并在实体类上打@Document(“集合名”)

第4步:编写 Repository 接口 继承 MongoRepository

​ 方法具体参考:https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation

​ 如果内置方法不够用 就自己定义 如:定义find|read|get 等开头的方法进行查询

第5步: 从Spring容器中获取Repository对象 进行测试

posted @ 2021-01-26 14:44  郭靖宇  阅读(427)  评论(0)    收藏  举报