前言
文章内容输出来源:拉勾教育Java高薪训练营;
NoSQL=Not Only SQL,支持类似SQL的功能, 与Relational Database相辅相成。其性能较高,不使用SQL意味着没有结构化的存储要求(SQL为结构化的查询语句),没有约束之后架构更加灵活。
NoSQL数据库四大家族 列存储 Hbase,键值(Key-Value)存储 Redis,图像存储 Neo4j,文档存储 MongoDB
MongoDB 是一个基于分布式文件存储的数据库,由 C++ 编写,可以为 WEB 应用提供可扩展、高性能、易部署的数据存储解决方案。
MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库中功能最丰富、最像关系数据库的。在高负载的情况下,通过添加更多的节点,可以保证服务器性能。
MongoDB体系结构

MongoDB使用了BSON这种结构来存储数据和网络数据交换。那 BSON 是什么呢?
BSON是一种类json的一种二进制形式的存储格式,简称Binary JSON,它和JSON一样,支持内嵌的文档对象和数组对象,但是BSON有JSON没有的一些数据类型,如Date和Binary Data类型。BSON可以做为网络数据交换的一种存储形式,是一种schema-less的存储形式,它的优点是灵活性高,但它的缺点是空间利用率不是很理想。{key:value,key2:value2} 这是一个BSON的例子,其中key是字符串类型,后面的value值,它的类型一般是字符串,double,Array,ISODate等类型。
BSON有三个特点:轻量性、可遍历性、高效性
MongoDB把 BSON 这种格式转化成一文档这个概念(Document),这里的一个Document也可以理解成关系数据库中的一条记录(Record),只是这里的Document的变化更丰富一些,如Document可以嵌套。
MongoDB中Document 中 可以出现的数据类型

安装
官网
https://www.mongodb.com/

选择自己合适的下载。

下载之后安装,我这里下载的是 linxu 版本。放到服务器上之后解压
tar -zxvf mongodb-linux-x86_64-4.1.3.tgz

编写 mongo.conf
创建配置文件,配置内容如下。指定路径端口和访问ip 等等配置。
dbpath=/data/mongo/
port=27017
bind_ip=0.0.0.0
fork=true
logpath = /data/mongo/MongoDB.log
logappend = true
auth=false
参数说明:
参数 | 说明 |
---|
启动
./mongod -f mongo.conf

说明启动成功啦。
连接
我们mongo 服务已经启动了,我们怎么像进入 mysql 进入mongo 数据库呢?
#默认配置启动
./bin/mongo
#指定主机和端口的方式启动
./bin/mongo --host=127.0.0.1 --port 27017

我们可以通过可视化工具来使用,类似于MySQL 的 navicat。
mongobooster下载地址:
https://nosqlbooster.com/downloads

下载安装完之后,连接mongo 数据库就可以了

Mongo 命令
MongoDB 的基本操作
库级别
1、查看数据库
show dbs;
2、创建数据库(切换数据库)
use 数据库名;
eg:
use test
3、删除库
#删除当前数据库
db.dropDatabase();
表级别
1、创建集合
db.createCollection("集合名")
2、查看集合
show tables;
show collections;
3、删除集合
db.集合名.drop();
eg:
db.user.drop();

集合数据操作
数据添加
插入单条数据
db.集合名.insert(文档)
文档的数据结构和JSON基本一样。所有存储在集合中的数据都是BSON格式。 BSON是一种类json的一种二进制形式的存储格式,简称Binary JSON。
eg:
db.people_table.insert({name:"张三",birthday:"2000-09-18",salary:15000,gender:0,city:"深圳"})
没有指定 _id 这个字段 系统会自动生成 当然我们也可以指定 _id( _id 类型是ObjectId 类型是一个12字节 BSON 类型数据,有以下格式:前4个字节表示时间戳 ObjectId("对象Id字符串").getTimestamp() 来获取 。接下来的3个字节是机器标识码,紧接的两个字节由进程id组成(PID)。最后三个字节是随机数。)
批量插入:
db.集合名.insert([文档,文档])
eg:
db.people_table.insert(
[{name:"李四",birthday:"2000-09-18",salary:15000,gender:0,city:"深圳"},
{name:"王五",birthday:"2020-09-18",salary:20000,gender:0,city:"上海"},
{name:"王五",birthday:"2010-09-18",salary:30000,gender:0,city:"北京"}
])

查询数据
db.集合名.find(条件)

#等于
db.people_table.find({salary:15000,name:'李四'})
#小于
db.people_table.find({salary:{$lt:20000}})
#小于等于
db.people_table.find({salary:{$lte:20000}})
#大于等于
db.people_table.find({salary:{$gte:20000}})
#不等于
db.people_table.find({salary:{$ne:20000}})

逻辑条件查询
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(一页显示多少数据)
eg:
db.people_table.find({})
.projection({})
.sort({_id:-1})
.limit(1)

数据更新
$set :设置字段值
$unset :删除指定字段
$inc:对修改的值进行自增
db.集合名.update(
<query>, <update>,
{ upsert: <boolean>,
multi: <boolean>,
writeConcern: <document>
} )
参数说明:
query : update的查询条件,类似sql update查询内where后面的。
update : update的对象和一些更新的操作符(如
upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认 、是false,不插入。
multi : 可选,MongoDB 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查 出来多条记录全部更新。
writeConcern :可选,用来指定mongod对写操作的回执行为比如写的行为是否需要确认。
例如:
db.people_table.update({salary:30000},
{$set:{name:'酸奶'}},
{ multi: false, upsert: false}
)

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选项。
数据删除
db.collection.remove( <query>, { justOne: <boolean>, writeConcern: <document> } )
参数说明:
query :(可选)删除的文档的条件。
justOne : (可选)如果设为 true 或 1,则只删除一个文档,如果不设置该参数,或使用默认值 false,则删除所有匹配条件的文档。
writeConcern :(可选)用来指定mongod对写操作的回执行为。
db.people_table.remove({name:'张三'}, {justOne: true})
聚合操作
聚合是MongoDB的高级查询语言,它允许我们通过转化合并由多个文档的数据来生成新的在单个文档里不存在的文档信息。一般都是将记录按条件分组之后进行一系列求最大值,最小值,平均值的简单操作,也可以对记录进行复杂数据统计,数据挖掘的操作。聚合操作的输入是集中的文档,输出可以是一个文档也可以是多个文档。
分为三类:
- 单目的聚合操作(Single Purpose Aggregation Operation)
- 聚合管道(Aggregation Pipeline)
- MapReduce 编程模型
单目的聚合操作
单目的聚合命令常用的有:count() 和 distinct()
db.people_table.remove({})
db.people_table.insert(
[{name:"李四",birthday:"2000-09-18",salary:20000,gender:0,city:"深圳"},
{name:"hhh",birthday:"2000-09-18",salary:45000,gender:0,city:"深圳"},
{name:"ttt",birthday:"2000-09-18",salary:25000,gender:0,city:"深圳"},
{name:"rrr",birthday:"2020-09-18",salary:20000,gender:0,city:"上海"},
{name:"sss",birthday:"2020-09-18",salary:30000,gender:0,city:"上海"},
{name:"fff",birthday:"2010-09-18",salary:20000,gender:0,city:"北京"},
{name:"bbb",birthday:"2010-09-18",salary:30000,gender:0,city:"北京"},
{name:"nnn",birthday:"2010-09-18",salary:60000,gender:0,city:"北京"},
{name:"王五",birthday:"2010-09-18",salary:30000,gender:0,city:"北京"}
])
db.people_table.find({}).count()
db.people_table.distinct("city")

聚合管道
db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)
MongoDB中聚合(aggregate)主要用于统计数据(诸如统计平均值,求和等),并返回计算后的数据结果。
表达式:处理输入文档并输出。表达式只能用于计算当前聚合管道的文档,不能处理其它的文档。

db.people_table.aggregate([{$group: { _id: "$city",city_count:{$sum: 1}}}])
db.people_table.aggregate([{$group: { _id: "$city",salary_avg:{$avg: "$salary"}}}])
db.people_table.aggregate([{$group: { _id: "$city",salary_max:{$max: "$salary"}}}])
db.people_table.aggregate([{$group: { _id: "$city",salary_min:{$min: "$salary"}}}])
db.people_table.aggregate([{$group: { _id: "$city",salary_point:{$push: "$salary"}}}])
db.people_table.aggregate([{$group: { _id: "$city",salary_point:{$addToSet: "$salary"}}}])
db.people_table.aggregate([{$group: { _id: "$city",salary_first:{$first: "$salary"}}}])
db.people_table.aggregate([{$group: { _id: "$city",salary_last:{$last: "$salary"}}}])

MongoDB 中使用 db.COLLECTION_NAME.aggregate([{},...]) 方法来构建和使用聚合管道,每个文档通过一个由一个或者多个阶段(stage)组成的管道,经过一系列的处理,输出相应的结果。MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。
这里我们介绍一下聚合框架中常用的几个操作:
- $group:将集合中的文档分组,可用于统计结果。
- $project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。
match使用MongoDB的标准查询操作。
- $limit:用来限制MongoDB聚合管道返回的文档数。
- $skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
- $sort:将输入文档排序后输出。
- $geoNear:输出接近某一地理位置的有序文档。
/**方式一*/
db.people_table.aggregate([
{$group: { _id: "$city",city_count:{$sum: 1}}},
{$project: {city:"$_id",count:"$city_count"}},
{$match: {count:{$gt:2}}}
])
/**方式二*/
db.people_table.aggregate([{$group: { _id: "$city",city_count:{$sum: 1}}}])
.match({city_count:{$gt:2}})
.project({city:"$_id",count:"$city_count"})
.sort({count:-1})
.limit(100)

MapReduce 编程模型
Pipeline查询速度快于MapReduce,但是MapReduce的强大之处在于能够在多台Server上并行执行复杂的聚合逻辑。MongoDB不允许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>
} )
使用 MapReduce 要实现两个函数 Map 函数和 Reduce 函数,Map 函数调用 emit(key, value), 遍历 collection 中所有的记录, 将 key 与 value 传递给 Reduce 函数进行处理。
参数说明:
- 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.people_table.mapReduce(
function () {emit(this.city, this.salary)}, //mapFunction
function(key, values){return Array.avg(values)},//reduceFunction
{
query:{salary:{$gt:20000} },
out: "salary_avg",
finalize:function(key,value){
return value+5000;
},
verbose:false
})

索引
索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容。索引目标是提高数据库的查询效率,没有索引的话,查询会进行全表扫描(scan every document in a collection),数据量大时严重降低了查询效率。默认情况下Mongo在一个集合(collection)创建时,自动地对集合的_id创建了唯一索引。
MongoDB 索引类型
单键索引(Single Field)
MongoDB支持所有数据类型中的单个字段索引,并且可以在文档的任何字段上定义。
对于单个字段索引,索引键的排序顺序无关紧要,因为MongoDB可以在任一方向读取索引。
单个例上创建索引:
db.集合名.createIndex({"字段名":排序方式})
过期索引 TTL(Time To Live)
TTL索引是MongoDB中一种特殊的索引, 可以支持文档在一定时间之后自动过期删除,目前TTL索引只能在单字段上建立,并且字段类型必须是日期类型。
db.集合名.createIndex({"日期字段":排序方式}, {expireAfterSeconds: 秒数})
db.people_table.createIndex({name:1})
db.people_table.getIndexes()
复合索引
通常我们需要在多个字段的基础上搜索表/集合,这是非常频繁的。 如果是这种情况,我们可能会考虑在MongoDB中制作复合索引。 复合索引支持基于多个字段的索引,这扩展了索引的概念并将它们扩展到索引中的更大域。制作复合索引时要注意的重要事项包括:字段顺序与索引方向。
db.集合名.createIndex( { "字段名1" : 排序方式, "字段名2" : 排序方式 } )
针对属性包含数组数据的情况,MongoDB支持针对数组中每一个element创建索引,Multikey indexes支持strings,numbers和nested documents
地理空间索引
针对地理空间坐标数据创建索引。
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] } } })
全文索引
MongoDB提供了针对string内容的文本查询,Text Index支持任意属性值为string或string数组元素的索引查询。注意:一个集合仅支持最多一个Text Index,中文分词不理想 推荐ES。
db.集合.createIndex({"字段": "text"})
db.集合.find({"$text": {"$search": "coffee"}})
哈希索引
针对属性的哈希值进行索引查询,当要使用Hashed index时,MongoDB能够自动的计算hash值,无需程序计算hash值。注:hash index仅支持等于查询,不支持范围查询。
db.集合.createIndex({"字段": "hashed"})
索引管理
创建索引并在后台运行
db.集合.createIndex({"字段":排序方式}, {background: true});
获取集合的索引
db.集合.getIndexes()
索引大小
db.集合.totalIndexSize()
索引的重建
db.集合.reIndex()
索引的删除
db.COLLECTION_NAME.dropIndex("INDEX-NAME")
db.COLLECTION_NAME.dropIndexes()
注意: _id 对应的索引是删除不了的
explain
explain()也接收不同的参数,通过设置不同参数我们可以查看更详细的查询计划。
- queryPlanner:queryPlanner是默认参数,具体执行计划信息参考下面的表格。
- executionStats:executionStats会返回执行计划的一些统计信息(有些版本中和 allPlansExecution等同)。
- allPlansExecution:allPlansExecution用来获取所有执行计划,结果参数基本与上文相同。
queryPlanner 默认参数

executionStats 默认参数

第一层,executionTimeMillis最为直观explain返回值是executionTimeMillis值,指的是这条语句的执行时间,这个值当然是希望越少越好。
其中有3个executionTimeMillis,分别是:
- executionStats.executionTimeMillis 该query的整体查询时间。
- executionStats.executionStages.executionTimeMillisEstimate 该查询检索document获得数据的时间。
- executionStats.executionStages.inputStage.executionTimeMillisEstimate 该查询扫描文档 index所用时间。
第二层,index与document扫描数与查询返回条目数 这个主要讨论3个返回项 nReturned、totalKeysExamined、totalDocsExamined,分别代表该条查询返回的条目、索引扫描条目、文档扫描条目。 这些都是直观地影响到executionTimeMillis,我们需要扫描的越少速度越快。 对于一个查询,我们最理想的状态是:nReturned=totalKeysExamined=totalDocsExamined
第三层,stage状态分析 那么又是什么影响到了totalKeysExamined和totalDocsExamined?是stage的类型。类型列举如下:
- COLLSCAN:全表扫描
- IXSCAN:索引扫描FETCH:根据索引去检索指定document
- SHARD_MERGE:将各个分片返回数据进行merge
- SORT:表明在内存中进行了排序
- LIMIT:使用limit限制返回数
- SKIP:使用skip进行跳过
- IDHACK:针对_id进行查询
- SHARDING_FILTER:通过mongos对分片数据进行查询
- COUNT:利用db.coll.explain().count()之类进行count运算
- TEXT:使用全文索引进行查询时候的stage返回
- PROJECTION:限定返回字段时候stage的返回
慢查询分析
1.开启内置的查询分析器,记录读写操作效率
db.setProfifilingLevel(n,m),n的取值可选0,1,2
- 0表示不记录
- 1表示记录慢速操作,如果值为1,m必须赋值单位为ms,用于定义慢速查询时间的阈值
- 2表示记录所有的读写操作
2.查询监控结果
db.system.profifile.fifind().sort({millis:-1}).limit(3)
3.分析慢速查询
应用程序设计不合理、不正确的数据模型、硬件配置问题,缺少索引等
4.解读explain结果 确定是否缺少索引
mongoDB 索引底层原理实现
MongoDB 是文档型的数据库,它使用BSON 格式保存数据,比关系型数据库存储更方便。比如之前关系型数据库中处理用户、订单等数据要建立对应的表,还要建立它们之间的关联关系。但是BSON就不一样了,我们可以把一条数据和这条数据对应的数据都存入一个BSON对象中,这种形式更简单,通俗易懂。MySql是关系型数据库,数据的关联性是非常强的,区间访问是常见的一种情况,底层索引组织数据使用B+树,B+树由于数据全部存储在叶子节点,并且通过指针串在一起,这样就很容易的进行区间遍历甚至全部遍历。MongoDB使用B-树,所有节点都有Data域,只要找到指定索引就可以进行访问,单次查询从结构上来看要快于MySql。
B-树是一种自平衡的搜索树,形式很简单:

B-树的特点:
(1) 多路 非二叉树
(2) 每个节点 既保存数据 又保存索引
(3) 搜索时 相当于二分查找
B+树是B-树的变种

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