阅读 752

MongoDB干货总结

什么是MongoDB

基于分布式文件存储的数据库,介于非关系型和关系型数据库之间。

MongoDB和MySQL对比

MongoDBMySQL
DatabaseDatabase
Collection(集合)Table
Document(文档)Row
FieldColumn

MongoDB有什么特点

(1)面向集合存储数据,数据以json格式存储;

(2)查询功能强大,几乎可以实现所有关系型数据库的单表查询功能;

(3)自带分布式文件系统,方便集群部署;

(4)自带了对map-reduce运算框架的支持。

MongoDB为什么快

(1)没有事务约束;

(2)使用的内存映射技术 ,写入数据时候只要在内存里完成就可以返回给应用程序,写入磁盘是异步操作;热点数据放入内存;数据在磁盘中相对集中,减少随机读写耗费的磁头定位时间;

(3)没有join,不使用关系。没有关系的存在,就表示每个数据都好比是拥有一个单独的存储空间,然后一个聚集索引来指向。

MongoDB适用场景

(1)网站实时数据处理,如PV、UV、统计商品的收藏次数、加入购物车次数、曝光次数等。

(2)服务器日志记录;

(3)大数据量,TB甚至PB级别的数据存储;

(4)数据模型经常变化,需要支持水平扩展

(5)游戏场景,使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、更新

物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以 MongoDB 内嵌数组的形式来存储,一次查询就能将订单所有的变更读取出来。

社交场景,使用 MongoDB 存储存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附近的人、地点等功能

物联网场景,使用 MongoDB 存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析

视频直播,使用 MongoDB 存储用户信息、礼物信息等。

MongoDB不适用场景

(1)数据修改需要严格的事物控制;

(2)功能的业务关系复杂、涉及到多表关联;

(3)比较重要的业务数据,如订单、商品、用户等数据。

MongoDB集合

MongoDB的集合,可类比于MySQL的表,字段(field)可类比于MySQL的列(column)。区别是,集合的filed是可动态扩展的,并且数据类型没有限制,不像Mysql需要先定义列名、列的字段类型等。

此外,集合无需像mysql的表一样事先定义。使用db.collectionName.insert()语句,插入一条json数据,该集合便生成了。

集合由文档组成,每个文档就是一条json数据。

插入文档

向集合中插入一个文档(类比向表中插入一条数据):

 `db.getCollection("b2b_theme_activity").insert( { 

  "merchant_code": "1ed0528595197975fa72f5ec8f9e0c62",

  "dfx_type": NumberInt("1"),

  "create_time": ISODate("2020-03-16T22:36:01.964Z"),

  "member_code": "e69e61a77b1ccc7b90be7995c6a6021f",

  "user_name": "李郃",

  "merchant_name": "南京好享家智能家居有限公司",

  source: NumberInt("10"),

  "visit_duration": "",

  type: NumberInt("11"),

  "member_name": "测试企业开户01",

  "theme_activity_page_title": "测试test01",

  "login_name": "17366180925",

  "user_id": "1161150535707852801",

  event: NumberInt("20"),

  "theme_activity_id": "1238383672212533250"

} );  


复制代码

修改文档

语法

修改文档的语法是:

db.collection.update(

  <query>,

  <update>,

  {

   upsert: <boolean>,

   multi: <boolean>,

   writeConcern: <document>

  }

)
复制代码
  • query : update的查询条件,类似sql update查询内where后面的。
  • update : update的对象和一些更新的操作符(如,,inc...)等,也可以理解为sql update查询内set后面的
  • upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。
  • multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。
  • writeConcern :可选,抛出异常的级别。

例子

现有集合:t_test_log,希望修改requestUri为:/ops/api/b2b/theme-activities的文档的requestSource值为1,且只修改符合条件的第一条数据,脚本如下:

db.t_test_log.update({'requestUri':'/ops/api/b2b/theme-activities'},{$set:{'requestSource': '1'}})
复制代码

如果想把所有符合条件的文档数据都修改了,则加上:{multi:true}

db.t_test_log.update({'requestUri':'/ops/api/b2b/theme-activities'},{$set:{'requestSource': '2'}}, {multi:true})
复制代码

删除文档

语法

db.collection.remove(
   <query>,
   <justOne>
)
复制代码
  • query:查询
  • justOne:是否只删除符合条件的第一个文档,true:是,false:否 默认false

例子

删除符合条件的第一个文档:

db.t_test_log.remove({'requestUri':'/ops/api/b2b/theme-activities/11111/22222/testPut'},1)
复制代码

删除符合条件的所有文档:

db.t_test_log.remove({'requestUri':'/ops/api/b2b/theme-activities/11111/22222/testPut'})
复制代码

查询

简单查询

select * from  spu where  spu_name="test0123"

db.spu.find({ spu_name:"test0123" })
复制代码

and

select * from  spu where  spu_name="test0123" and  type=3

db.spu.find({spu_name:"test0123", type:"3" });
复制代码

like

select * from  user where  username like "%test0123%"

db.user.find({username:/test0123/});
复制代码

大于等于、小于等于

//大于、大于等于、小于、小于等于 $lt, $lte, $gt, $gte
select * from  user where  age>=10 and age<=20

db.user.find({ age:{$gte:10, $lte:20} });
复制代码

等于null

select * from  user where  address is null

db.user.find({ address: null });
复制代码

不为null

select * from  user where  address is not null

db.user.find({ address: {$ne:null} });
复制代码

不为null 且不为""

select * from  user where  address is not null and address !=''

db.user.find({ address: {$nin:[null,"" ]} });
复制代码

in/not in

db.activity.find({ event: {$in:[50,60,70]} });
db.activity.find({ event: {$nin:[50,60,70]} });
复制代码

count

db.activity.find().count();
db.activity.find({ event: {$in:[50,60,70]} }).count();
复制代码

distinct

db.activity.distinct("user_name");
复制代码

sort

db.getCollection("activity").find({type:10}).sort({createTime:1});
复制代码

时间段

查询某个时间段的数据

db.activity.find({createTime:{$gte:ISODate('2020-03-01T00:00:00.000Z'), $lte:ISODate( '2020-03-11T00:00:00.000Z' )}});
复制代码

聚合

聚合查询

MongoDB中聚合(aggregate)主要用于处理数据(求平局值、求和等),并返回计算后的数据结果。类似sql中的group。

$sum求和

按user_id分组,求每个用户订单总额

db.order.aggregate([{$group : {_id : "$user_id", total_order_money: {$sum : "$order_money"}}}])
复制代码

$avg求平均值

按user_id分组,求每个用户订单平均支付金额

db.order.aggregate([{$group : {_id : "$user_id", avg_order_money: {$avg : "$order_money"}}}])
复制代码

$min求最小值

按user_id分组,求每个用户订单最小支付金额

db.order.aggregate([{$group : {_id : "$user_id", min_order_money: {$min : "$order_money"}}}])
复制代码

$max 求最大值

按user_id分组,求每个用户订单最大支付额

db.order.aggregate([{$group : {_id : "$user_id", max_order_money: {$max : "$order_money"}}}])
复制代码

$push

按user_id分组,查看每个用户所有的支付方式(不去重)

db.order.aggregate([{$group : {_id : "$user_id", payMethods: {$push: "$pay_method"}}}])
复制代码

$addToSet

按user_id分组,查看每个用户所有的支付方式(去重)

db.order.aggregate([{$group : {_id : "$user_id", payMethods: {$addToSet : "$pay_method"}}}])
复制代码

$first

按user_id分组,求每个用户第一笔订单金额

db.order.aggregate([{$group : {_id : "$user_id", first_order_money : {$first : "$order_money"}}}])
复制代码

$last

按user_id分组,求每个用户最后笔订单金额

db.order.aggregate([{$group : {_id : "$user_id", last_order_money: {$last : "$order_money"}}}])
复制代码

聚合管道

聚合管道是 MongoDB 2.2版本引入的新功能。它由阶段(Stage)组成,文档在一个阶段处理完毕后,聚合管道会把处理结果传到下一个阶段。

聚合管道功能:

  • 对文档进行过滤,查询出符合条件的文档

  • 对文档进行变换,改变文档的输出形式

    下图是官网给出的聚合管道的流程图:

上图中,match为筛选条件,将符合条件的数据筛选出来传递给下一阶段match 为筛选条件,将符合条件的数据筛选出来传递给下一阶段 group 中进行分组求和计算,最后返回 Results。其中,matchmatch、group 都是阶段操作符,而阶段 group中用到的group 中用到的 sum 是表达式操作符。

常用阶段操作符

阶段操作符描述
$project修改文档的结构,可以用来重命名、增加或删除文档中的字段。
$match过滤条件。$match 尽量出现在管道的最前面,过滤出需要的数据,在后续的阶段中可以提高效率。
$group分组
$sort排序
$limit限制结果条数,可用于实现分页查询
$skip跳过指定数量的文档,并返回余下的文档。
$unwind将文档中数组类型的字段拆分成多条,每条文档包含数组中的一个值。

实例1(聚合查询)

按user_id分组,求每个用户订单状态为20的订单平均金额

mysql:
select  user_id, avg(total_amount) from t_child_order 
where child_order_status=20
group by merchant_code;

mongo:
db.t_child_order.aggregate
( {$match:{child_order_status:20}}   ,       
{$group:{_id:"$user_id",  avgDur:{$avg:"$total_amount" }}  }

);
复制代码

其中,match表示查询条件;match表示查询条件;group表示通过user_id分组,并计算user_id相同值的visit_duration的平均数据。

实例2 (管道操作)

需求: 统计2020-03-01至2020-04-18期间,活动ID为1239575334792146946,且操作事件为查看活动详情页面(event:10)该活动每天的访问PV、每天的平均访问时长、每天的访问用户集合。

db.activity.aggregate(
[ 
	//条件,筛选出来的数据,传入到下一阶段
	{"$match" : { "activity_id" : '1239575334792146946', "create_time" : { "$lte" : ISODate( '2020-04-18T00:00:00.000Z' ), "$gte" : ISODate('2020-03-01T00:00:00.000Z') }, "event" : 10} }, 

	//设置数据列的展示规则,然后传入到下一阶段
	{ "$project" : 
			{ "create_time" : { "$dateToString" : { "format" : "%Y-%m-%d", "date" : "$create_time" } }, "visit_duration" : 1, "user_id" : 1		      
			} 	
	}, 
	
	//分组,计算数量、求和、求平均,然后传入到下一个阶段
	{ "$group" : { "_id" : "$create_time", "ALIAS_PAGE_PV" : { "$sum" : 1 }, "ALIAS_AVG_VISIT_DURATION" : { "$avg" : "$visit_duration" }, "ALIAS_USER_ARRAY" : { "$addToSet" : "$user_id" } } 
	},   
	
	//设置结果是否展示
	{ "$project" : { "_id" : 0, "create_time" : "$_id", "ALIAS_PAGE_PV" : 1, "ALIAS_AVG_VISIT_DURATION" : 1, "ALIAS_USER_ARRAY" : 1,  } 
	}, 
	
	//排序
	{ "$sort" : { "create_time" : -1 } } ,

    //限制结果条数
	 {"$limit":3}
] 

);
复制代码

查询结果如图:

Map Reduce

Map-Reduce是一种计算模型,可对复杂的计算分解执行,然后将结果合并。

使用 MapReduce 要实现Map 函数和 Reduce 函数。

Map 函数调用 emit(key, value), 遍历 collection 中所有的记录, 将 key 与 value 传递给 Reduce 函数进行处理。

Map 函数必须调用 emit(key, value) 返回键值对。

db.collection.mapReduce(
   function() {emit(key,value);},  //map 函数
   function(key,values) {return reduceFunction},   //reduce 函数
   {
      out: collection,
      query: document,
      sort: document,
      limit: number
   }
)
复制代码
  • map :映射函数 (生成键值对序列,作为 reduce 函数参数)。
  • reduce 统计函数,reduce函数的任务就是将key-values变成key-value,也就是把values数组变成一个单一的值value。。
  • out 统计结果存放集合 (不指定则使用临时集合,在客户端断开后自动删除)。
  • query 一个筛选条件,只有满足条件的文档才会调用map函数。(query。limit,sort可以随意组合)
  • sort 和limit结合的sort排序参数(也是在发往map函数前给文档排序),可以优化分组机制
  • limit 发往map函数的文档数量的上限(要是没有limit,单独使用sort的用处不大)

下图为MongoDB官网给出的Map-Reduce工作流程,从订单集合中,查找status为A的文档,按照cust_id分组,求每个cust_id的订单总额。

Map-Reduce使用实例

//统计每个用户的总访问时长
db.activity.mapReduce(
    function() {emit(this.user_id, this.visit_duration);},
    function(key, values) {		    
				return Array.sum(values)
		

		},
	{
	    query: {
	        activity_id: "1236122613668032514",
						event:10
	    },
				
			 out:"temp_result"
	}

).find();
复制代码
//统计每个用户的平均访问时长
db.activity.mapReduce(
    function() {emit(this.user_id, this.visit_duration);},
    function(key, values) {
		    var total= Array.sum(values);
				var sizes= values.length;
				return total/sizes
		

		},
	{
	    query: {
	        activity_id: "1236122613668032514",
						event:10
	    },
				
			 out:"temp_result"
	}

).find();
复制代码
//统计每个用户的访问该页面的次数
db.activity.mapReduce(
    function() {emit(this.user_id, 1);},
    function(key, values) { return Array.sum(values)},
    {
        query: {
            activity_id: "1236122613668032514",
						event:10
        },
				

			 out:"temp_result"
	}

);
db.temp_result.find();
复制代码

文章分类
后端
文章标签