文档引用
-
1. 前言
- 我们在进行系统设计的时候,如果后台数据库选择的是关系型数据库,通常会先进行业务模型设计。而业务模型背后所对应的就是存储业务数据的表,以及它们之间的关联关系。当选择MongoDB 作为系统后台数据库时,在文档的设计上会经常使用到嵌入和引用的方式,这个特性在关系型数据库中是没有的。
- 由于MongoDB的非关系特点我们在文档的设计上可以尽量的合理利用数据库本身的特性,多采用反范式设计会在很大的程度上提高业务系统性能,下面就MongoDB文档的设计模式做一些简单介绍。
2. 嵌入式文档
2.1 简介- MongoDB 的join查询可以通过引用来实现。可以将文档内容嵌入到另一个文档中,也可以将文档内容引用到另一个文档中。嵌入意味着要把某一类型的数据,如包含更多数据的数组,嵌入到文档本身。引用意味着创建一个引用,包含另一个文档的数据。
2.2 什么是嵌入
- 例如:我们想使用一个关系型数据库来记录CD、DVD和购买信息。在这个数据中,需要一个表来收集CD,另一个表来存储CD歌曲信息。因此,如果要查询特定的信息就可能需要查询多个表的。对于MongoDB 或者其他非关系型数据库,会更容易的嵌入这些信息,采用这种方法会使数据库简洁,确保所有相关信息保存在单一的文档中,同时,检索数据更快。对于非关系型数据库,文档看起来如下所示:
[ { "_id" : ObjectId("5353463193efef02c962da73"), "Type" : "CD", "Artist" : "Nirvana", "Title" : "Nevermind", "Tracklist" : [ { "Track" : "1", "Title" : "Smells like teen spirit", "Length" : "5:02" }, { "Track" : "2", "Title" : "In Bloom", "Length" : "4:15" } ] } ]
- 上面这个例子中,歌曲信息嵌入到文档本身中,在关系型数据库中至少需要两个表,在非关系型数据库中,仅需要一个集合和一个文档。在检索信息时,只需要数据从一个文档加载到内存中,而不必加载多个文档。
3. 引用
3.1 简介- 在很多场景下,把数据嵌入到文档足以满足很多应用程序,如上所示。然而,有时就需要引用另一个文档。在 MongoDB 中的文档引用是通过执行额外的查询来解决的。MongoDB 提供两种方式来实现:手动引用和使用DBRef标准。
3.2 手动引用
- 手动引用是最简单最直接的方式。当手动引用数据时,将其他文档的_id值存储在你的文档中。实例如下:
> use ttlsa_com switched to db ttlsa_com > apress = ( { "_id" : "Apress", "Type" : "Technical Publisher", "Category" : ["IT", "Software","Programming"] } ) { "_id" : "Apress", "Type" : "Technical Publisher", "Category" : [ "IT", "Software", "Programming" ] } > db.publisherscollection.insert(apress)
> book = ( { "Type" : "Book", "Title" : "Definitive Guide to MongoDB, the", "ISBN" : "987-1- 4302-3051-9", "Publisher" : "Apress","Author" : ["Membrey,Peter","Plugge, Eelco","Hawkins, Tim"] } ) { "Type" : "Book", "Title" : "Definitive Guide to MongoDB, the", "ISBN" : "987-1-4302-3051-9", "Publisher" : "Apress", "Author" : [ "Membrey,Peter", "Plugge, Eelco", "Hawkins, Tim" ] } > db.mediaCollection.insert(book)
> book = db.mediaCollection.findOne({"ISBN" : "987-1-4302-3051-9"}) { "Author" : [ "Hawkins, Tim", "Plugge, Eelco" ], "ISBN" : "987-1-4302-3051-9", "Publisher" : "Apress", "Title" : " Different Title 2", "Type" : "Book", "_id" : ObjectId("5353462f93efef02c962da71") } > book.Publishe Apress > db.publisherscollection.findOne( { _id : book.Publisher } ) { "_id" : "Apress", "Type" : "Technical Publisher", "Category" : [ "IT", "Software", "Programming" ] }
3.3 DBRef
- DBRef提供了一个更正式的规范引用文档之间的数据。在DBRef中,数据库引用以标准的JSON/ BSON嵌入对象存储的。语法如下:
{ $ref : <collectionname>, $id : <id value>[, $db : <database name>] }
代表引用的集合的名称。 所引用的文档的_id值。 - $db是可选的,引用的文档所在的数据库的名称。示例如下:
> apress = ( { "Type" : "Technical Publisher", "Category" :["IT","Software","Programming"] } ) { "Type" : "Technical Publisher", "Category" : [ "IT", "Software", "Programming" ] } > db.publisherscollection.save(apress) > apress { "Type" : "Technical Publisher", "Category" : [ "IT", "Software", "Programming" ], "_id" : ObjectId("53588c221697e7511678752c") } > book = { "Type" : "Book", "Title" : "Definitive Guide to MongoDB, the", "ISBN" : "987-1-4302-3051-9", "Author": ["Membrey, Peter","Plugge,Eelco","Hawkins, Tim"], Publisher : [ new DBRef ('publisherscollection',apress._id) ] } { "Type" : "Book", "Title" : "Definitive Guide to MongoDB, the", "ISBN" : "987-1-4302-3051-9", "Author" : [ "Membrey, Peter", "Plugge,Eelco", "Hawkins, Tim" ], "Publisher" : [ DBRef("publisherscollection", ObjectId("53588c221697e7511678752c")) ] } > db.media.save(book) > db.media.find().toArray() [ { "_id" : ObjectId("53588ce01697e7511678752d"), "Type" : "Book", "Title" : "Definitive Guide to MongoDB, the", "ISBN" : "987-1-4302-3051-9", "Author" : [ "Membrey, Peter", "Plugge,Eelco", "Hawkins, Tim" ], "Publisher" : [ DBRef("publisherscollection", ObjectId("53588c221697e7511678752c")) ] } ]
4. 嵌入与引用比较
- 嵌入 :小的子文档,数据不经常改变,当最终一致性是可以接受的,文档增长小,经常需要进行二次查询来获取数据,读快。
- 引用 :大的子文档,非易失性数据,当实时一致性是必要的,文档增长大,经常需要从结果中排除数据,写快。
5. 什么是反范式
反范式是通过增加冗余数据或数据分组来提高数据库读性能的过程。在某些情况下, 反范式有助于掩盖关系型数据库软件的低效。关系型的范式数据库即使做过优化, 也常常会带来沉重的访问负载。
数据库的范式设计会存储不同但相关的信息在不同的逻辑表, 如果这些表的存储在物理上也是分离的,那么从几个表中完成数据库的查询可能就会很慢 (比如JOIN操作)。如果JOIN操作的表很多,那么可能会慢得离谱。 有两个办法可以解决这个问题。首选的方法是使逻辑上的设计遵循范式, 但允许数据库管理系统(DBMS)在磁盘上存储额外的冗余信息来加快查询响应。 在这种情况下,DBMS还要保证冗余副本与原始数据的一致性。 这种方法通常在SQL中以索引视图(微软的SQL Server)或物化视图(Oracle)实现。 视图将信息表示为方便查询的格式,索引确保视图上的查询进行了优化。
更常见的做法是对数据做反范式设计。这种方法同样能提高查询响应速度, 但此时不再是DBMS而是数据库设计者去保证数据的一致性。 数据库设计者们通过在数据库中创建规则来保证数据的一致性,这些规则叫约束。 这样一来,数据库设计的逻辑复杂度就增加了,同时额外约束的复杂度也增加了, 这使该方法变得危险。此外,“约束”在加快读操作(SELECT)的同时,减慢了写操作 (INSERT, UPDATE和DELETE)。这意味着一个反范式设计的数据库, 可能比它的范式版本有着更差的写性能。
反范式数据模型与没有范式化的数据模型不同。 只有在范式化已经达到一定的满意水平并且所需的约束和规则都已经建立起来, 才进行反范式化。例如,所有的关系都属于第三范式, 连接的关系和多值依赖得到了妥善处理。