2019独角兽企业重金招聘Python工程师标准>>>
技巧一:速度与完整性的折中
在关系数据库中,我们一般范式化设计,而且有时候又会有冗余来提高查询效率,这里说的就是范式化和反范式化两种策略取舍建议。
已商品订单为例,范式化的设计:
//商品
{
"_id":productId,
"name":name,
"price":price,
"desc":description
}
//订单
{
"_id":orderId,
"user":userInfo,
"items":[
productId1,
productId2,
productId3,
]
}
反范式化设计:
//商品(与上面相同)
{
"_id":productId,
"name":name,
"price":price,
"desc":description
}
//订单
{
"_id":orderId,
"user":userInfo,
"items":[
{ "_id":productId1,
"name":name1,
"price":price1
},
{ "_id":productId2,
"name":name2,
"price":price2
},
{ "_id":productId3,
"name":name3,
"price":price3
}
]
}
范式化读取慢,但一致性有保证(因为这是ID引用),反范式化读取快,一致性削弱了。
我们在选择时,主要考虑以下几点:
是否总是额外读取一次几乎不怎么改变的数据?
一致性很重要吗?
要不要快速的读取?
技巧三:尽量单个查询获取数据
MongoDB的数据库设计要从应用单元的查询出发,应用单元可以理解成对后端的一次请求,很多情况下一个应用单元要对应多个文档,但尽量只通过单条查询来完成这种请求。
技巧四:嵌入关联数据
这个和第一个类似,那些只有一个键一个值的表(如标签、权限、省市)在MongoDB中几乎都应该嵌入,还有某些信息只在一个文档中使用,则应该嵌入这个文档。
技巧五:嵌入时间点数据
什么是时间点数据呢?订单中的商品图片与价格都是时间点数据,比如A用户100元购买,过段时间打折了,B用户可能70就购买了同样的商品。同样,订单中的收货地址也是时间点数据,若某人更新了个人信息,并不需要更改以往订单的收货地址。
技巧六:不要嵌入不断增加的数据
MongoDB存储数据的机制决定了对数组不断追加数据是很低效的。嵌入20个、100个、1000000个子文档都不是问题,关键是提前就这么做, 之后基本保持不变。对数组,特别是动态数组数据结构了解的同学应该知道,如果数组长度是10,那么追加第11个元素时,系统一般是新分配20个长度的空 间,把原来数组的10个元素复制过去,再追加第11个元素。MongoDB文档的分配也是一样的,所以,放任文档不断增长会使系统慢得让你受不了的。
技巧七:预填充数据
如果已经知道未来要用到哪些字段,第一次插入的时候就定义这些字段会比用到时再动态创建效率更高,这点要效仿关系型数据库了,比如月统计:
{
"_id":201403,
"day1":0,
"day2":0,
......,
"day31":0
}
//或用数组
{
"_id":201403,
days:[0,0,......,0]
}
这一思想也适用于其他类型的数据,甚至集合和数据库也可以用类似的方法。若每天都要使用新的集合,最好也提前创建。
技巧八:尽可能预先分配空间
这个和技巧六、七关系紧密。只要知道文档开始比较小,后来会变成确定大小,就可以用这种方法优化。具体的方法就是用垃圾字段填充到和最终文档大小一样大,然后删除该字段。
> collection.insert({"_id":123, /* 其他字段*/, "garbage":"someLongString"})
> collection.update({"_id":123},{"$unset":{"garbage":1}})
技巧九:用数组存放要匿名访问的内嵌数据
内嵌信息到底用数组还是用子文档?这个要根据查询来,如果确切知道要查询的内容,则要用子文档。如果不清楚查询的具体内容,则要用数组。这个举例说明反而麻烦,具体做法就是先设计出来,看能否满足你的查询,如果不行,就换另一种好了!
技巧十:文档要自给自足
MongoDB几乎不做任何数据的处理,仅仅存储数据,要尽量遵守这点。像求平均值或求和,要放在客户端去做。所以,那些在关系数据库里的一般聚合查询,在MongoDB里,最好用单独的字段保存起来。
技巧十一:优先使用$操作符
有些情况查询不能用$操作符表达,这时可以使用$where字句来执行JavaScript代码:
db.members.find({"$where":function(){
return this.member[0].age==this.member[1].age;
}});
this就是每一个文档,正如预期,$where为查询带来了极大的灵活性,但效率着实不高!
效率不高是因为MongoDB要为此做很多事情,一般的查询(没有$where的查询),客户端将查询转换成BSON,MongoDB 中数据也是BSON格式,所以可以直接比较,而用$where时,MongoDB要将集合每个文档转换成JavaScript对象,解析文档BSON并添 加所有字段到JavaScript对象中,然后执行JavaScript代码,之后就销毁,所以极其消耗时间和资源。
如果非要用$where不可时,要尽量减少$where匹配的文档数来缓解性能损失,具体做法就是在$where条件前加限定条件。
技巧十二:随时聚合
要尽可能用$inc随时计算聚合,这点和技巧十相应,如果前期没有随时聚合,应该都通过批处理计算完成。