设为首页 收藏本站
查看: 1026|回复: 0

[经验分享] MongoDB实战-面向文档的数据(找到最合适的数据建模方式)

[复制链接]

尚未签到

发表于 2018-10-25 07:36:09 | 显示全部楼层 |阅读模式
  前一段时间一直研究通过Ruby操作MongoDB数据库,在学习的过程中也分享了自己学习成长的过程,撰写了包含两篇入门操作文章和十二篇进阶文章。本篇文章开始,我们将进入MongoDB的实战操作流程,MongoDB这一非关系型数据库-是一个文档型数据库,存储的是面向文档的数据。

  •   如何在MongoDB数据库中使用schema
  设计数据库schema是在已知数据库系统特性、数据本质以及应用程序需求的情况下为数据集选择最佳表述的过程。传统的关系型数据库RDBMS中鼓励使用正规化的数据模型,从而确保数据的可查询性和解决数据更新带来的不一致问题。但是schema的设计不是一门精确的科学。当出现要应用程序处理非结构化数据,或者应用程序对性能要求很高时,就可能会要求一个通用的数据模型。MongoDB中缺乏硬性Schema设计规则。
  为了能够参考传统RDBMS的schema设计规则,我们首先需要清楚RDBMS和MongoDB在如下三个方面的对应关系和相应区别:

  •   数据的基本单元分别是什么?
      在RDBMS中,数据的基本单元指的是带有列和行的数据表;
      在键值存储中指向不定类型值的键;
      在MongoDB中,数据的基本类型是BSON文档
  •   如何查询和更新数据?
      数据查询操作中:
      RDBMS支持即时查询和联结操作查询;
      MongoDB支持即时查询,但是不支持联结操作;
      简单的键值存储只能根据单个键来获取值
      数据更新操作中:
      RDBMS中,可以使用SQL以复杂的方式来更新文档,将多条更新封装在一个事务中可以获得原子性,还可以回滚;
      MongoDB不支持事务,但支持多种原子操作,这些操作可以作用于复杂文档的内部结构;
      简单的键值存储中,可以更新一个值,通常每次更新都是将值完全替换掉。
  •   应用程序的访问模式是什么?
      要想确定理想的数据模型,必须问无数个与应用程序有关的问题。读写比?需要何种查询?数据如何更新?并发问题?数据机构化程度?
  总的来说,最好的schema设计总是源于对正在使用的数据库的深入理解,对应用程序需求的准确判断以及过去的经验。
  2. 实战-设计电子商务数据模型
  在本部分,我们将演示如何在MongoDB中对电子商务数据进行建模,我们会关注产品与分类、用户与订单、产品评论。对很多开发人员来讲,数据建模总会伴随着对象映射。使用对象映射器有利于进行验证、类型检查和关联。MongoDB没有对象映射的需要,一方面是因为文档已经是类似对象的表述了,同时驱动程序为MongoDB提供了相当高阶的接口,参考前序博文的学习,使用驱动接口就能在MongoDB上构建完整的应用程序。很多成熟的MongoDB对象映射器在基本语言驱动之上又提供了一层额外的抽象。
  由于最终还是需要跟文档打交道,关注文档本身,认识到一个精心设计的MongoDB Schema里文档是什么样的,能让我们更好的使用该数据库。
  2.1 产品与分类
  产品和分类是会出现在任何电子商务网站的信息。在传统的RDBMS中,产品会使用大量的数据表,比如存储基本信息的表,存储关联送货信息和价格历史的表,以及其他可能会出现的一系列复杂属性。这种多表schema在RDBMS的表联结能力的帮助下很有用。
  但是在MongoDB数据库中,对产品进行建模会相对简单。集合并不一定有schema。任何产品信息文档都可以容纳产品所需的各种动态属性。通过使用数组来容纳内部文档结构,还可以将RDBMS里的多表描述为一个MongoDB集合。
  下面是一个取自园艺商店的示例产品
doc={  
   _id:new ObjectId("59884b76b53fab2a8024b6ad"),
  
   slug:"wheel-barrow-9092",
  
   sku:"9092",
  
   name:"Extra Large Wheel Barrow",
  
   description:"Heavy duty wheel barrow",
  
   details:{
  
       weight:47,
  
       weight_unite:"1bs",
  
       model_num:40392882,
  
       manufacturer:"Acme",
  
       color:"Green"
  
   },
  
   total_review:4,
  
   average_review:4.5,
  
   pricing:
  
   {
  
     retail:589700,
  
     sale:489700
  
   },
  
   price_history:[
  
   {
  
   retail:529700,
  
   sale:429700,
  
   start:new Date(2010,4,1),
  
   end:new Date(2010,4,8)
  
   },
  
   {
  
   retail:529700,
  
   sale:529700,
  
   start:new Date(2010,4,9),
  
   end:new Date(2010,4,16)
  
   }
  
   ],
  
   cateory_ids:[
  
    new ObjectId("59884ee3b53fab2a8024b6ae"),
  
    new ObjectId("59884ee3b53fab2a8024b6af")
  
   ],
  
   main_cate_id:new ObjectId("59884ee3b53fab2a8024b6b1"),
  
   tags:["tools","gardening","soil"]
  
}
  在此,如果要为文档生成一个URL,通常建议设置一个短名称字段。且该字段应该有唯一索引,这样就可以把其中的值用作主键。假设将此文档存储在products集合里,可以像下面一样创建唯一性索引。
db.products.ensureIndex({slug:1},{unique:true})  由于在slug上存在唯一索引,插入文档时需要使用安全模式,这样就可以知道插入成功与否。在Ruby中执行插入代码
@products.insert({:name=>"Extra Large Wheel Barrow",  
                 :sku=>"9092",
  
                 :slug=>"wheel-barrow-9092"},
  
                 :safe=>true
  
                 )
  代码中的:safe=>true;如果插入成功,就不会抛出异常,表明选择了一个唯一的短名称;如果抛出异常,代码需要使用一个新的短名称进行重试。上述文档中后续存储了details-不同产品的详细信息,接着存储了当前价格pricing和历史价格price_history,category_ids存储了标签名称的数组。
  RDBMS数据库可以使用join操作进行多表联合查询。作为不支持联结查询的MongoDB数据库,如何支持多对多的策略呢?文档中存储category_ids数组,其中包含的是一个对象ID的数组,每个对象ID都是一个指针,指向某个分类文档的_id字段。下面是一个分类文档的演示:
doc={  
     _id:new ObjectId("59884ee3b53fab2a8024b6ae"),
  
     slug:"gradening-tools",
  
     ancestors:[
  
     {
  
        name:"Home",
  
        _id:new ObjectId("59884ee3b53fab2a80240003"),
  
        slug:"home"
  
     },
  
     {
  
        name:"Outdoors",
  
        _id:new ObjectId("59884ee3b53fab2a80240001"),
  
        slug:"outdoors"
  
     }
  
     ],
  
     parent_id:new ObjectId("59884ee3b53fab2a80240001"),
  
     name:"Gardening Tools",
  
     description:"Gardening gadgets galore"
  
}
  观察产品文档的category_ids字段里的对象ID,发现该产品关联了Gardening Tools分类。在产品文档中放入category_ids的数组键让那些多对多的查询成为可能。
  查询Gardening Tools分类里的所有产品
db.products.find({category_ids=>category{'_id'}})  查询指定产品的所有分类,可以使用$in操作符,它类似于SQL的IN指令。
db.categories.find({_id:{$in:procuct['category_ids']}})  分类文档中,存放父文档数组的含义是去正规化,将上级分类的名称放入每个子分类的文档里,这也是由于MongoDB不支持关联查询。这样一来,查询Gardening Tools分类时,就不需要执行额外的查询来获取上级分类(Outdoors和Home)的名称和URL了。
  2.2 用户与订单
  看看如何对用户和订单建模,以此阐明另一种常见关系——一对多关系。一个用户可能会拥有多张订单。在RDBMS中,会在订单表中使用外键;在MongoDB中惯例很相似,如:
doc=  
{
  
  _id:new ObjectId("6a5b1476238d3b4dd5000001"),
  
  user_id:new ObjectId("4a5b1476238d3b4dd5000001"),
  
  state:"CART",
  
  line_items:[{
  
      _id:new ObjectId("4a5b1472134d3b4dd5000921"),
  
      sku:"9092",
  
      name:"Extra Large Wheel Barrow",
  
      quantity:1,
  
      pricing:{
  
          retail:5897,
  
          sale:4897,
  
       }
  
  },
  
  {
  
      _id:new ObjectId("4a5b1472134d3b4dd5000922"),
  
      sku:"10027",
  
      name:"Rubberized Work Glove,Block",
  
      quantity:2,
  
      pricing:{
  
          retail:1499,
  
          sale:1299,
  
       }
  
  }
  
  ],
  
  shipping_address:{
  
    street:"588 5th Street",
  
    city:"Brooklyn",
  
    state:"NY",
  
    zip:11215
  
  },
  
  sub_total:6196
  
}
  订单中的第二个属性user_id保存了一个用户的_id,它是指指向示例用户的指针。这样的设计可以方便地查询关系中的任意一方。要查找一个用户的所有订单:
db.orders.find({user_id:user{'_id'}})  要获取指定订单的用户同样简单:
user_id=order['user_id']  
db.users.find({_id:user_id})
  上面的订单表述方式有明显的优点,首先,它易于理解,完整的订单概念都能被封装在一个实体里,包括条目明细、送货地址以及最终的支付信息。查询数据库时,可以通过一条简单的查询返回整个订单对象。其次,可以把产品在购买时的信息保存在订单文档里,这样能够轻易地查询并修改订单信息。
  用户的文档也使用了类似的模式。保存了一个地址文档的列表和一个支付方式的列表。在文档的最上层还能找到任何用户模型里都有的基本常见属性。
doc={  
_id:new ObjectId("4a5b1476238d3b4dd5000001"),
  
email:"kylebanker@gl.com",
  
first_name:"Kyle",
  
last_name:"Banker",
  
hashed_password:"bd1cfa194c3a603e7186780824b04419",
  
address:[
  
{ name:"home",
  
  street:"588 5th Street",
  
  city:"Brooklyn",
  
  state:"NY",
  
  zip:10010
  
},
  
{
  
  name:"work",
  
  street:"1 E.23rd Street",
  
  city:"New York",
  
  state:"NY",
  
  zip:10010
  

  
}
  
],
  
payment_methods:[{
  
  name:"VISA",
  
  last_four:2127,
  
  crypted:"43f6baldfda6b8106dc7",
  
  expiration_date:new Date(2014,4)
  
}
  
]
  
}
  2.3 评论信息
  一般产品都会有评论信息。一般而言,一个产品有多个评论,该关系是也用对象ID应用product_id来编码的。
doc={  
_id:new ObjectId("4c4b1476238d3b4dd5000041"),
  
product_id:new ObjectId("59884b76b53fab2a8024b6ad"),
  
date:new Date(2010,5,7),
  
title:"Amazing",
  
text:"Has a squeaky wheel,but still a darn good wheel barrow",
  
rating:4,
  
user_id:new ObjectId("4a5b1476238d3b4dd5000001"),
  
user_name:"dgreenthumb",
  
helpful_votes:3,
  
voter_ids:[
  
{new ObjectId("59884b76b53fab2a8024b600")},
  
{new ObjectId("59884b76b53fab2a8024b601")},
  
{new ObjectId("59884b76b53fab2a8024b602")}
  
}
  上面的评估信息中,由于MongoDB不支持联结查询,所以冗余存储了user_name,同时还有一个voter_ids数组,用于存储对该评论进行投票的用户。去除了重复投票,同时也让我们有能力查询某个用户投过票的所有评论。
  至此,我们已经覆盖了电子商务的数据模型了,讲解了具体的建模方法,以及由于MongoDB不支持联结查询带来的局限性问题的去正规化解决方案,从而找到一个最适用于应用的schema。



运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.iyunv.com/thread-626090-1-1.html 上篇帖子: MongoDB语法实践 下篇帖子: MongoDB 文档字段增删改
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表