MongoDB应用设计技巧

速度和完整性的折中

多文档中使用的数据既可以采用反范式的内嵌方式,也可采用范式化的引用方式。两种策略没有优劣之分,各有优缺点,关键是要选择适合自己的应用场景。

极高的性能和瞬间一致性不可兼得

网上购物车

范式化设计

# 商品
db.products.insert({_id:product_id, name:name: price:price, desc:description})
# 订单
db.orders.insert({_id:order_id, user:userinfo, items:[produce_id1, product_id2...]})

每个订单文档中存放商品的编号,当需显示订单内容时可查询订单集合获取对应订单,之后查询商品集合获取指定商品编号的商品信息。

问题是这种设计下,一次查询无法获取完整的订单。若商品信息更新了,所有引用此商品的文档都会更新,因为这些文档只是指向了保存商品信息的文档。

范式化的结构是读取速度较慢,但所有订单的一致性会由保证,多个文档会原子性地更新,因为仅仅引用的文档会更新。

反范式化设计

# 商品
db.products.insert({_id:product_id, title:title, price:price, desc:description})
# 订单
db.orders.insert({
  _id:order_id,
  user:userinfo,
  items:[
    {_id:product_id, title:title, price:price},
    {_id:product_id, title:title, price:price},
    ...
  ]
})

将商品作为内嵌文档存在订单数据中,当显示订单时仅需一次查询。若商品信息变化了需要更新,就要改动所有相应的订单。

反范式化读取速度较快,一致性稍弱。商品信息的变更不能原子性地更新到多个文档。

考虑因素

  • 是否总是要额外读取一次几乎不怎么改变的数据呢?
    可能读了商品信息一万次才会修改一次详细信息,为了一次写入快一点儿或保证一致性,搭上上万次的读取消耗值得吗?绝大多数应用都具有高读写比,不妨测算下应用的读写比。
    你认为引用的数据多久更新一次呢?更新越少越适合反范式化。有些极少变化的数据几乎根本不值得引用,例如姓名、生日、股票代码、地址。

  • 一致性很重要吗?
    多个文档可能需要原子性地更新,若设计的是一个交易系统,特定的证券必须在指定的时间交易,那么会希望在不能交易时瞬间将其全部锁起来。为此就可以用一个锁文档来引用这组证券文档。但是这种操作最好在应用层处理,因为应用需知道何时上锁何时解锁。
    当应用中的不一致性难以被容忍时也必须考虑一致性。订单从商品中获取信息,但商品不会从订单中获取信息。若有多个信息源文档,就比较难取舍了。
    若希望将某个商品打八折,此时不想改变已有订单的任何信息,仅仅是想更改一下商品的描述。这里要的是某一时刻的数据快照。

  • 要不要快速的读取呢?
    若想读取尽可能快,则要反范式化。

订单文档非常适合反范式化,因为其中的商品信息不经常变化,就算变了也不必更新到所有订单。范式化在此就没什么优势可言。

适应未来的数据要范式化

范式化使数据可用性更长久,将来可在不同的应用中以不同方式查询范式化的数据。

前提是有些数据将会年复一年不断地被各种应用使用,的确有这种数据,大多数人的数据都会不断地演化,陈旧数据要么被更新要么被丢弃。大多数人都希望数据库尽可能地对当前查询做出快速响应,若将来查询变了,他们会针对新的查询优化数据库。

应用若很成功其数据经常会高度定制化。当然这并不是说这些数据不能用到别处,而且你很可能至少想要对数据做些元分析。但这和适应未来不同,适应未来代表满足未来10年内的所有查询。

尽量单个查询获取数据

MongoDB的数据库设计要从应用单元的查询出发,所谓应用单元,对于web或移动应用,可将对后端的一次请求视作一个应用单元。类似的,对于桌面应用一次用户交互算作一个应用单元,对于分析系统一个图表的加载算作一个应用单元。应用程序中这些离散的应用单元涉及与数据库的交互。

你可能感兴趣的:(MongoDB应用设计技巧)