上面是对学生 Zhang Scan 的记录存储,可以看出我们把学生成绩当作是学生表的内嵌字段,由于是一对多的关系,我们把他存储成一个数组的形式。这种基于 JSON 文档的存储结构有一下几点优势:
数据一目了然,当你从数据库中取出一条学生记录后,关于学生的基本信息全部显示出来。方便大家阅读浏览。
避免了多次数据库表连接操作。在关系型数据库中存在着多种表之间的链接操作,比如左右连接,内连,外连等等。为了找到关于一个学生的全部信息,我们也许需要进行若干张表的连接才能拿到想要的数据。除了需要写更复杂的 SQL 语句以外,数据库的性能也会受到影响。当数据库进行一次连接操作时,内部可能是需要从磁盘不同位置读取数据,加大了 IO 操作。反观 MongoDB,一次查询只需要读取一次磁盘,大大提高的查询效率。
删除、修改操作简单方便。如果所有相关学生的信息都存储在一张 Collection 中,那么对学生信息的删除和修改只需要在一张表中操作就可以。试想一下在关系型数据库中,如果需要删除一个学生纪录,有可能需要操作学生表、成绩表、宿舍表、等等与学生关联的所有表,这样的设计是困扰关系型数据库开发人员的一大难题。搞不好数据库中就会存储大量过时、失效的数据,而这些数据可能成为永远也不会被访问的死角。
所有 Document 都是自我描述的,这方便大家进行数据库的水平扩展。在 MongoDB Shard 中,我们可以将一个 Collection 切分到不同的 Shard 集群中,这种切分方法在不需要进行 JOIN 的操作前提下变得十分简单。因为,DBA 再不用担心需要进行夸节点的 JOIN 操作。(关于 MongoDB 水平扩展的内容,情参考另外一篇文章MongoDB 的水平扩展,你做对了吗?。
内嵌还是引用上面是一个将一对多关系的两张表整合到一个 Document 中,实际上我们的数据表结构会复杂很多,一个企业级应用动辄就要设计几百甚至上千张表,表之间会有一对一,一对多,多对多种关联关系。对于如此复杂的场景目前我们还没有一个准确的可以使用任何情况的解决方案。基本上都需要针对业务数据具体分析,从而得出新的数据结构。这里,我可以给大家列出一些基本的原则以及处理不同关系的基本方法,根据这些基本原则方法我想大家总可以根据自己的业务归纳出一个行之有效的解决方案。具体到 MongoDB,有内嵌和饮用两种方式来进行关联,下面我们分布看一下它们应用的场景。
内嵌就像上面举的例子那样,将关系型数据中表的一行内嵌到与他相关联的表中使之在新的 Collection 中成为一个 Document。这种内嵌的方法适用于两种情况:
当表关系是一对一时,或者
当表关系是一对多时
在上面两种关系下,如果关系表不经常单独进行查询,它只是依附在主表查询的基础上进行,那么我们可以考虑使用内嵌的方法。以产品和产品价格为例说明一下,在纪录产品价格时,价格是会随着时间的变化而取不同的值。一款新上线的产品价格相对较高,随着时间的推移其价格也会随之下降。在一些类似双十一节假日期间,价格也会临时调整。在分析产品销售状况的时候,我们还要考虑到在什么样的价格下产品销量高,所以不能简单的把产品和价格放到一张表中,必然会存在一张与产品相关联的价格表,它纪录了产品当前价格以及历史价格。那么,我们在统计产品的销量报表时,这张价格表不会单独存在,它必然会依附在产品表之下。此时,将产品价格内嵌到产品表中就是一个比较可行的方案。查询语句可以通过一个 Collection 找出所有产品相关价格从而避免了表之间的 JOIN 操作。
但是并不是所有的一对一和一对多的关系都适合使用内嵌的方式。在一下情况下应当慎重使用内嵌数据结构:
如果一个 Document 的大小超过了 MongoDB 的限制(16M),此时不应考虑嵌入数据结构。当你的数据表关系很复杂,可能将所有相关的数据内嵌到一个 Document 中会超过 16M 的限制。