MongoDB - 应用中的使用

一、范式化和反范式化

    个人认为范式化和反范式化是使用MongoDB的时候最重要的因素之一,其余的因素个人认为主要的还有如下:

    1、字段长度的准确的定义;

    2、索引的使用(与业务关联度非常高)

    3、集群的使用(主从复制、副本集、分片;主要是考虑应用的数据量和需要响应是时间要求)

    但是前提条件还是在针对具体应用的时候,梳理关系,考虑应用系统的并发访问量、响应时间要求等;所以需要专门理解范式化和反范式化,由于MongoDB不像关系型数据库可以支持join操作,所以每一次的查询只能查询一个集合,所以范式化和范式化的设计对于使用MongoDB的应用来说,重要性远高于关系型数据库。

    比如,我们现在的电商商品系统(主要考虑到商品数据的变化性,利用MongoDB集合的动态模式)就是使用的MongoDB,为了表示一个商品可以使用多个Map(map中再去存储动态的key和value)进行存储数据,而key又是一个数据字典表的主键id,完全按照关系型数据库的完全范式化关系进行存储,其关系非常的复杂,导致需要查询其他系统需要的rpc商品属性接口的时候,需要在循环中多次嵌套去查询其具体的属性值,例如字典表的数据一般是基本是不会变化的,就是变化的时候,再去将索引的数据改变(即使非常影响性能),但是方便每一次的查询性能,这种牺牲也是可以接受的。

1、范式化

    在针对应用查询的关联关系的梳理的时候,再复杂的关系无非也就是一对一、一对多和多对多进行表示。只是在传统行业的关系型数据库中需要使用外键的形式强关联,而一般互联网行业则不会使用外键关联。完全按照范式化的存储数据则如下:

    1)、一对一

    主表中有一个另一个表的主键字段,则可以根据其进行查询另一张表的该条数据即可(如用户主表有一个用户附表的主键id)。

    2)、一对多

    主表中有多个另一张表的主键id字段,则可以根据另一张表的id,在该表中查询到多条数据(如一个部门下有多个员工,则可以根据部门表的id在员工表中查询到多条数据)。

    3)、多对多

    多对多则需要使用一张中间表进行关系,中间表一般只有三个id字段(比如用户与角色的关系,一个用户可以有多个角色,一个角色可以赋给多个用户,需要查询某一个用户拥有的角色,则可以在中间表中根据用户id查询到多个角色id再join角色表获取角色的其他字段)。

2、反范式化

  很多时候即使是在关系型数据库中,完全的范式化进行数据存储,即使我们能通过join去获取数据(少增加一个或多次链接宝贵的数据库资源,哪怕是在使用数据库连接池的情况下),则我们也需要适当的反范式化。比如很多时候在显示用户信息列表的时候显示部门名称,则可以将部门名称冗余到用户表中,增加一个字段。则引申出来的问题就是当部门名称修改的时候需要(同步或异步)修改用户表中的部门名称字段。是否进行反范式化的冗余存储数据,完全取决于查询性能的要求,查询数据的并发量等。

    由于Mongo中完全不能在一次查询中返回其他集合是字段数据,则应该会比在关系型数据库中会更加多的使用反范式化,也更加多的需要关注范式化和反范式化。

 

二、基数

    如上面所说的需要引用(外键关联)其他集合的时候则成为一个基数。由于MongoDB数据库中不能使用join进行外键的关联,需要大量使用反范式化的方式冗余数据。那么怎么样的数据需要进行反范式化操作呢?比较难以权衡,则需要将关系进行重新梳理,一对一、一对多,和多对多需要进行重新定义。

    当一对多和多对多的时候,需要对多的一方进行严格的划分。多可能是真的比较多,也可能多的数量(基数)不太多并且可控(可知),如现在我们系统中,已知有一个字典表一般也就十多条数据,一般增加也就一两条,并在一般不会进行修改。则可以将关系多分为多和少,按照数量进行分类。则在权衡使用内嵌文档的形式还是引用数据的形式存储数据,一般基数的可以使用内嵌方式关联,一般基数的建议使用引用数据关联。

    当然需要考虑应用的具体情况,是需要快速响应还是写也会比较频繁。内嵌一般还需要考虑写(创建、修改)的频率较低,读取的场景较多;引用反之。可以参考以下原则:

嵌套引用
子文档较小子文档较大
数据不会定时改变数据经常改变
数据最终一致即可数据强一致性较高
文档数据小幅度增加文档数据大幅增加
数据通常需要二次查询数据经常不包含在结果集中
快速读取快速写入

 

三、管理优化

1、写性能优化

    由于除了分片类型的MongoDB集群,在大量写入的情况下性能很快会成为瓶颈。比如我们现在的商品系统,写的压力就比较大,并且暂时是使用副本集的集群方式。而且现在版本为2.6,写的时候会直接进行锁库,而不是锁集合,所以需要升级到3.2以上版本。除了以上因素外,当Document在修改的时候增加的字段体积较大的时候,需要将文档移动到其他能分片空间的位置,并且需要定时进行碎片整理(这就是我们原来的时候,用一段时间Mongo就会非常的卡,并且写入日志操作频繁,但是重启之后就非常的快,之前不是很清楚为什么)。

    为了解决以上重新调整文档位置的可能性,可以在创建文档的时候,在文档的最后创建一个比较大(合理)的字段,然后在修改数据的时候,每次带上一个选项,使用$unset将预创建的字段(如字段名叫occupySpace)进行删除操作,释放空间。当第一次修改文档的时候occupySpace字段会存在,则进行删除操作,否则什么也不做,如下:

    "$unset":{"occupySpace":true}  

 

2、删除旧数据

    很多时候由于应用程序处于各种原因需要保存一段时间的历史数据,但是只需要保存近期数据,则需要定时清除不需要的数据。但是需要注意,比如我们现在的版本为2.6,即使在删除数据(或者drop表)的情况下数据空间任然不能释放,需要升级到3.0以上版本,在删除数据的时候才能释放空间。

    比如我们现在数据库中,需要存储商家操作商品的记录信息(很多时候商家可能跨城市,或者由于店内多人可操作商品的CRUD),方便商家对商品操作的追溯;或者我们会记录某些快照信息等。则需要定时清除不需要的数据,或者定时将不需要的数据让大数据抽取进行保存,则短时间内的数据可以直接在平台进行追溯,一般商家也不会在几个月或者几年后才知道自己的线上商品信息被人改变了。偶尔需要查询较长时间的数据则可以在大数据平台,或者进行其他手段的查找。   并且最近发现,平台很多不经常用的类似数据,都是数以千万或者亿计。

    针对以上情况需要定时删除不需要数据,可以使用:

    1)、定长集合,

    2)、或者TTL集合(可以按照时间,精确的删除过期的文档),如MongoDB特殊的集合和索引--TTL

    3)、将数据按照日期,存储到不同的集合当中(根据日期:动态的插入和查找不同的文档)

 

3、数据库和集合的设计原则

    刚才提及Mongo数据库的写入和读取的时候,使用读写锁控制数据的写入和读取。Mongo数据库在3.0版本以前使用库基本的读写锁,之后才使用集合基本的读写锁。所以可以根据不同的版本,不同的数据读写频繁度特点,将数据存储到不同的数据库中。比如:日志类的文档,写入非常的频繁,并且数据重要性较差;商品等查询远大于写的操作;订单系统进行大量的写入操作。则可以考虑将其分到不同的数据库中进行存储。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值