MongoDB体系结构
NoSQL 和 MongoDB
NoSQL=Not Only SQL,支持类似SQL的功能, 与Relational Database相辅相成。其性能较高,不使用SQL意味着没有结构化的存储要求(SQL为结构化的查询语句),没有约束之后架构更加灵活。
NoSQL数据库四大家族 列存储 Hbase,键值(Key-Value)存储 Redis,图像存储 Neo4j,文档存储
MongoDB
MongoDB 是一个基于分布式文件存储的数据库,由 C++ 编写,可以为 WEB 应用提供可扩展、
高性能、易部署的数据存储解决方案。
MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库中功能最丰富、
最像关系数据库的。在高负载的情况下,通过添加更多的节点,可以保证服务器性能。
MongoDB 体系结构
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做为主键) |
什么是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有三个特点:轻量性、可遍历性、高效性
BSON在MongoDB中的使用
MongoDB使用了BSON这种结构来存储数据和网络数据交换。把这种格式转化成一文档这个概念(Document),这里的一个Document也可以理解成关系数据库中的一条记录(Record),只是这里的Document的变化更丰富一些,如Document可以嵌套。
MongoDB中Document 中 可以出现的数据类型
数据类型 | 说明 | 解释说明 | Document举例 |
---|---|---|---|
String | 字符串 | UTF-8 编码的字符串才是 合法的。 | {key:“cba”} |
Integer | 整型数值 | 根据你所采用的服务器, 可分为 32 位或 64 位。 | {key:1} |
Boolean | 布尔值 | 用于存储布尔值(真/ 假)。 | {key:true} |
Double | 双精度浮点值 | 用于存储浮点值 | {key:3.14} |
ObjectId | 对象ID | 用于创建文档的ID | {_id:new ObjectId()} |
Array | 数组 | 用于将数组或列表或多个值存储为一个键 | {arr:[“a”,“b”]} |
Timestamp | 时间戳 | 从开始纪元开始的毫秒数 | { ts: new Timestamp() } |
Object | 内嵌文档 | 文档可以作为文档中某个 key的value | {o:{foo:“bar”}} |
Null | 空值 | 表示空值或者未定义的对 象 | {key:null} |
Date或者 ISODate | 格林尼治时间 | 日期时间,用Unix日期格 式来存储当前日期或时 间。 | {birth:new Date()} |
File | 文件 | 1、二进制转码(Base64)后 存储 (<16M) 2、 GridFS(>16M) | GridFS 用两个集合来存储一个文件:fs.files与 fs.chunks 真正存储需要使用mongofiles -d gridfs put song.mp3 |
MongoDB在Linux的安装
-
下载社区版 MongoDB 4.1.3
去官网下载对应的MongoDB 然后上传到Linux虚拟机 -
将压缩包解压即可
tar -zxvf MongoDB-linux-x86_64-4.1.3.tgz
-
启动
./bin/mongod
-
指定配置文件方式的启动
./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
MongoDB启动和参数说明
参数 | 说明 |
---|---|
dbpath | 数据库目录,默认/data/db |
port | 监听的端口,默认27017 |
bind_ip | 监听IP地址,默认全部可以访问 |
fork | 是否已后台启动的方式登陆 |
logpath | 日志路径 |
logappend | 是否追加日志 |
auth | 是开启用户密码登陆 |
config | 指定配置文件 |
mongo shell 的启动
启动mongo shell
./bin/mongo
指定主机和端口的方式启动
./bin/mongo --host=主机IP --port=端口
Mongodb GUI工具
MongoDB Compass Community
MongoDB Compass Community由MongoDB开发人员开发,这意味着更高的可靠性和兼容性。它为MongoDB提供GUI mongodb工具,以探索数据库交互,具有完整的CRUD功能并提供可视方式。借助内置模式可视化,用户可以分析文档并显示丰富的结构。为了监控服务器的负载,它提供了数据库操作的实时统计信息。就像MongoDB一样,Compass也有两个版本,一个是Enterprise(付费),社区可以免费使用。适用于Linux,Mac或Windows。
NoSQLBooster(mongobooster)
NoSQLBooster是MongoDB CLI界面中非常流行的GUI工具。它正式名称为MongoBooster。
NoSQLBooster是一个跨平台,它带有一堆mongodb工具来管理数据库和监控服务器。这个Mongodb工具包括服务器监控工具,Visual Explain Plan,查询构建器,SQL查询,ES2017语法支持等等…它有免费,个人和商业版本,当然,免费版本有一些功能限制。NoSQLBooster也可用于Windows,MacOS和Linux。
MongoDB命令
MongoDB的基本操作
查看数据库
show dbs;
切换数据库 如果没有对应的数据库则创建
use 数据库名;
创建集合
db.createCollection("集合名")
查看集合
show tables;
show collections;
删除集合
db.集合名.drop();
删除当前数据库
db.dropDatabase();
MongoDB集合数据操作(CURD)
数据添加
- 插入单条数据 db.集合名.insert(文档)
文档的数据结构和JSON基本一样。
所有存储在集合中的数据都是BSON格式。
BSON是一种类json的一种二进制形式的存储格式,简称Binary JSON。 - 例如:
db.learn_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)
- 最后三个字节是随机数。)
- 插入多条数据
db.集合名.insert([文档,文档])
数据查询
-
比较条件查询
db.集合名.find(条件)操作 条件格式 例子 RDBMS中的条件 等于 {key:value} db.col.find({字段名:值}).pretty() where 字段名=值 大于 {key:{$gt:value}} db.col.find({字段名:{$gt:值}}).pretty() where 字段名>值 小于 {key:{$lt:value}} db.col.find({字段名:{$lt:值}}).pretty() where 字段名<值 大于等于 {key:{$gte:value}} db.col.find({字段名:{$gte:值}}).pretty() where 字段名>=值 小于等于 {key:{$lte:value}} db.col.find({字段名:{$lte:值}}).pretty() where 字段名<=值 不等于 {key:{$ne:value}} db.col.find({字段名:{$ne:值}}).pretty() where 字段名!=值 -
逻辑条件查询
- 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:{KaTeX parse error: Expected '}', got 'EOF' at end of input: not:{操作符:value}}).pretty()
- and 条件
-
分页查询
db.集合名.find({条件}).sort({排序字段:排序方式})).skip(跳过的行数).limit(一页显示多少数据)
数据更新 调用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选项。
数据删除
db.collection.remove(
<query>,
{
justOne: <boolean>,
writeConcern: <document>
}
)
参数说明:
query :(可选)删除的文档的条件。
justOne : (可选)如果设为 true 或 1,则只删除一个文档,如果不设置该参数,或使用默认值 false,则删除所有匹配条件的文档。
writeConcern :(可选)用来指定mongod对写操作的回执行为。
MongoDB 聚合操作
聚合操作简介
聚合是MongoDB的高级查询语言,它允许我们通过转化合并由多个文档的数据来生成新的在单个文档里不存在的文档信息。一般都是将记录按条件分组之后进行一系列求最大值,最小值,平均值的简单操作,也可以对记录进行复杂数据统计,数据挖掘的操作。聚合操作的输入是集中的文档,输出可以是一个文档也可以是多个文档。
MongoDB 聚合操作分类
- 单目的聚合操作(Single Purpose Aggregation Operation)
- 聚合管道(Aggregation Pipeline)
- MapReduce 编程模型
单目的聚合操作
单目的聚合命令常用的有: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)主要用于统计数据(诸如统计平均值,求和等),并返回计算后的数据结果。
表达式:处理输入文档并输出。表达式只能用于计算当前聚合管道的文档,不能处理其它的文档。
表达式 | 描述 |
---|---|
$sum | 计算总和 |
$avg | 计算平均值 |
$min | 获取集合中所有文档对应值得最小值 |
$max | 获取集合中所有文档对应值得最大值 |
$push | 在结果文档中插入值到一个数组中 |
$addToSet | 在结果文档中插入值到一个数组中,但数据不重复 |
$first | 根据资源文档的排序获取第一个文档数据 |
$last | 根据资源文档的排序获取最后一个文档数据 |
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上并行执行复杂的聚合逻辑。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, l
imit: number,
finalize: <function>,
verbose: <boolean>
}
)
使用 MapReduce 要实现两个函数 Map 函数和 Reduce 函数,Map 函数调用 emit(key, value), 遍历collection 中所有的记录, 将 key 与 value 传递给 Reduce 函数进行处理。
参数说明:
- map:是JavaScript 函数,负责将每一个输入文档转换为零或多个文档,生成键值对序列,作为reduce 函数参数
- reduce:是JavaScript 函数,对map操作的输出做合并的化简的操作(将key-value变成keyvalues,也就是把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
什么是索引
索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容。索引目标是提高数据库的查询效率,没有索引的话,查询会进行全表扫描(scan every document in a collection),数据量大时严重降低了查询效率。默认情况下Mongo在一个集合(collection)创建时,自动地对集合的_id创建了唯一索引。
索引类型
单键索引 (Single Field)
MongoDB支持所有数据类型中的单个字段索引,并且可以在文档的任何字段上定义。
对于单个字段索引,索引键的排序顺序无关紧要,因为MongoDB可以在任一方向读取索引。
单个例上创建索引:
db.集合名.createIndex({“字段名”:排序方式})
特殊的单键索引 过期索引 TTL ( Time To Live)
TTL索引是MongoDB中一种特殊的索引, 可以支持文档在一定时间之后自动过期删除,目前TTL索引只能在单字段上建立,并且字段类型必须是日期类型。
db.集合名.createIndex({"日期字段":排序方式}, {expireAfterSeconds: 秒数})
复合索引(Compound Index)
通常我们需要在多个字段的基础上搜索表/集合,这是非常频繁的。 如果是这种情况,我们可能会考虑在MongoDB中制作复合索引。 复合索引支持基于多个字段的索引,这扩展了索引的概念并将它们扩展到索引中的更大域。
制作复合索引时要注意的重要事项包括:字段顺序与索引方向。
db.集合名.createIndex( { "字段名1" : 排序方式, "字段名2" : 排序方式 } )
多键索引(Multikey indexes)
针对属性包含数组数据的情况,MongoDB支持针对数组中每一个element创建索引,Multikey
indexes支持strings,numbers和nested documents
地理空间索引(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]
}
}
})
全文索引
MongoDB提供了针对string内容的文本查询,Text Index支持任意属性值为string或string数组元素的索引查询。注意:一个集合仅支持最多一个Text Index,中文分词不理想 推荐ES。
db.集合.createIndex({"字段": "text"})
db.集合.find({"$text": {"$search": "coffee"}})
哈希索引 Hashed Index
针对属性的哈希值进行索引查询,当要使用Hashed index时,MongoDB能够自动的计算hash值,无需程序计算hash值。注:hash index仅支持等于查询,不支持范围查询。
db.集合.createIndex({"字段": "hashed"})
索引和explain 分析
索引管理
创建索引并在后台运行
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 对应的索引是删除不了的
explain 分析
使用js循环 插入100万条数据 不使用索引字段 查询查看执行计划 ,然后给某个字段建立索引,使用索引字段作为查询条件 再查看执行计划进行分析
explain()也接收不同的参数,通过设置不同参数我们可以查看更详细的查询计划。
- queryPlanner:queryPlanner是默认参数,具体执行计划信息参考下面的表格。
- executionStats:executionStats会返回执行计划的一些统计信息(有些版本中和
allPlansExecution等同)。 - allPlansExecution:allPlansExecution用来获取所有执行计划,结果参数基本与上文相同。
queryPlanner 默认参数
参数 | 含义 |
---|---|
plannerVersion | 查询计划版本 |
namespace | 要查询的集合(该值返回的是该query所查询的表)数据库.集合 |
indexFilterSet | 针对该query是否有indexFilter |
parsedQuery | 查询条件 |
winningPlan | 被选中的执行计划 |
winningPlan.stage | 被选中执行计划的stage(查询方式),常见的有:COLLSCAN/全表扫描:(应该知道就CollectionScan,就是所谓的“集合扫描”, 和mysql中table scan/heap scan类似,这个就是所谓的性能最烂最无奈的由来)、IXSCAN/索引扫描:(是IndexScan,这就说明我们已经命中索引了)、FETCH/根据索引去检索文档、SHARD_MERGE/合并分片结果、IDHACK/针对_id进行查询等 |
winningPlan.inputStage | 用来描述子stage,并且为其父stage提供文档和索引关键字。 |
winningPlan.stage的child stage | 如果此处是IXSCAN,表示进行的是index scanning。 |
winningPlan.keyPattern | 所扫描的index内容 |
winningPlan.indexName | winning plan所选用的index。 |
winningPlan.isMultiKey | 是否是Multikey,此处返回是false,如果索引建立在array上,此处将是true。 |
winningPlan.direction | 此query的查询顺序,此处是forward,如果用了.sort({字段:-1})将显示backward。 |
filter | 过滤条件 |
winningPlan.indexBounds | winningplan所扫描的索引范围,如果没有制定范围就是[MaxKey,MinKey],这主要是直接定位到mongodb的chunck中去查找数据,加快数据读取。 |
rejectedPlans | 被拒绝的执行计划的详细返回,其中具体信息与winningPlan的返回中意义相同,故不在此赘述) |
serverInfo | MongoDB服务器信息 |
executionStats参数
参数 | 含义 |
---|---|
executionSuccess | 是否执行成功 |
nReturned | 返回的文档数 |
executionTimeMillis | 执行耗时 |
totalKeysExamined | 索引扫描次数 |
totalDocsExamined | 文档扫描次数 |
executionStages | 这个分类下描述执行的状态 |
stage | 扫描方式,具体可选值与上文的相同 |
nReturned | 查询结果数量 |
executionTimeMillisEstimate | 检索document获得数据的时间 |
inputStage.executionTimeMillisEstimate | 该查询扫描文档 index所用时间 |
works | 工作单元数,一个查询会分解成小的工作单元 |
advanced | 优先返回的结果数 |
docsExamined | 文档检查数目,与totalDocsExamined一致。检查了总共的document 个数,而从返回上面的nReturned数量 |
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的返回
对于普通查询,我希望看到stage的组合(查询的时候尽可能用上索引):
- Fetch+IDHACK
- Fetch+IXSCAN
- Limit+(Fetch+IXSCAN)
- PROJECTION+IXSCAN
- SHARDING_FITER+IXSCAN
不希望看到包含如下的stage:
- COLLSCAN(全表扫描)
- SORT(使用sort但是无index)
- COUNT 不使用index进行count)
-
allPlansExecution参数
queryPlanner 参数和executionStats的拼接
慢查询分析
-
开启内置的查询分析器,记录读写操作效率
db.setProfilingLevel(n,m),n的取值可选0,1,2
- 0表示不记录
- 1表示记录慢速操作,如果值为1,m必须赋值单位为ms,用于定义慢速查询时间的阈值
- 2表示记录所有的读写操作
-
查询监控结果
db.system.profile.find().sort({millis:-1}).limit(3)
-
分析慢速查询
应用程序设计不合理、不正确的数据模型、硬件配置问题,缺少索引等 -
解读explain结果 确定是否缺少索引
MongoDB 索引底层实现原理分析
MongoDB 是文档型的数据库,它使用BSON 格式保存数据,比关系型数据库存储更方便。比如之前关系型数据库中处理用户、订单等数据要建立对应的表,还要建立它们之间的关联关系。但是BSON就不一样了,我们可以把一条数据和这条数据对应的数据都存入一个BSON对象中,这种形式更简单,通俗易懂。MySql是关系型数据库,数据的关联性是非常强的,区间访问是常见的一种情况,底层索引组织数据使用B+树,B+树由于数据全部存储在叶子节点,并且通过指针串在一起,这样就很容易的进行区间遍历甚至全部遍历。MongoDB使用B-树,所有节点都有Data域,只要找到指定索引就可以进行访问,单次查询从结构上来看要快于MySql。
B-树是一种自平衡的搜索树,形式很简单:
B-树的特点:
- 多路 非二叉树
- 每个节点 既保存数据 又保存索引
- 搜索时 相当于二分查找
B+树是B-树的变种
B+ 树的特点:
- 多路非二叉
- 只有叶子节点保存数据
- 搜索时 也相当于二分查找
- 增加了 相邻节点指针
从上面我们可以看出最核心的区别主要有俩,一个是数据的保存位置,一个是相邻节点的指向。就是这俩造成了MongoDB和MySql的差别。
- B+树相邻接点的指针可以大大增加区间访问性,可使用在范围查询等,而B-树每个节点 key 和data 在一起 适合随机读写 ,而区间查找效率很差
- B+树更适合外部存储,也就是磁盘存储,使用B-结构的话,每次磁盘预读中的很多数据是用不上的数据。因此,它没能利用好磁盘预读的提供的数据。由于节点内无 data 域,每个节点能索引的范围更大更精确。
- 注意这个区别相当重要,是基于(1)(2)的,B-树每个节点即保存数据又保存索引 树的深度小,所以磁盘IO的次数很少,B+树只有叶子节点保存,较B树而言深度大磁盘IO多,但是区间访问比较好。