一文搞懂NoSQL与MongoDB

一、什么是MongoDB

1.1 定义
MongoDB 是由 C++语言编写的, 是一个基于分布式文件存储的开源数据库系统。 在高负载的情况下, 添加更多的节点, 可以保证服务器性能。 MongoDB 旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。 MongoDB 将数据存储为一个文档, 数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON 对象。 字段值可以包含其他文档, 数组及文档数组。
MongoDB 是一个跨平台的,面向文档的数据库,是当前 NoSQL 数据库产品中最热门的一种。


1.2 特点

  • MongoDB是一个面向文档存储的数据库,操作起来比较简单。
  • 可以在MongoDB记录中设置任何属性的索引来实现更快的排序。
  • 可以通过本地或网络创建数据镜像,使得MongoDB具有更强的扩展性。
  • 如果负载增加,可以分布在计算机网络中的其他节点上,即分片。
  • 支持和丰富的查询表达式。
  • 使用update命令可以实现替换完成的文档或者一些指定的数据字段。
  • Map/reduce主要用来对数据进行批量处理和聚合操作。
  • GridFS是MongoDB中的一个内置功能,用于用于存放大量小文件。
  • 允许在服务端执行脚本。
  • 支持各种编程语言,ruby、python、Java、c++、php、c#等。


二、什么是NoSQL?

2.1 定义

NoSQL是Not Only SQL的缩写。它指的是非关系型的数据库,是以key-value形式存储,NoSQL和传统的关系型数据库不一样。


2.2 特点

  • 非关系型
  • 分布式
  • 易扩展
  • 大数据量
  • 灵活的数据模型

2.3 为什么要用NoSQL?

先与关系型数据库做个比较:
1)关系型数据库的特点:扩展困难、读写慢、成本高、有限的支撑容量。
2)NoSQL:对数据高并发读写和海量数据的存储,在架构和模型上做了减法,而在扩展和并发方面做了加法。


2.4 NoSQL优缺点

优点

  • 扩展简单:可以比较简单的添加一台新的服务器。
  • 读写快速:传统数据库受限于IO,在高并发情况下原理倍增,redis内存数据库支持10w/s读写。
  • 成本低廉:相比较Oracle企业级授权费用比较低。
  • 数据模型灵活:传统关系型数据库都是结构化的表NoSQL可以是列式存储,key-value存储和文档存储。

缺点

  • 不提供对SQL的支持。
  • 支持的特性不够丰富,大多数NoSQL不支持事务(redis支持,MongoDB不支持)。
  • 初创产品,不够成熟。
  • NoSQL只能保证数据相对一致性,数据同步的时候主从服务器的状态不一致。


三、MongoDB详述

3.1 逻辑关系对比

1)关系型数据库:MySQL数据库(database),表(table),记录(rows)三个层次概念组成。

2)非关系性感数据库:MongoDB数据库(database),集合(collection),文档对象(document)三个层次概念组成。

MongoDB里的集合对应关系型数据库里的表,但是集合中没有列、行和关系的概念。集合中只有文档,一个文档相当于一条记录。


3.2 数据结构存储

1)MySQL数据结构存储
MySQL的每个数据库存放在一个与数据库同名的文件夹中。
MyISAM存储引擎,数据库文件类型包括:.frm、.MYD、.MYI
InnoDB存储引擎,数据库文件类型包括:.frm、.ibd

文件位置:/var/lib/mysql

2)MongoDB数据结构存储
MongoDB默认数据目录/data/db,负责存储所有的MongoDB数据文件。


3.3 MongDB安装

MongoDB网址:https://www.mongodb.com/download-center#community
1)下载

wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-4.2.6.tgz

2)解压

tar -zxvf mongodb-linux-x86_64-ubuntu1604-4.2.6.tgz

3)将压缩包拷贝到指定目录

mv  mongodb-linux-x86_64-ubuntu1604-4.2.6 /usr/local/mongodb

创建数据库文件(默认的数据库文件的位置是/data/db,启动时会自动创建)。
提示:MongoDB没有具体的安装过程,解压文件包后,可以直接使用,非常高效和方便。


3.4 MongoDB配置

MongoDB安装后,包含服务端Mongod和客户端Mongo,默认情况下服务端是没有配置文件的,这样可能会导致非本机IP连接访问的时候无法连接,因此需要增加一个配置文件让服务端可以接收任意IP的连接访问。

1)创建文件夹或文件

sudo mkdir -p /data/db						//创建数据库目录
sudo mkdir -p /usr/local/mongodb/logs				//创建logs目录
sudo touch /usr/local/mongodb/logs/mongodb.log 	//创建空文件

2)在MongoDB安装位置/bin目录 sudo vi mongodb.conf

#数据文件存放目录
dbpath=/data/db

#日志文件存放目录
logpath=/usr/local/mongodb/logs/mongodb.log

#以守护程序的方式启动(在后台运行)
fork=true

#远程连接
bind_ip=0.0.0.0

3.5 MongDB启动

1)启动服务
方式1:

sudo /usr/local/mongodb/ mongodb-linux-x86_64-ubuntu1604-4.2.6/bin/mongod

方式2(后台启动):

sudo /usr/local/mongodb/ mongodb-linux-x86_64-ubuntu1604-4.2.6/bin/mongod  -config /usr/local/mongodb/mongodb-linux-x86_64-ubuntu1604-4.2.6/bin/ mongodb.conf

2)启动客户端
运行MongoDB命令:sudo/usr/local/mongodb/mongodb-linux-x86_64-ubuntu1604-4.2.6/bin/mongo

3)启动常用选项说明

- dbpath 			指定数据库的目录
- port			    指定数据库的端口,默认是27017
- bind_ip 			绑定IP
- directoryperdb	为每个db创建一个独立子目录
- logpath			指定日志存放目录
- logappend			指定日志生成方式(追加/覆盖)
- pidfilepath		指定进程文件路径,如果不指定,将不产生进程文件
- keyFile			集群模式的关键标识
- journal			启用日志
- nssize			指定.ns文件的大小,单位MB,默认是16M,最大是2GB
- maxConns			最大的并发连接数
- notablescan		不允许进行表扫描
- noprealloc		关闭数据文件的预分配功能
- fork				以后台 Daemon形式运行服务

更多资源选项使用 mongo -help进行查看。


3.6 Unix系统指令操作MongoDB

1)查看MongDB运行信息

$ps aux|grep mongod

2)注意事项
不要使用 kill -9 PID 杀死MongoDB进程,这样可能会导致MongoDB的数据损坏。可以用kill -2 PID杀死进程。

知识点补充:kill的常用信号包括,

HUP(1)		终端断线
INT(2)		中断(Ctrl+C)
QUIT(3)		退出(Ctrl+\)
TERM(15)	终止
KILL(9) 	强制终止
CONT(18)	继续(与STOP相反,fg/bg命令)
STOP(19)	暂停(Ctrl+Z)

只有第9种信号可以无条件终止进程,其他信号都有权利忽略。
第2种信号时程序在结束之前,能够保存相关数据,然后再退出。


3.7 MongoDB一些指令操作

use db 				//创建或切换到db数据库
db.version()		//查看数据库版本
db.dropDatabase();	//删除数据库 (db的所有操作都是对当前数据库)
db.createCollection(“teacher”);	//创建teacher集合
show collections;	//显示所有集合
db.teacher.drop();	//删除集合
db.teacher.inert({
    
    name: “abc”, sex:1});  //集合添加数据
db.teacher.inert({
    
    name: “efg”, sex:1, age:18}); //插入数据到集合
db.teacher.find();	//查询集合数据

1)插入数据
db.teacher.inert({name: “efg”, sex:1, age:18});
在这里插入图片描述

2)修改数据
db.teacher.update({name:“efg”}, {$set:{age:30}});
在这里插入图片描述

3)删除数据
db.teacher.remove({name:“efg”}); //删除指定数据{name:”efg”}相当于SQL中的WHERE条件语句
在这里插入图片描述

4)条件查找-AND条件
格式:db.col.find({key1:value1, key2:value2}).pretty();

  • 不增加pretty()格式转换输出
    在这里插入图片描述

  • 增加pretty()格式转换输出
    在这里插入图片描述
    5)条件查找-OR条件
    db.col.find(
    {
    $or: [{key1: value1}, {key2:value2}]
    }
    ).pretty()
    在这里插入图片描述

6)$ type操作符
db.teacher.find({name:{$ type:2}}) 等价于 db.teacher.find({name:{$type:“string”}})
在这里插入图片描述
$type操作符是基于BSON类型来检索集合中匹配的数据类型,并返回结果。
MongoDB 中可以使用的类型如下表所示:
在这里插入图片描述
7)limit
limit接受一个数字参数,表示要读取的记录条数。
语法:db.COLLECTION_NAME.find().limit(NUMBER)
在这里插入图片描述
如果设置的读取NUMBER数量超出了集合的数据总数,则返回集合的所有数据。

8)skip
使用skip()方法来跳过指定数量的数据,skip方法同样接受一个数字参数作为跳过的记录条数。
语法:db.COLLECTION_NAME.find().limit(NUMBER).skip(NUMBER)
在这里插入图片描述
9)sort方法
db.COLLECTION_NAME.find().sort({“key”:NUM})
在这里插入图片描述
NUM:1 升序,-1 降序。

10)aggregate方法
用于处理数据(诸如统计平均值,求和等),并返回计算结果。有点类似SQL语句中的count(*)。
语法:db. COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)
在这里插入图片描述
一些聚合的表达式
在这里插入图片描述


3.8 MongoDB可视化工具

  • Navicat for MongoDB : http://www.navicat.com.cn/what-is-navicat-for-mongodb
  • MongoDB Compass Community : https://www.mongodb.com/download-center/compass
  • NoSQLBooster(mongobooster): https://nosqlbooster.com/downloads
  • ClusterControl: https://severalnines.com/download-clustercontrol-database-management-system
  • Mongo Management Studio: http://mms.litixsoft.de/index.php?lang=de/
  • Nosqlclient: https://github.com/nosqlclient/nosqlclient

3.9 集合和MySQL中table的区别

1)MySQL字段事先定义好,大小固定。
2)MongoDB不需要事先定义号,大小不固定。


3.10 索引

参考:https://mongoing.com/archives/2797

3.10.1 为什么需要索引?

当你抱怨MongoDB集合查询效率低的时候,可能就需要考虑使用索引了。MongoDB索引机制:
在这里插入图片描述
当往某各个集合插入多个文档后,每个文档在经过底层的存储引擎持久化后,会有一个位置信息,通过这个位置信息就能从存储引擎里读出该文档。
比如:mmapv1引擎里,位置信息是[文件id+文件内offset],在writetiger存储引擎(一个KV存储引擎里),位置是writetiger在存储文档时生成的一个key,通过这个key能访问到对应的文档;为了方便介绍,同一用pos(position的缩写)来代表位置信息。

比如上面的例子,person集合里面包含插入了4个文档,假设其存储后位置信息如下(为方便描述,文档省去_id字段)
在这里插入图片描述
假设现在有个查询db.person.find({age:18}),查询所有年龄为18岁的人,这时需要遍历所有的文档(『全表扫描』),根据位置信息读出文档,对比age字段是否为18。当然如果只有4个文档,全表扫描的开销并不大,但如果集合文档数量到百万、甚至千万上亿的时候,对集合进行全表扫描开销是非常大的,一个查询耗费数十秒甚至几分钟都有可能。

如果想加速db.person.find({age:18}),就可以考虑对person表的age字段建立索引。

db.person.createIndex( {
    
    age: 1} )  // 按age字段创建升序索引

建立索引后,MongoDB会额外存储一份按age字段升序排序的索引数据,索引结构类似如下,索引通常采用类似btree的结构持久存储,以保证从索引里快速(O(logN)的时间复杂度)找出某个age值对应的位置信息,然后根据位置信息就能读取出对应的文档。
在这里插入图片描述
简单的说,索引就是将文档按照某个(或某些)字段顺序组织起来,以便能够根据该字段高效的查询。
有了索引,至少能够优化如下场景的效率:

  • 查询,比如查询年龄为18的所有人。
  • 更新/删除,将年龄为18的所有人的信息更新或删除,因为更新或删除时,需要根据条件先查出所有符合条件的文档,本质上还是在优化查询。
  • 排序,将所有人的信息按年龄排序,如果没有索引,需要全盘扫描文档,然后再对扫描的结果进行排序。

MongoDB默认会为插入的文档生成 _id 字段(如果应用本身没有指定该字段),** _id是文档唯一的标识**,为了保证能够根据文档id快速查询文档,MongoDB默认会为集合创建_id字段的索引
在这里插入图片描述

3.10.2 MongoDB索引类型

MongoDB支持多种类型的索引,包括单字段索引、复合索引、多key索引、文本索引等,每种类型的索引有不同的使用场合。

1)单字段索引(Single Field Index)

db.person.createIndex( {
    
    age: 1} )

上述语句针对age创建了单字段索引,其能加速对age字段的各种查询请求,是最常见的索引形式,MongoDB默认创建的id索引也是这种类型。
{age: 1} 代表升序索引,也可以通过{age: -1}来指定降序索引,对于单字段索引,升序/降序效果是一样的。


2) 复合索引(Compound Index)
复合索引是Single Field Index的升级版本,它针对多个字段联合创建索引,先按第一个字段排序,第一个字段相同的文档按第二个字段排序,依次类推,如下针对age,name这2个字段创建一个符合索引。

db.person.createIndex({
    
    age:1, name:1});

上述索引对应的数据组织类似下表,与{age:1}索引不同的是,当age字段相同时,再根据name字段进行排序,所以pos5对应的文档排在pos3之前。
在这里插入图片描述
复合索引能满足的查询场景比单字段索引更丰富,不但能满足多个字段组合起来的查询,比如:db.person.find( {age:18, name: “jack”} ) ,也能满足所有能匹配符合索引前缀的查询,这里{age:1}即为{age:1, name:”jack”}的前缀,所以类似db.person.find( {age:18} ),也能通过索引来加速;但db.person.find( {name:”jack”} ) 则无法使用该符合索引。如果经常需要根据 [name字段] 以及 [name和age字段组合]来查询,则应该创建如下的复合索引。
db.person.createIndex( {name: 1, age: 1} )

除了查询的需求能够影响索引的顺序,字段的值分布也是一个重要的考量因素,即使person集合所有的查询都是『name和age字段组合』(指定特定的name和age),字段的顺序也是有影响的。
age字段的取值很有限,即拥有相同age字段的文档会有很多;而name字段的取值则丰富很多,拥有相同name字段的文档很少;显然先按name字段查找,再在相同name的文档里查找age字段更为高效。


3) 多key索引(Multikey Index)
当索引的字段为数组时,创建出的索引称为多key索引,多key索引会为数组的每个元素建立一条索引,比如person表加入一个habbit字段(数组)用于描述兴趣爱好,需要查询有相同兴趣爱好的人就可以利用habbit字段的多key索引。
{“name” : “jack”, “age” : 19, habbit: [“football, runnning”]}
db.person.createIndex( {habbit: 1} ) // 自动创建多key索引
db.person.find( {habbit: “football”} )


4)其他类型索引

  • 哈希索引(Hashed Index)是指按照某个字段的hash值来建立索引,目前主要用于MongoDB Shared Cluster的Hash分片,hash索引只能满足字段完全匹配的查询,不能满足范围查询等。
  • 地理位置索引(Geospatial Index)能很好的解决O2O的应用场景,比如『查找附近的美食』、『查找某个区域内的车站』等。
  • 文本索引(Text Index)能解决快速文本的查找需求,比如有一个博客文章集合,需要根据博客的内容来快速查找,则可以针对博客内容建立文本索引。


5)索引其他额外属性
MongoDB除了支持多种不同类型的索引,还能对索引定制一些特殊的属性

  • 唯一索引 (unique index):保证索引对应的字段不会出现相同的值,比如_id索引就是唯一索引。
  • TTL索引:可以针对某个时间字段,指定文档的过期时间(经过指定时间后过期 或 在某个时间点过期)。
  • 部分索引 (partial index): 只针对符合某个特定条件的文档建立索引,3.2版本才支持该特性。
  • 稀疏索引(sparse index): 只针对存在索引字段的文档建立索引,可看做是部分索引的一种特殊情况。

3.10.3 MongoDB索引优化

db profiling
MongoDb支持对DB的请求进行profiling,目前支持三种级别的profiling。

  • 0:不开启profiling。
  • 1:将处理时间超过某个阈值(默认100ms)的请求都记录到DB下的system.profile集合(类似于MySQL、redis的slowlog)。
  • 2:将所有的请求都记录到DB下的system.profile集合(生产环境慎重)。

通常,生产环境建议使用1级别的profiling,并根据自身需求配置合理的阈值,用于监测慢请求的情况,并及时的做索引优化。
如果能在集合创建的时候就能『根据业务查询需求决定应该创建哪些索引』,当然是最佳的选择;但由于业务需求多变,要根据实际情况不断的进行优化。索引并不是越多越好,集合的索引太多,会影响写入、更新的性能,每次写入都需要更新所有索引的数据;所以你system.profile里的慢请求可能是索引建立的不够导致,也可能是索引过多导致。

查询计划
索引已经建立了,但查询还是很慢怎么破?这时就得深入的分析下索引的使用情况了,可通过查看下详细的查询计划来决定如何优化。通过执行计划可以看出如下问题。
1)根据某个/些字段查询,但没有建立索引。
2)根据某个/些字段查询,但建立了多个索引,执行查询时没有使用预期的索引。

建立索引前,db.person.find({age:18})必须执行COLLSCAN(全盘扫描)。
在这里插入图片描述
建立索引后,通过查询计划可以看出,先进行[IXSCAN]从索引中查找,然后FETCH,读取出满足条件的文档。
在这里插入图片描述
在这里插入图片描述

3.10.4 索引操作

1)创建索引
语法:db.collection.createIndex(keys, options)
Key值为要创建的索引字段;options:1 指定按升序创建索引,-1按降序创建。

2)查看集合索引db.col.getIndexes()

在这里插入图片描述
3)查看集合索引大小db.col.totalIndexSize()

4)删除集合所有索引db.col.dropIndexes()

5)删除集合指定索引 db.col.dropIndex("索引名称")

3.10.5 什么情况下索引会失效,导致全表扫描?

1)条件中用or,即使其中有条件带索引,也不会使用索引查询(这就是查询尽量不要用or的原因,用in吧)
在这里插入图片描述
注意:使用or,又想索引生效,只能将or条件中的每个列都加上索引。

2)对于多列索引,不是使用的第一部分,则不会使用索引。类似电话薄。

3)like的模糊查询,以%开头,索引失效。
在这里插入图片描述
4)存在索引列的数据类型隐形转换,则用不上索引,比如列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不会使用索引。
在这里插入图片描述
5)where子句里有对索引列上有数学运算,用不上索引。
在这里插入图片描述

6)where子句里对有索引列使用函数,用不上索引。
在这里插入图片描述

7)如果mysql估计使用全表扫描要比使用索引快,则不使用索引。比如数据量极少的表。

3.10.6 什么情况下不推荐使用索引?

1)唯一性差的字段不要使用索引
比如性别,只有两种可能。意味着索引的二叉树级别少,多是平级。这样的二叉树无异于全盘扫描。

2)频繁更新的字段(更新索引消耗)
比如login count登录次数,频繁变化导致索引也频繁变化,增大数据库工作量,降低效率。

3)字段不在where条件中
字段不在where语句中出现时,不要添加索引,如果where后含IS NULL / IS NOT NULL / like ‘%输入符%’ 等条件,不建议使用索引。
只有在where语句出现,mysql才会去使用索引。

4)索引使用 < > 时,效果一般。


3.11 如何知道该语句是全屏扫描?

可以通过explain来查看语句的执行计划,是否是全局扫描,如果是全局扫描可以对语句进行优化。
explain:提供了查询信息,使用索引及查询统计等。有利于我们对索引的优化。
如在查询语句中使用explain:
db.users.find({gender:“M”},{user_name:1,_id:0}).explain()

返回结果:

{
    
    
   "cursor" : "BtreeCursor gender_1_user_name_1",
   "isMultiKey" : false,
   "n" : 1,
   "nscannedObjects" : 0,
   "nscanned" : 1,
   "nscannedObjectsAllPlans" : 0,
   "nscannedAllPlans" : 1,
   "scanAndOrder" : false,
   "indexOnly" : true,
   "nYields" : 0,
   "nChunkSkips" : 0,
   "millis" : 0,
   "indexBounds" : {
    
    
      "gender" : [
         [
            "M",
            "M"
         ]
      ],
      "user_name" : [
         [
            {
    
    
               "$minElement" : 1
            },
            {
    
    
               "$maxElement" : 1
            }
         ]
      ]
   }
}

字段分析:

  • indexOnly: 字段为 true ,表示使用了索引。
  • cursor:因为这个查询使用了索引,MongoDB 中索引存储在B树结构中,所以这是也使用了 BtreeCursor 类型的游标。如果没有使用索引,游标的类型是 BasicCursor。这个键还会给出你所使用的索引的名称,你通过这个名称可以查看当前数据库下的system.indexes集合(系统自动创建,由于存储索引信息,这个稍微会提到)来得到索引的详细信息。
  • n:当前查询返回的文档数量。
  • nscanned/nscannedObjects:表明当前这次查询一共扫描了集合中多少个文档,我们的目的是,让这个数值和返回文档的数量越接近越好。
  • millis:当前查询所需时间,毫秒数。
  • indexBounds:当前查询具体使用的索引。

3.12 Hint

使用hint来强制MongoDB使用一个指定的索引。
这种方法某些情形下会提升性能。 一个有索引的 collection 并且执行一个多字段的查询(一些字段已经索引了)。
如下查询实例指定了使用 gender 和 user_name 索引字段来查询:
db.users.find({gender:“M”},{user_name:1,_id:0}).hint({gender:1,user_name:1})

可以使用 explain() 函数来分析以上查询:
db.users.find({gender:“M”},{user_name:1,_id:0}).hint({gender:1,user_name:1}).explain()


3.13 存储索引

3.13.1Wiredtiger

Wiredtiger的Cache采用Btree的方式组织,每个Btree节点为一个page,root page是btree的根节点,internal page是btree的中间索引节点,leaf page是真正存储数据的叶子节点;btree的数据以page为单位按需从磁盘加载或写入磁盘。
btree的每个PAGE以文件里的extent形式(由文件offset + size标识)存储。
参考:https://mp.weixin.qq.com/s/Wuzh47jsBh5QonBrZxUnjg

在这里插入图片描述
存储引擎要做的事无外乎是将磁盘上的数据读取到内存并返回给应用,或者是将应用修改的数据由内存写到磁盘。
在这里插入图片描述
如何设计一种高效的数据结构和算法是所有存储引擎要考虑的根本问题,目前大多数流行的存储引擎是基于B-tree或LSM(LogStructured merge)Tree这两种数据结构来设计的。

像Oracle、SQL Server、DB2、MySQL(InnoDB)和Postgresql这些传统的关系数据库依赖的底层存储引擎是基于b-tree开发的,而像Cassandra、Elasticsearch (Lucene)、Google Bigtable、Apache HBase、LevelDB和RocksDB这些当前比较流行的NoSQL数据库存储引擎是基于LSM开发的。当然有些数据库采用了插件式的存储引擎架构,实现了Server层和存储引擎层的解耦,可以支持多种存储引擎,如MySQL既可以支持B-Tree结构的InnoDB存储引擎,还可以支持LSM结构的RocksDB存储引擎。

对于MongoDB来说,也采用了插件式存储引擎架构,底层的WiredTiger存储引擎还可以支持B-Tree和LSM两种结构组织数据,但MongoDB在使用WiredTiger作为存储引擎时,目前默认配置是使用了B-tree 结构。

3.13.2 典型B-Tree数据结构

B-Tree是为磁盘或其它辅助存储设备而设计的一种数据结构,目的是为了在查找数据的过程中减少磁盘I/O的次数,一个典型的B-Tree结构如下图所示:
在这里插入图片描述
在整个B-Tree中,从上往下依次为Root结点、内部结点和叶子结点,每个结点就是一个Page,数据以Page为单位在内存和磁盘间进行调度,每个Page的大小决定了相应结点的分支数量,每条索引记录会包含一个数据指针,指向一条数据记录所在文件的偏移量。

如上图,假设每个结点100个分支,那么所有叶子结点合起来可以包含100万个键值(等于100100100)。通常情况下Root结点和内部结点的Page会驻留在内存中,所以查找一条数据可能只需2次磁盘I/O。但随着数据不断的插入、删除,会涉及到B-Tree结点的分裂、位置提升及合并等操作,因此维护一个B-Tree的平衡也是比较耗时的。

3.13.3 磁盘上的基础数据结构

对于WiredTiger存储引擎来说,集合所在的数据文件和相应的索引文件都是按B-Tree结构来组织的,不同之处在于数据文件对应的B-Tree叶子结点上除了存储键名外(keys),还会存储真正的集合数据(values),所以数据文件的存储结构也可以认为是一种B+Tree,其整体结构如下图所示:
在这里插入图片描述
从上图可以看到,B+ Tree中的leaf page包含一个页头(page header)、块头(block header)和真正的数据(key/value),其中页头定义了页的类型、页中实际载荷数据的大小、页中记录条数等信息;块头定义了此页的checksum、块在磁盘上的寻址位置等信息。

WiredTiger有一个块设备管理的模块,用来为page分配block。如果要定位某一行数据(key/value)的位置,可以先通过block的位置找到此page(相对于文件起始位置的偏移量),再通过page找到行数据的相对位置,最后可以得到行数据相对于文件起始位置的偏移量offsets。由于offsets是一个8字节大小的变量,所以WiredTiger磁盘文件的大小,其最大值可以非常大(264bit)。

3.13.4 内存上的基础数据结构

WiredTiger会按需将磁盘的数据以page为单位加载到内存,同时在内存中会构造相应的B-Tree来存储这些数据。为了高效的支撑CRUD等操作以及将内存里面发生变化的数据持久化到磁盘上,WiredTiger也会在内存里面维护其他几种数据结构,如下图:
在这里插入图片描述
上图是WiredTiger在内存里面的大概布局图,通过它我们可以梳理清楚存储引擎是如何将数据加载到内存,然后如何通过相应数据结构来支持查询、插入、修改操作的。

  • 内存里面B-Tree包含三种类型的page,即rootpage、internal page和leaf page,前两者包含指向其子页的page index指针,不包含集合中的真正数据,leaf page包含集合中的真正数据即keys/values和指向父页的home指针;
  • 内存上的leaf page会维护一个WT_ROW结构的数组变量,将保存从磁盘leaf page读取的keys/values值,每一条记录还有一个cell_offset变量,表示这条记录在page上的偏移量;
  • 内存上的leaf page会维护一个WT_UPDATE结构的数组变量,每条被修改的记录都会有一个数组元素与之对应,如果某条记录被多次修改,则会将所有修改值以链表形式保存。
  • 内存上的leaf page会维护一个WT_INSERT_HEAD结构的数组变量,具体插入的data会保存在WT_INSERT_HEAD结构中的WT_UPDATE属性上,且通过key属性的offset和size可以计算出此条记录待插入的位置;同时,为了提高寻找待插入位置的效率,每个WT_INSERT_HEAD变量以跳转链表的形式构成。

下图是一个跳转链表的插入示例:
在这里插入图片描述
假如现在插入一个16,最终结果:
在这里插入图片描述
如果是一个普通的链表,寻找合适的插入位置时,需要经过:
开始结点2581020的比较。

对于跳转链表来说只需要经过:开始结点51020的比较。可以看到比在普通链表上寻找插入位置时需要的比较步骤少,所以,通过跳转链表的数据结构能够提升插入操作的效率。


page的其他数据结构
对于一个面向行存储的leaf page来说,包含的数据结构除了上面提到的WT_ROW(keys/values)、WT_UPDATE(修改数据)、WT_INSERT_HEAD(插入数据)外,还有如下几种重要的数据结构:

  • WT_PAGE_MODIFY
    保存page上事务、脏数据字节大小等与page修改相关的信息;

  • read_gen
    page的read generation值作为evict page时使用,具体来说对应page在LRU队列中的位置,决定page被evict server选中淘汰出去的先后顺序。

  • WT_PAGE_LOOKASIDE
    page关联的lookasidetable数据。当对一个page进行reconcile时,如果系统中还有之前的读操作正在访问此page上修改的数据,则会将这些数据保存到lookasidetable;当page再被读时,可以利用lookasidetable中的数据重新构建内存page。

  • WT_ADDR
    page被成功reconciled后,对应的磁盘上块的地址,将按这个地址将page写到磁盘,块是最小磁盘上文件的最小分配单元,一个page可能有多个块。

  • checksum
    page的校验和,如果page从磁盘读到内存后没有任何修改,比较checksum可以得到相等结果,那么后续reconcile这个page时,不会将这个page的再重新写入磁盘。



四、应用开发

4.1 安装mongo-c-driver

1)安装openssl

 sudo  apt-get  install  pkg-config  libssl-dev  libsasl2-dev

2)安装mongo-c-driver-1.16.2

  • 下载:wget https://github.com/mongodb/mongo-c-driver/releases/download/1.16.2/mongo-c-driver-1.16.2.tar.gz

  • 解压:tar -xvf mongo-c-driver-1.6.2.tar.gz

  • 进入目录:cd mongo-c-driver-1.6.2

  • 创建cmake-bulid:mkdir cmake-build

  • 进入目录:cd cmake-build
    保存后更新资源:cmake -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF ..
    编译:sudo make & make install

  • 进入头文件安装目录:$cd /usr/local/include/
    查看是否有mongdb的头文件:$ ll
    在这里插入图片描述

  • 进入依赖库安装目录 $ cd /usr/local/lib/
    查看是否有mongdb的库文件 $ ll
    在这里插入图片描述

  • Libmongoc API信息 :http://mongoc.org/libmongoc/1.16.2


4.2 源码应用

参考:https://blog.csdn.net/chenjiayi_yun/article/details/50779284

猜你喜欢

转载自blog.csdn.net/locahuang/article/details/110947342
今日推荐