MongoDB高级部分及实现(一)

来源:互联网 发布:应用系统性能优化方案 编辑:程序博客网 时间:2024/05/21 17:26

MongoDB高级部分及实现(一)

 

MongoDB是由C++语言所编写的一种面向文档的非关系型数据库(是一种NoSql数据库实现),也是介于关系型数据库和非关系型数据库之间的数据存储产品,其提供了高性能、高可用、高可拓展及基于分布式存储的数据库,是非关系型数据库中功能最丰富,最类似关系型数据库的一种集合、文档格式的数据库。

 

l   文档关系

l   原子操作

l   ObjectID

l   Map Reduce

l   GridFS

 

一、文档关系

在MongoDB数据库中,文档之间存在类似于RDBMS 数据库表的集中关系,比如:一对一,一对多,多对多等,而MongoDB中文档也存在相同的概念的逻辑关系,所以说其类似关系型数据库,介于关系型数据库(RDBMS)和非关系型数据库(NoSql)之间的数据格式。

 

1、关系方式

在MongoDB中,建立文档之间的逻辑关系有两种方式,分别为嵌入式建模和引用模型建模,那么什么时候使用哪种方式?答案是,如果文档包含或与较少文档有关联关系,此时我们可以使用嵌入建模方式实现关联;而如果文档包含的其它文档数比较多,那么推荐使用模型引用方式,因为可以提高系统引擎检索文档的性能。好了,下面就以用户文档和其所下订单为例演示文档之间的关系实现,并且用户文档和订单文档之间是1:N的逻辑关系,因为用户可以下多个订单,而某一订单必须属于某个用户。

 

两个文档的结构如下:

user:

{

     "_id" :ObjectId("57e89964b316d2e13cc0ba9b"),

     "username" :"marky@123.com",

     "nickname" : "marky",

     "address" : "云端路1024号,柯南私募基金大厦",

     "contact" :"13141250012",

     "created" :"2012-07-07"

}

 

order:

{

     "_id" :ObjectId("57e89b3ab316d2e13cc0ba9c"),

     "orderno" : "YD000001",

     "title" : "席梦思抱枕一对",

     "oriprice" : 80,

     "discount" : "0.70",

     "saleprice" : "56.00",

     "type" : "居家",

     "coupon" : 0

}

{

     "_id" :ObjectId("57e89bcfb316d2e13cc0ba9d"),

     "orderno" : "YD000002",

     "title" : "席梦思抱软硬适中床垫",

     "oriprice" : 280,

     "discount" : "0.80",

     "saleprice" : "224.00",

     "type" : "居家",

     "coupon" : 0

}

 

这里新建了一个用户和两个用户订单文档,目前它们没有任何关系哦,那么,就分别以下面两种方式来实现该关系建立。

 

2、嵌入建模

这里的嵌入指的是将若干个订单文档直接嵌入到用户文档中,待查询用户信息获得订单信息,具体如下实现:

首先,我们需要修改上面给到的user文档结构,为其添加个orders字段,该字段是用来存放用户所有订单的数组,我们需要使用如下代码:

>db.user.update({'orders':{$exists:false}},{$set:{'orders':[{'_id' :ObjectId("57e89b3ab316d2e13cc0ba9c"),'orderno':'YD000001','title':'席梦思抱枕一对','oriprice':80,'discount':0.70,'saleprice':56.00,'type':'居家','coupon':0}]}})

 

上面语句的意识是如果原文档中不存在orders字段,那么新增该字段,并添加对应的内容,结果显示如下则说明嵌入成功,如下:

{

     "_id" :ObjectId("57e89964b316d2e13cc0ba9b"),

     "username" :"marky@123.com",

     "nickname" : "marky",

     "address" : "云端路1024号,柯南私募基金大厦",

     "contact" :"13141250012",

     "created" :"2012-07-07",

     "orders" : [

         {

              "_id" :ObjectId("57e89b3ab316d2e13cc0ba9c"),

              "orderno" :"YD000001",

              "title" : "席梦思抱枕一对",

              "oriprice" : 80,

              "discount" : 0.7,

              "saleprice" : 56,

              "type" : "居家",

              "coupon" : 0

         }

     ]

}

 

3、模型引用

这里的引用指的是直接在user文档中,同样增一个orders字段,不同的是我们不必像嵌入模型的方式那么麻烦,我们只需要将订单的objectid放入到user文档的orders即可,具体操作如下:

首先,需要使用update语句,如下:

>db.user.update({'_id':ObjectId("57e89964b316d2e13cc0ba9b")},{$set:{'orders':[ObjectId("57e89b3ab316d2e13cc0ba9c"),ObjectId("57e89bcfb316d2e13cc0ba9d")]}})

 

结果,显示如下:

{

     "_id" :ObjectId("57e89964b316d2e13cc0ba9b"),

     "username" :"marky@123.com",

     "nickname" : "marky",

     "address" : "云端路1024号,柯南私募基金大厦",

     "contact" :"13141250012",

     "created" :"2012-07-07",

     "orders" : [

          ObjectId("57e89b3ab316d2e13cc0ba9c"),

         ObjectId("57e89bcfb316d2e13cc0ba9d")

     ]

}

 

注意:

此种方式建立的文档关联的优点:当用户文档中,存在大量的订单文档时,如果选用直接嵌入方式嵌入订单,那么Mongodb检索引擎会一次性检索所有的订单数据,性能大大降低;反之,使用引用的方式,数据库引擎需要两次检索,第一次检索用户文档中的ObjectId,然后第二次则通过这些ObjectId检索对应的订单信息,索然检索两次,但效率相比于前者大大提高了,实际使用时推荐引用方式建立关系。

 

二、原子操作

在MongoDB中,文档的操作不支持事务,但是其对文档的保存,修改及删除等都是原子的,也就是要么操作完成,要么操作不完成,不存在中间不确定状态,因此可以保证数据的完整性。

MongoDB官方提供了一种做法,就是分两阶段提交,基本原理就是利用写操作的幂等性,但是有个前提条件:业务实现过程中,可以忽略中间态的不一致性,但最终结果是一致的,实际上绝大多数需求,只要结果一致就可,也希望MongoDB在这方面做更好的拓展和优化。

 

1、数据模型

这里还是以上面的user文档为例,不过我们需要为文档新增一个计数器字段,它没有实际的业务作用,只是用来作为操作原子性的标志位avaliable,具体代码如下:

{

     "_id" :ObjectId("57e89964b316d2e13cc0ba9b"),

     "username" :"marky@123.com",

     "nickname" : "marky",

     "address" : "云端路1024号,柯南私募基金大厦",

     "contact" :"13141250012",

     "created" : "2012-07-07",

     "orders" : [

         ObjectId("57e89b3ab316d2e13cc0ba9c"),

         ObjectId("57e89bcfb316d2e13cc0ba9d")

     ],

     "available": 1

}

注意:

此种添加一个计数器标志位的方式虽然可以实现原子性,但对于业务的紧密度及可理解方面不友好,所以实际上,我们只在比较敏感的文档操作时才使用,比如:金钱交易等。

 

2、如何操作

如何操作?其实就是使用findAndModify()方法实现即可,如果找到的文档的计数器available值不为-1为大于等于0时,则代表有新的操作状态,然后在更新新的操作,同时同步修改available为默认值,具体如下:

>db.user.findAndModify({query:{_id:ObjectId("57e89964b316d2e13cc0ba9b"), available:{$gt:0}},update:{$inc:{ available:-1},$set:{created:'2012-07-08'}}})

 

返回的结果:

{

     "_id" : ObjectId("57e89964b316d2e13cc0ba9b"),

     "username" :"marky@123.com",

     "nickname" : "marky",

     "address" : "云端路1024号,柯南私募基金大厦",

     "contact" :"13141250012",

     "created" :"2012-07-08",

     "orders" : [

         ObjectId("57e89b3ab316d2e13cc0ba9c"),

         ObjectId("57e89bcfb316d2e13cc0ba9d")

     ],

     " available" : 0

}

 

3、常用命令

$set

用来指定一个键并更新键值,若键不存在并创建。

{ $set : { field :value } }

 

$unset

用来删除一个键。

{ $unset : { field: 1} }

 

$inc

$inc可以对文档的某个值为数字型(只能为满足要求的数字)键进行增减的操作。

{ $inc : { field :value } }

 

$push

用法:

{ $push : { field: value } }

把value追加到field里面去,field一定要是数组类型才行,如果field不存在,会新增一个数组类型加进去。

 

$pushAll

同$push,只是一次可以追加多个值到一个数组字段内。

{ $pushAll : {field : value_array } }

 

$pull

从数组field内删除一个等于value值。

{ $pull : { field: _value } }

 

$addToSet

增加一个值到数组内,而且只有当这个值不在数组内才增加。

 

$pop

删除数组的第一个或最后一个元素

{ $pop : { field :1 } }

 

$rename

修改字段名称

{ $rename : {old_field_name : new_field_name } }

 

$bit

位操作,integer类型

{$bit : { field :{and : 5}}}

 

三、ObjectID

我们已经使用若干个文档,每个文档都有ObjectId唯一标志,它是由MongoDB为每个新建的文档自动生成的ID,共12个字节,结构如下:

l   前4个字节为时间戳;

l   接下来3个字节为机器识别码;

l   再接下来2个字节为集成id(pid);

l   最后3个字节为随机数;

 

MongoDB数据库中,存放的文档必须分配一个”_id”键,这个键的值可以为任何类型的数据,默认为ObjectId对象类型。在一个集合里,每个结合里的文档必须有个唯一的”_id”,来确保每个文档的唯一性。另外,MongoDB采用ObjectId作为标志,而不是其他常规的做法(如自动增加的主键)的主要原因,是因为在多个服务器上同步自动增加主键值比较费时费力。

 

1、新建ObjectID

我们可以为文档手动生成ObjectID,或者替代MongoDB自动生成的ObjectID,操作很简单,如下:

>myObjectId = ObjectId()

ObjectId("57eb2cf20cf7e671c6c93397")

上面操作后,返回一个12字节的新的ObjectID哦。

 

2、获取时间戳

我们知道,ObjectID的_id存储了4字节的时间戳,所以大多情况不需要额外存储时间,只需要直接从ObjectID获取即可,获取的方法及结果:

>ObjectId().getTimestamp()

ISODate("2016-09-28T02:41:50Z")

此时,返回的时间格式为ISO日期格式,我们可以将其转为标准的日期格式,具体如下操作:

首先,获取ISODate的时间戳:

>ObjectId().getTimestamp().valueOf()

1475030904000

 

其次,从时间戳中获取日期时间:

>var datetime = new Date(1475030904000);var year=datetime.getFullYear();varmonth=datetime.getMonth()+1;var day=datetime.getDate();varh=datetime.getHours();var minu=datetime.getMinutes();varsec=datetime.getSeconds();vardatestr=year+"-"+month+"-"+day+""+h+":"+minu+":"+sec;print(datestr)

2016-9-28 10:48:24

 

所以,最后的日期时间为:2016-9-28 10:48:24

 

3、ObjectID字符串

实际使用中,我们有时会将objectid转为字符串格式,可以这样:

>var myobjid=ObjectId().str;print(myobjid)

57eb33d40cf7e671c6c9339a

 

四、Map Reduce

MongoDB中支持Map-Reduce数据拆合计算模型,也就是将大数据量的计算分解为多个子模块执行(Map),并在最终将结果合并为最终的结果(Reduce)。而且,该技术十分灵活,对于大数据的分析也很使用。

 

1、语法格式

语法格式:

>db.collecion.mapReduce(

     function(){emit(key,value)},

     function(key,values){return reduceFunction},

     {

         out:collecion,

         query:document,

         sort:document,

         limit:number

})

 

参数说明:

使用Map-Reduce需要实现两个函数,分别为map和reduce函数,而map函数会调用emit(key,value),遍历集合中所有的文档记录,并将key和value作为参数传递给reduce()函数处理。

 

A、map:映射函数,生成键值对序列,并将其作为reduce函数参数;

B、reduce:统计函数,也就是将多个键值对数组,便成单一的键值;

C、out: 统计结果存放集合 (不指定则使用临时集合,在客户端断开后自动删除);

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

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

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

 

2、如何使用

A、文档准备

{

     "_id" :ObjectId("57e89b3ab316d2e13cc0ba9c"),

     "orderno" : "YD000001",

     "title" : "席梦思抱枕一对",

     "oriprice" : 80,

     "discount" : "0.70",

     "saleprice" : "56.00",

     "type" : "居家",

     "coupon" : 0

}

{

     "_id" :ObjectId("57e89bcfb316d2e13cc0ba9d"),

     "orderno" : "YD000002",

     "title" : "席梦思抱软硬适中床垫",

     "oriprice" : 280,

     "discount" : "0.80",

     "saleprice" : "224.00",

     "type" : "居家",

     "coupon" : 0

}

 

B、如何查询

>db.order.mapReduce(function(){emit(this.title,1)},function(key,values){returnArray.sum(values)},{query:{type:"居家"},out:"order_total"})

{

     "result" :"order_total",

     "timeMillis" : 84,

     "counts" : {

         "input" : 2,

         "emit" : 2,

         "reduce" : 0,

         "output" : 2

     },

     "ok" : 1

}

 

结果参数说明:

result:存放reduce处理的结果,是临时的集合,待Map-Reduce关闭删除;

timeMillis:单位为毫秒,执行花费的时间;

input:满足条件,被发送到map函数的文档个数;

emit:在map函数中,emit被调用的次数,也是所有集合中的数据总量;

output:结果集合中文档的数目;

ok:是否执行成功,1为成功;

err:如果失败,会有失败的原因;

 

C、find查看结果

只需要在B查询的最后,加上find()即可,具体如下:

>db.order.mapReduce(function(){emit(this.title,1)},function(key,values){returnArray.sum(values)},{query:{type:"居家"},out:"order_total"}).find()

{"_id" : "席梦思抱枕一对", "value" : 1 }

{"_id" : "席梦思抱软硬适中床垫", "value" : 1 }

 

实际使用中,往往会构建较为复杂的复合查询,但万变不离其宗哦。

五、GridFS

GridFS是MongoDB用来处理存储大于16M的资源文件,比如:图片、音频及视频等,而MongoDB,则巧妙的将这些文件存储在文档集合中。

 

1、工作原理

GridFS首先将大文件拆分为多个小的文件片段(chunk),默认为256k/个大小,而每个chunk将被作为MongoDB的一个文档,被存储在chunks集合中,这就是GridFS的工作原理,简单吧!

 

2、集合特点

GridFS采用两个集合来存储拆分后的大文件,分别为chunks集合和files集合,前者是用来存储文件片段的实际内容,而后者则用来存储文件的meta数据,也就是一些文件基本信息数据,下面查看下这个集合的格式:

A、fs.files:

{

"_id": ObjectId("57ecdaa74ab844328ad9d851"),

"chunkSize": 261120,

"uploadDate": ISODate("2016-09-29T09:11:03.941Z"),

"length": 85777688,

"md5": "c7313999503a9f739662b5d363551166",

"filename": "/temp/DSCN0470.MOV"

}

 

B、fs.chunks:

{

   "files_id": ObjectId("634b75d19f42bfec8a2fe44c"),

   "n": NumberInt(0),

   "data": "Hello ,this is binarydata!"

}

 

3、如何使用

A、添加文件

使用GridFS的put命令,来存储一.mov视频文件DSCN0470.MOV,其大小为80.5M,此时我们需要调用MongoDB安装目录bin下的mongofiles命令,具体如下:

$sudo./mongofiles -d mydb put /temp/DSCN0470.MOV

2016-09-29T17:11:03.474+0800     connected to: localhost

added file: /temp/DSCN0470.MOV

 

参数说明:

mydb:数据库名字

put:输入命令

DSCN0470.MOV:视频文件

addedfile:添加文件返回的结果,代表添加完成

 

B、查看文件

>db.fs.files.find()

 

结果如下:

{

"_id": ObjectId("57ecdaa74ab844328ad9d851"),

"chunkSize": 261120,

"uploadDate": ISODate("2016-09-29T09:11:03.941Z"),

"length": 85777688,

"md5": "c7313999503a9f739662b5d363551166",

"filename": "/temp/DSCN0470.MOV"

}

 

从上面结果可以查看到fs.chunks集合中所有文件模块,也可以得到文件的_id值,通过这个值,我们可以查看到该文件被拆分的所有的文档数据,具体如下操作:

>db.fs.chunks.find({files_id:ObjectId("57ecdaa74ab844328ad9d851")})

 

由于返回的结果数据较大,这里不罗列出来。

 

C、列出文件

$sudo./mongofiles -d mydb list

2016-09-29T17:41:47.739+0800     connected to: localhost

/temp/DSCN0470.MOV 85777688

/temp/DSCN0470.MOV      85777688

 

注意:

因为put了两次视频文件,所以结果返回两个文件,因为存储时指定数据库为mydb,所以罗列时也必须指定,否则没有结果返回。

 

D、下载文件

$sudo ./mongofiles -d mydb get /temp/DSCN0470.MOV

 

结果返回:

2016-09-29T17:47:19.448+0800     connected to: localhost

finished writing to /temp/DSCN0470.MOV

 

E、删除文件

$sudo ./mongofiles -d mydb delete /temp/DSCN0470.MOV

 

结果返回:

2016-09-29T17:48:46.780+0800     connected to: localhost

successfully deleted all instances of'/temp/DSCN0470.MOV' from GridFS

 

查看是否删除成功:

$sudo./mongofiles -d mydb list

2016-09-29T17:50:06.095+0800  connected to: localhost

 

上面的结果已经说明删除完成了。

 

 

 

 

 

好了,Mongodb高级部分(一)就介绍到这里,由于作者水平有限,如有问题请在评论发言或是QQ群讨论,谢谢。

 

 

 

 

技术讨论群:

276592700(新)

1 0
原创粉丝点击