一、數據模型介紹
MongoDB中的數據有着靈活的架構。與SQL數據庫不同,因為SQL數據庫必須先定義表結構,然后才能向其中插入數據,而MongoDB的集合不強制任何文檔結構。這個靈活性方便了文檔與實體或者對象之間的映射。每個文檔可以匹配所表示實體的數據域,哪怕這個數據后面會發生變化。當然實際應用中,最好還是讓集合中的文檔有着類似的結構。
數據模型最富有挑戰的意義是在於能平衡應用需要與數據庫引擎性能以及數據獲取模式。當設計數據模型時,總是會考慮應用程序對數據的使用(如查詢、更新和數據處理),以及數據本身的繼承結構。
文檔結構
為MongoDB應用程序設計數據模型的關鍵點是解析文檔的結構和應用程序如何表示數據之間的關系。應用程序表示數據關系可以有兩種方式:引用和嵌入文檔。
引用
引用通過包含鏈接或從一個文檔到另一個文檔的引用來存儲數據關系。應用程序可以通過解析這些引用來訪問有關數據。一般地我們稱之為規范化數據模型。
嵌入數據
嵌入文檔通過把數據存儲到一個獨立文檔結構中來獲取數據之間的關系。MongoDB允許將一個文檔結構嵌入到另一個文檔的字段或者數組中。這些去規范化數據模型允許應用程序在一個獨立的數據庫操作中獲取和操作有關數據。
寫操作的原子性
MongoDB的寫操作在文檔級別是原子性的,沒有單個寫操作對超過一個文檔或者超過一個集合是原子性的。帶有嵌入數據的去規范化數據模型將一個可表現實體的所有有關數據合並到單個文檔中。這有益於原子寫操作,因為單個寫操作可以對一個實體實現插入和更新。規范數據意味着把數據切分到不同的集合,這需要多個寫操作,而這些寫操作雖然自己本身是原子性的,但是合並起來看則不具有原子性。
文檔增長
有些更新,如將元素壓入數組或者添加新字段,則會增加文檔大小。
對MMAPv1存儲引擎而言,如果文檔大小超過為這個文檔分配的空間,MongoDB會在磁盤上遷移此文檔。當使用MMAPv1存儲引擎時,文檔增長的考慮會影響我們規范化數據還是去規范化數據。
數據使用和性能
設計數據模型時,考慮一下應用程序如何使用數據庫。例如,如果應用程序僅使用最近插入的文檔,那可以考慮使用Capped Collections。如果程序主要是對集合的讀操作,那么對集合添加索引可以提高性能。
二、文檔驗證
MongoDB在更新和插入操作期間可以驗證文檔。驗證規則使用validator選項在每個集合上指定,這個validator選項用一個文檔具現化驗證規則和表達式。這些表達式的指定可以使用任何查詢操作符,除了$geoNear, $near, $nearSphere, $text和$where。
如要對一個集合添加文檔驗證,使用帶validator選項的collMod命令。你可以在創建新集合時就指定文檔驗證規則,即,使用帶有validator選項的db.createCollection()方法,如下所示:
db.createCollection( "contacts", { validator: { $or: [ { phone: { $type: "string" } }, { email: { $regex: /@mongodb\.com$/ } }, { status: { $in: [ "Unknown", "Incomplete" ] } } ] } } )
MongoDB提供了validationLevel選項,這個選項決定了MongoDB在更新文檔時應用驗證規則的嚴格程度。還有一個是validationAction選項,這決定了MongoDB是否引發錯誤並拒絕違背驗證規則的文檔,還是給出警告並把違背驗證規則消息寫入日志同時接受無效的文檔。
行為
驗證在更新和插入時驗證。當你添加驗證規則到一個集合時,已經存在的文檔不會被驗證是否符合驗證規則,除非這些文檔被修改。
存在的文檔
可以使用validationLevel選項來控制MongoDB如何處理已存在的文檔,即是否驗證之前已存在的規則。
默認情況下,validationLevel值為strict,MongoDB應用驗證規則到所有的插入和更新操作。設置validationLevel為moderate,則應用驗證規則到插入操作和對已存在且滿足驗證標准的文檔的更新操作。在moderate級別下,對不滿足驗證標准的已存在文檔,MongoDB則不檢測這些文檔的有效性。
例子:
考慮以下contacts集合中的文檔:
{ "_id": "125876" "name": "Anne", "phone": "+1 555 123 456", "city": "London", "status": "Complete" }, { "_id": "860000", "name": "Ivan", "city": "Vancouver" }
用以下命令對contacts集合添加一個驗證器
db.runCommand( { collMod: "contacts", validator: { $or: [ { phone: { $exists: true } }, { email: { $exists: true } } ] }, validationLevel: "moderate" } )
現在contacts集合有一個moderate級別的驗證器。如果試圖更新_id為125876的文檔,MongoDB將應用驗證規則,因為這個已存在文檔匹配驗證條件。相反地,MongoDB不會應用驗證規則到_id為860000的文檔更新操作上,因為這個文檔原先不滿足驗證規則。
如果想完全禁用驗證規則,可以設置validationLevel為off。
接受或拒絕無效文檔
validationAction選項決定了MongoDB如何處理違反驗證規則的文檔。
默認地,validationAction為error,這時MongoDB拒絕任何違反驗證條件的插入或更新操作。當validationAction值設置為warn,MongoDB記錄任何違反驗證的信息到日志但是允許插入或者更新的操作處理。
例如,下例創建一個contacts集合,這個集合帶有一個驗證器,這個驗證器指定插入和更新文檔需要滿足以下三個條件中的至少一個條件:
- phone字段是string類型
- email字符滿足正則表達式
- status字段要么是Unknown,要么是Incomplete
db.createCollection( "contacts", { validator: { $or: [ { phone: { $type: "string" } }, { email: { $regex: /@mongodb\.com$/ } }, { status: { $in: [ "Unknown", "Incomplete" ] } } ], validationAction: "warn" } } )
有了驗證器之后,以下插入操作不滿足驗證規則,但是因為validationAction值為warn,故寫操作記錄此信息到日志。
db.contacts.insert( { name: "Amanda", status: "Updated" } )
日志信息包含了集合的全命名空間和不滿足驗證規則的文檔,以及操作時間
2015-10-15T11:20:44.260-0400 W STORAGE [conn3] Document would fail validation collection: example.contacts doc: { _id: ObjectId('561fc44c067a5d85b96274e4'), name: "Amanda", status: "Updated" }
限制
我們不能為admin,local和config數據庫指定驗證器,也不能對system.*集合添加驗證器。
繞過文檔驗證
用戶可以使用bypassDocumentValidation選項繞過文檔驗證,參考Document Validation可以獲得支持bypassDocumentValidation選項的命令的列表。
如果部署已經允許訪問控制,為了繞過文檔驗證,已認證用戶必須需要bypassDocumentValidation動作。內建的dbAdmin和restore角色提供了這個動作。
三、數據模型設計
有效的數據模型可以支持程序的需求。關鍵考慮點便是文檔結構采用嵌入式還是引用式。
嵌入數據模型
上面已經介紹過,即去規范化數據模型
引用數據模型
上面已經介紹過,即規范化數據模型
嵌入文檔的一對一模型
考慮以下patron與address之間的關系。這個例子表明了如果需要查看處於其他上下文中的數據實體,那么嵌入式比引用式數據模型具有優勢。在這個patron和address數據之間的一對一關系中,address屬於patron
在規范化數據模型中,address文檔包含了一個隊patron文檔的引用。
{ _id: "joe", name: "Joe Bookreader" } { patron_id: "joe", street: "123 Fake Street", city: "Faketon", state: "MA", zip: "12345" }
如果address數據需要頻繁獲取名字信息,那在引用式數據模型中,程序需要進行多個查詢來解析這個引用。更好的數據模型會把address數據嵌入到patron數據中,如下
{ _id: "joe", name: "Joe Bookreader", address: { street: "123 Fake Street", city: "Faketon", state: "MA", zip: "12345" } }
這樣,程序在一次查詢中就能獲取全部信息。
嵌入文檔的一對多關系模型
以下例子中,patron與address數據之間存在一對多關系,即patron有多個address實體。
在規范化數據模型中,address文檔包含了對patron文檔的引用,如下
{ _id: "joe", name: "Joe Bookreader" } { patron_id: "joe", street: "123 Fake Street", city: "Faketon", state: "MA", zip: "12345" } { patron_id: "joe", street: "1 Some Other Street", city: "Boston", state: "MA", zip: "12345" }
如果程序頻繁獲取address數據中的名稱信息,那么需要多次查詢以解析引用。一個較優的方案是嵌入address數據到patron數據中,如下
{ _id: "joe", name: "Joe Bookreader", addresses: [ { street: "123 Fake Street", city: "Faketon", state: "MA", zip: "12345" }, { street: "1 Some Other Street", city: "Boston", state: "MA", zip: "12345" } ] }
這樣通過一次查詢,程序就能獲取全部的patron信息。
文檔引用的一對多關系模型
考慮以下出版社與書之間關系映射。這個例子說明了引用比嵌入具有的優勢,避免了出版社信息的重復。
嵌入式數據模型如下
{ title: "MongoDB: The Definitive Guide", author: [ "Kristina Chodorow", "Mike Dirolf" ], published_date: ISODate("2010-09-24"), pages: 216, language: "English", publisher: { name: "O'Reilly Media", founded: 1980, location: "CA" } } { title: "50 Tips and Tricks for MongoDB Developer", author: "Kristina Chodorow", published_date: ISODate("2011-05-06"), pages: 68, language: "English", publisher: { name: "O'Reilly Media", founded: 1980, location: "CA" } }
可見,這種數據模型導致出版社信息的重復,使用引用數據模型使出版社信息在一個獨立的集合中則可以解決冗余問題。
當使用引用數據模型時,關系的增長決定將引用存儲到何處。如果每個出版社的書數量增長緩慢,那將書的引用存儲到出版社文檔中可能會是不錯的主意。否則如果每個出版社的書數量沒有界限,那這個數據模型會導致可變的,增長數組,如下所示,
{ name: "O'Reilly Media", founded: 1980, location: "CA", books: [12346789, 234567890, ...] // 隨着書的數量增長 } { _id: 123456789, title: "MongoDB: The Definitive Guide", author: [ "Kristina Chodorow", "Mike Dirolf" ], published_date: ISODate("2010-09-24"), pages: 216, language: "English" } { _id: 234567890, title: "50 Tips and Tricks for MongoDB Developer", author: "Kristina Chodorow", published_date: ISODate("2011-05-06"), pages: 68, language: "English" }
為了避免文檔中可變的增長數組,存儲出版社信息到書文檔中,如下
{ _id: "oreilly", name: "O'Reilly Media", founded: 1980, location: "CA" } { _id: 123456789, title: "MongoDB: The Definitive Guide", author: [ "Kristina Chodorow", "Mike Dirolf" ], published_date: ISODate("2010-09-24"), pages: 216, language: "English", publisher_id: "oreilly" } { _id: 234567890, title: "50 Tips and Tricks for MongoDB Developer", author: "Kristina Chodorow", published_date: ISODate("2011-05-06"), pages: 68, language: "English", publisher_id: "oreilly" }