1.反范式设计
先看看关系数据库做个关联查询怎么做的
比如2个表如下,一个订单有多个产品
products:{
'id',
'name',
'price',
'desc'
}
orders:{
'id',
'user',
'items':[product_id1,product_id2,product_id3]
}
这种做法关系数据库肯定没问题,一个表关联查询就行了。但mongodb则不得不查询多次,否则只能拿到产品id而已。大大降低查询性能
那要一次的话如下设计:
orders:{
'id',
'user',
'items':[
{ 'id':product_id1, 'name':..., 'price':..., 'desc':... },
{ 'id':product_id2, 'name':..., 'price':..., 'desc':... },
{ 'id':product_id3, 'name':..., 'price':..., 'desc':... }]
}
这样只需要一次查询便可,而且还能用elemMatch方法对items进一步过滤,就和关联查询差不多了。虽然DBRef也能做到,但DBRef不能进一步过滤。但这种方式缺点就是你在更新products时放弃了一致性。比如产品名字和价格变化,那老的数据还在order里,但这个需求是正确的,因为产品变了,但当时的订单的确不该变,不是吗。
何时需要反范式设计:
1.你认为引用的数据是否需要保持最新的?不是的话用反范式设计。(比如产品和订单的例子)
2.要保持最新,那更新频率多久一次?如果极少变化,那适合用反范式设计。
3.必须做到查询快速。那你得用反范式。
但你可以在修改时,实时或延时同步过去,看数据量是否庞大,太庞大就算了。
比如2个表如下,一个订单有多个产品
products:{
'id',
'name',
'price',
'desc'
}
orders:{
'id',
'user',
'items':[product_id1,product_id2,product_id3]
}
这种做法关系数据库肯定没问题,一个表关联查询就行了。但mongodb则不得不查询多次,否则只能拿到产品id而已。大大降低查询性能
那要一次的话如下设计:
orders:{
'id',
'user',
'items':[
{ 'id':product_id1, 'name':..., 'price':..., 'desc':... },
{ 'id':product_id2, 'name':..., 'price':..., 'desc':... },
{ 'id':product_id3, 'name':..., 'price':..., 'desc':... }]
}
这样只需要一次查询便可,而且还能用elemMatch方法对items进一步过滤,就和关联查询差不多了。虽然DBRef也能做到,但DBRef不能进一步过滤。但这种方式缺点就是你在更新products时放弃了一致性。比如产品名字和价格变化,那老的数据还在order里,但这个需求是正确的,因为产品变了,但当时的订单的确不该变,不是吗。
何时需要反范式设计:
1.你认为引用的数据是否需要保持最新的?不是的话用反范式设计。(比如产品和订单的例子)
2.要保持最新,那更新频率多久一次?如果极少变化,那适合用反范式设计。
3.必须做到查询快速。那你得用反范式。
但你可以在修改时,实时或延时同步过去,看数据量是否庞大,太庞大就算了。
2.不要把不断增加的数据嵌入
首先说说什么叫嵌入,数据库一对多,有2种做法,比如帖子和评论2个表,评论有个字段是帖子ID,那帖子和评论就是1对多,这个做法是引用。如果帖子有个字段叫评论(是个放ID的数组)这个就是内嵌。
不断增加的数据,mongodb对数组追加效率是很低的。不管10个1000个10万个都不算啥,但之后要保持不变,否则会慢得你受不了。比如一个帖子的评论,那就应该使用引用做法,而不是内嵌
不断增加的数据,mongodb对数组追加效率是很低的。不管10个1000个10万个都不算啥,但之后要保持不变,否则会慢得你受不了。比如一个帖子的评论,那就应该使用引用做法,而不是内嵌
3.嵌入做法预留空间
mongodb如果要增加内嵌的数组,是很慢的,要分配空间。但是只是修改内容是很快的。
所以如果你在插入数据时,明确知道总共多少条或最多多少条,可以先预留空间。放几个0或空字符串填充。
所以如果你在插入数据时,明确知道总共多少条或最多多少条,可以先预留空间。放几个0或空字符串填充。
4.尽量避免让mongoDB做计算
由于mongodb是种无脑的大型数据库,几乎不会做任何高效的处理。虽然group,mapreduce,和聚合功能都有,但效率不佳。所以尽量避免这样去做。
比如你有个表
{id, appleCount, orangeCount}分别有苹果和橘子总数,你当然可以用上面方式得到他们总数,但你最好增加一个totalCount字段来达到这个效果
比如你有个表
{id, appleCount, orangeCount}分别有苹果和橘子总数,你当然可以用上面方式得到他们总数,但你最好增加一个totalCount字段来达到这个效果
5.要有几个定时任务协助mongodb
1. 一致性修复器
使用反范式模式,但你有希望数据一致性
2.预分配器
创建今后文档需要用到的空间,否则到了应用会很慢
3.聚合器
通过增加表或增加字段的方式聚合数据
4.结构校验器
确保当前文档都有必填字段,否则矫正或通知。
当然以上都不能做到实时
使用反范式模式,但你有希望数据一致性
2.预分配器
创建今后文档需要用到的空间,否则到了应用会很慢
3.聚合器
通过增加表或增加字段的方式聚合数据
4.结构校验器
确保当前文档都有必填字段,否则矫正或通知。
当然以上都不能做到实时
6.ObjectId
无意义的主键用ObjectId存储,千万别转换成字符串。
1.方便查询
2.含有有用信息
3.字符串要多占两倍磁盘
1.方便查询
2.含有有用信息
3.字符串要多占两倍磁盘
7.如果有自己主键,请不要用默认主键
默认主键_id,如果你有自己的主键就不要用它,节省空间资源。
要知道1亿数据下,主键要占几百M内存。
要知道1亿数据下,主键要占几百M内存。
8.DBRef尽量避免使用
1.浪费一个字段$ref的空间2.完全可以自己用id做引用的范式设计代替这个功能
9.小文件数据不要用GridFS
GridFS查询性能较差。如果确定都是小文件(小于16M)请存储在表里
10.不要到处使用索引
比如你要返回集合中50%记录,就不该使用索引,比不用索引还慢。
所以,索引一般用在返回结果只是总体数据的一小部分时,超过一般就不要用了。
如果已经简历索引,但又在大规模查询不用索引,就用{"$natural":1} 强制禁用索引
写入操作大大多于查询操作,不要使用索引,否则写入会变慢。
所以,索引一般用在返回结果只是总体数据的一小部分时,超过一般就不要用了。
如果已经简历索引,但又在大规模查询不用索引,就用{"$natural":1} 强制禁用索引
写入操作大大多于查询操作,不要使用索引,否则写入会变慢。
11.建立分级文档加速扫描
假如你有一个表有10个字段,其中一个是zip,你查询如下
那mongodb会遍历每个字段来查找zip字段
使用内嵌文档我们能这样设计
把10个字段分成3~4个组,每个字段里面放字段
那么查询语句就变成
1.
db.users.find({
'zip'
:‘10003’})
使用内嵌文档我们能这样设计
把10个字段分成3~4个组,每个字段里面放字段
那么查询语句就变成
1.
db.users.find({'address.zip'
:‘10003’})
12.and 和 or查询
对于and,越是结果少的要放在条件的前面
对于or则相反,越是结果多的放在条件的前面
原理自己想想吧
对于or则相反,越是结果多的放在条件的前面
原理自己想想吧
13. 日志和备份节点
日志(-journal)和备份节点都会影响性能,而且都能做为数据恢复用,每台服务器2个都开启实在没啥必要。
建议是主纯备份节点可以用日志, 主节点和热备节点可以不用。如果没有纯备份节点,就用某个从节点开启日志
建议是主纯备份节点可以用日志, 主节点和热备节点可以不用。如果没有纯备份节点,就用某个从节点开启日志
14.开发时使用notablescan参数
开启这个选项,只要做全表扫描都会报错。开发时开启很有用,但生产环境不要开启