設計歷來就是個挑戰。面試
當咱們第一次接觸數據庫,學習數據庫基礎理論時,都須要學習範式,老師也一再強調範式是設計的基礎。範式是這門課程中的重要部分,在期末考試中也必定是個重要考點。若是咱們當年大學掛科了,說不定就是範式這道題沒有作好。畢業後,當咱們面試時,每每也有關於表設計方面拷問。數據庫
不少時候,咱們錯誤地認爲,花費大量時間用在設計上,問題根源在於關係數據庫(RDBMS),在於二維表及其之間的聯繫組成的一個數據組織。而真實的環境中,咱們正在大量使用noSQL或者NewSQL,按照目前的趨勢(DB-Engines Ranking 得分),未來還會愈來愈廣泛。選用noSQL或者NewSQL 就不須要模式設計了。而且,隨着公司、行業數字化程度的加深,智能化觸角逐漸延伸,數據量愈來愈大,結構愈來愈複雜。 例如如今很火的IOT行業,複雜的業務信息、多樣的傳輸協議、不斷升級的傳感器,都須要靈活的數據模型來應對。在這種呼喚聲中,MongoDB閃亮登場了。MongoDB支持靈活的數據模型。主要體如今如下2點:設計模式
(1)自由模式,無需提早聲明、建立表結構,即不用先建立表、添加字段,而後才能夠Insert數據。默認狀況下MongoDB無需這樣操做,除非開啓了模式驗證。數組
(2)鍵值類型自由,MongoDB 將數據存儲爲一個文檔,數據結構由鍵值(key=>value)對組成。字段值能夠包含其餘文檔,數組及文檔數組。數據結構
MongoDB不須要模式設計時錯誤的,其實面對複雜的結構對象,模式的自由帶來更大的挑戰。ide
模式的自由是對數據insert這個動做而言,它去除不少限制了,能夠快速講對象的存進來,而且易於擴展。可是不必定就會帶來好的查詢性能,好的查詢性能還要來自於好的模式設計、來自於好的集合文檔的設計。性能
MongoDB能夠將模式設計劃分爲內嵌模式(Embedded)和 引用模式(References)學習
簡單來說,內嵌模式就是將關聯數據,放在一個文檔中。例如如下員工信息採用內嵌模式了而存儲在了一個文檔中:優化
引用模式是將數據存儲在不一樣集合的文檔中,而經過關係數據進行關聯。例如,這裏採用引用模式將員工信息存儲在了3個文檔中,基本信息一個文檔,聯繫方式一個文檔,登陸權限放在了一個文檔中。每一個文檔以前經過user_id來關聯。網站
下面咱們經過一些業務場景,一些具體的案例,來分析、品味一下MongoDB模式設計的選擇。
假如如今咱們描述來顧客(patron)和顧客的地址(address),其ER圖以下:
咱們能夠將patron和address設計成兩個集合(collection,相似於RDBMS數據庫中的table),其具體信息以下:
patron 集合
{
_id: "joe",
name: "Joe Bookreader"
}
address 集合
{
patron_id: "joe",
street: "123 Fake Street",
city: "Faketon",
state: "MA",
zip: "12345"
}
在設計address 集合時,內嵌了patron集合的_id字段,經過這個字段進行關聯。
但這種實體關係爲1:1,強關聯的關係
推薦設計成以下模式:
{
_id: "joe",
name: "Joe Bookreader",
address: {
street: "123 Fake Street",
city: "Faketon",
state: "MA",
zip: "12345"
}
}
即便用內嵌模式,將數據存儲在一個集合中。
一個顧客維護一個地址是理想的情況,回頭看看咱們淘寶帳號,就會發現收貨地址通常都是2個以上 ( 流淚 ╥╯^╰╥)
patron 集合顧客joe的文檔記錄
{
_id: "joe",
name: "Joe Bookreader"
}
address 集合joe顧客的地址1的文檔記錄
{
patron_id: "joe",
street: "123 Fake Street",
city: "Faketon",
state: "MA",
zip: "12345"
}
address 集合中joe顧客的地址2的文檔記錄
{
patron_id: "joe",
street: "1 Some Other Street",
city: "Boston",
state: "MA",
zip: "12345"
}
像這種1:N的關係,而且N能夠預見不是不少的狀況下,咱們推薦採用內嵌模式,
將集合文檔設計成以下模式:
{
_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"
}
]
}
與案例1的不一樣就是地址信息採用了數組類型,數組的字段值又爲內嵌子文檔。
上面介紹的是1對多的關係(1:N),可是N值不是很大。可是現實世界中,有時候會遇到N值比較大的狀況。
好比 出版社和書籍的關係,一個出版社可能已將出版了成千上萬本書籍了。
其設計模式能夠以下(內嵌模式),將出版社的信息做爲一個子文檔,來內嵌到書籍的文檔中,具體信息以下:
如下書籍《MongoDB: The Definitive Guide》的文檔信息:
{
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"
}
}
如下書籍《50 Tips and Tricks for MongoDB Developer》的文檔信息:
{
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"
}
}
從中能夠看出,publisher信息描述比較多,而且都相同,每一個文檔中都存放,浪費太多的存儲空間,顯得無用臃腫,還有個明顯的缺點就是 當publisher數據更新時,須要對全部的書籍文檔進行刷新。理所固然地,就會想到將出版社獨立出來,單獨設計一個文檔。(引用模式)。
咱們能夠這樣設計:出版社單獨設計爲一個集合文檔(文檔中引用書籍的編號),以下:
{
name: "O'Reilly Media",
founded: 1980,
location: "CA",
books: [123456789, 234567890, ...]
}
書籍集合中編號爲123456789的書籍的文檔:
{
_id: 123456789,
title: "MongoDB: The Definitive Guide",
author: [ "Kristina Chodorow", "Mike Dirolf" ],
published_date: ISODate("2010-09-24"),
pages: 216,
language: "English"
}
書籍集合中編號爲234567890的書籍的文檔:
{
_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"
}
此時書籍的文檔記錄以下:(書籍爲123456789,文檔引用了出版社的_ID)
{
_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"
}
此時書籍的文檔記錄以下:(書籍爲234567890,文檔引用了出版社的_ID)
{
_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"
}
上面三個例子,在關係型數據庫中均可以用咱們學習過的關係(例如1:1;1:N)來描述,那麼咱們再舉一個關係型數據庫難以描述的關係 -- 樹狀關係。
例如,咱們在電商網站上常見的商品分類關係,一級商品、二級商品、三級商品、四級商品關係。咱們簡化此例子以下:
那麼在MongoDB中能夠輕鬆實現他們關係的查詢。
文檔的設計爲:
db.categories.insert( { _id: "MongoDB", parent: "Databases" } )
db.categories.insert( { _id: "dbm", parent: "Databases" } )
db.categories.insert( { _id: "Databases", parent: "Programming" } )
db.categories.insert( { _id: "Languages", parent: "Programming" } )
db.categories.insert( { _id: "Programming", parent: "Books" } )
db.categories.insert( { _id: "Books", parent: null } )
查詢節點的父節點(或稱爲查詢上一級分類)的語句,例如查詢MongoDB所屬分類:
db.categories.findOne( { _id: "MongoDB" } ).parent
查詢節點的子節點(或者爲查詢下一級分類),例如查詢Database的直連的子節點(不是孫子節點)。
db.categories.find( { parent: "Databases" } )
上面的文檔能夠查詢出子文檔,可是會顯示出多個文檔,例如上面的查詢語句,會返回出MongoDB 文檔和 dbm文檔 ,咱們還須要還特殊處理,那麼可不能夠在一個文檔中顯示出因此的子節點呢?
能夠的。文檔模式設計以下:
db.categories.insert( { _id: "MongoDB", children: [] } )
db.categories.insert( { _id: "dbm", children: [] } )
db.categories.insert( { _id: "Databases", children: [ "MongoDB", "dbm" ] } )
db.categories.insert( { _id: "Languages", children: [] } )
db.categories.insert( { _id: "Programming", children: [ "Databases", "Languages" ] } )
db.categories.insert( { _id: "Books", children: [ "Programming" ] } )
若是這時候查詢Databases的子節點,就會是一個文檔了。查詢驗證語句以下:
db.categories.findOne( { _id: "Databases" } ).children
此模式也支持查詢節點的父節點。例如查詢MongoDB這個節點的父節點:
db.categories.find( { children: "MongoDB" } )
其文檔設計爲:
db.categories.insert( { _id: "MongoDB", ancestors: [ "Books", "Programming", "Databases" ], parent: "Databases" } )
db.categories.insert( { _id: "dbm", ancestors: [ "Books", "Programming", "Databases" ], parent: "Databases" } )
db.categories.insert( { _id: "Databases", ancestors: [ "Books", "Programming" ], parent: "Programming" } )
db.categories.insert( { _id: "Languages", ancestors: [ "Books", "Programming" ], parent: "Programming" } )
db.categories.insert( { _id: "Programming", ancestors: [ "Books" ], parent: "Books" } )
db.categories.insert( { _id: "Books", ancestors: [ ], parent: null } )
例如查詢MongoDB節點的祖先節點:
db.categories.findOne( { _id: "MongoDB" } ).ancestors
固然也能夠查詢 後代節點:
db.categories.find( { ancestors: "Programming" } )
MongoDB的模式設計是一個比較大的課題,須要多看看情景案例,多品味一些優秀的文檔設計,多問些問什麼要這樣作,是否有更優的設計,要慢慢去領悟MongoDB的哲學思想。
總之,這是一個多看、多想、多思的蛻變羽化過程,可能時間很長、過程有些痛苦。
本文版權歸做者全部,未經做者贊成不得轉載,謝謝配合!!!
本文版權歸做者全部,未經做者贊成不得轉載,謝謝配合!!!