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(新)
- MongoDB高级部分及实现(一)
- MongoDB高级部分及实现(二)
- MongoDB基础部分及实现(一)
- MongoDB基础部分及实现(二)
- mongodb学习2(高级部分)
- MongoDB 高级查询(一)
- 高级部分(百度地图一)
- MongoDB之高级查询一
- MongoDB学习(一)初识NoSql及MongoDB
- Mongodb知识及使用总结(一)
- MongoDB系列(一):简介及安装
- MongoDB系列(一):简介及安装
- MongoDB一(介绍及安装)
- JavaScript高级编程(一)-基础部分笔记
- 高级办公自动化试题及答案(一)
- vSphere高级功能(一)——VMotion及SVMotion的实现
- Android开发,MapBox的使用及部分功能实现(一)----- 初始化、标记、定位、styleurl
- mongodb进阶一之高级查询
- 全球同服架构设计
- 获取验证码倒计时
- 从TS流到PAT和PMT
- Report Studio 汇总
- 创建第一个vue应用
- MongoDB高级部分及实现(一)
- css+div布局,版心的使用(一)拉伸不会改变布局
- C# 队列处理日志
- Web实验3之CSS选择器的运用
- Kotlin的基本类型
- Jtable隔行分别颜色显示
- Thrift C++ server 线程模型
- JDBC初步学习
- BZOJ 1864