MongoDB更需要好的模式設計 及 案例賞析

来源:https://www.cnblogs.com/xuliuzai/archive/2018/09/13/9637158.html
-Advertisement-
Play Games

一 挑戰 設計從來就是個挑戰。 當我們第一次接觸資料庫,學習資料庫基礎理論時,都需要學習範式,老師也一再強調範式是設計的基礎。範式是這門課程中的重要部分,在期末考試中也一定是個重要考點。如果我們當年大學掛科了,說不定就是範式這道題沒有做好。畢業後,當我們面試時,往往也有關於表設計方面拷問。 很多時候 ...


一  挑戰

設計從來就是個挑戰。

當我們第一次接觸資料庫,學習資料庫基礎理論時,都需要學習範式,老師也一再強調範式是設計的基礎。範式是這門課程中的重要部分,在期末考試中也一定是個重要考點。如果我們當年大學掛科了,說不定就是範式這道題沒有做好。畢業後,當我們面試時,往往也有關於表設計方面拷問。

很多時候,我們錯誤地認為,花費大量時間用在設計上,問題根源在於關係資料庫(RDBMS),在於二維表及其之間的聯繫組成的一個數據組織。而真實的環境中,我們正在大量使用noSQL或者NewSQL,按照目前的趨勢(DB-Engines Ranking 得分),將來還會越來越普遍。選用noSQL或者NewSQL 就不需要模式設計了。並且,隨著公司、行業數字化程度的加深,智能化觸角逐漸延伸,數據量越來越大,結構越來越複雜。 例如現在很火的IOT行業,複雜的業務信息、多樣的傳輸協議、不斷升級的感測器,都需要靈活的數據模型來應對。在這種呼喚聲中,MongoDB閃亮登場了。MongoDB支持靈活的數據模型。主要體現在以下2點:

(1)自由模式,無需提前聲明、創建表結構,即不用先創建表、添加欄位,然後才可以Insert數據。預設情況下MongoDB無需這樣操作,除非開啟了模式驗證。

(2)鍵值類型自由,MongoDB 將數據存儲為一個文檔,數據結構由鍵值(key=>value)對組成。欄位值可以包含其他文檔,數組及文檔數組。

MongoDB不需要模式設計時錯誤的,其實面對複雜的結構對象,模式的自由帶來更大的挑戰。

模式的自由是對數據insert這個動作而言,它去除很多限制了,可以快速講對象的存進來,並且易於擴展。但是不一定就會帶來好的查詢性能,好的查詢性能還要來自於好的模式設計、來自於好的集合文檔的設計。

 

二  模式設計

MongoDB可以將模式設計劃分為內嵌模式(Embedded)和 引用模式(References)

內嵌模式

簡單來講,內嵌模式就是將關聯數據,放在一個文檔中。例如以下員工信息採用內嵌模式了而存儲在了一個文檔中:

引用模式

引用模式是將數據存儲在不同集合的文檔中,而通過關係數據進行關聯。例如,這裡採用引用模式將員工信息存儲在了3個文檔中,基本信息一個文檔,聯繫方式一個文檔,登錄許可權放在了一個文檔中。每個文檔之前通過user_id來關聯。

 

 三  案例 

下麵我們通過一些業務場景,一些具體的案例,來分析、品味一下MongoDB模式設計的選擇。

 

案例 1

 

 假如現在我們描述來顧客(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

 

 一個顧客維護一個地址是理想的狀況,回頭看看我們淘寶賬號,就會發現收貨地址一般都是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的不同就是地址信息採用了數組類型,數組的欄位值又為內嵌子文檔。

 

案例3

 

 上面介紹的是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數據更新時,需要對所有的書籍文檔進行刷新。理所當然地,就會想到將出版社獨立出來,單獨設計一個文檔。(引用模式)。

 引用模式1

我們可以這樣設計:出版社單獨設計為一個集合文檔(文檔中引用書籍的編號),如下:

{

   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"

}

此設計中,將出版社出版的書的編號,保存在了出版社這個集合中。

但是這種設計還是有問題,例如,數組的更新、刪除相對比較困難。還有就是,每增加一個書籍集合的文檔,同時還要修改這個出版社結合的文檔。 所以,我們還可以將這種集合文檔設計優化如下。

引用模式2

此時出版社的文檔記錄如下:(不再應用書籍文檔的編號)

{

   _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"

}

 

 案例 4

 

上面三個例子,在關係型資料庫中都可以用我們學習過的關係(例如1:1;1:N)來描述,那麼我們再舉一個關係型資料庫難以描述的關係 -- 樹狀關係

例如,我們在電商網站上常見的商品分類關係,一級商品、二級商品、三級商品、四級商品關係。我們簡化此例子如下:

 

 那麼在MongoDB中可以輕鬆實現他們關係的查詢。

情景1  查詢節點的父節點(或稱為查詢上一級分類);或者查詢節點的子節點(或者為查詢下一級分類)

文檔的設計為:

 

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" } )

情景2  查詢祖先節點

其文檔設計為:

 

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的哲學思想。

總之,這是一個多看、多想、多思的蛻變羽化過程,可能時間很長、過程有些痛苦。

 

本文版權歸作者所有,未經作者同意不得轉載,謝謝配合!!!

本文版權歸作者所有,未經作者同意不得轉載,謝謝配合!!!

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 系統 環境 vue nginx 步驟 1、打包vue項目 2、配置nginx 打包vue項目 1、項目配置 我們使用伺服器的8000埠 2、打包 # npm run build 打包成功會創建dist目錄 3、上傳到伺服器 配置nginx 1、進入nginx 2、複製預設的配置文件default. ...
  • 一. 掛載存儲媒體 linux文件系統將所有的磁碟都併入一個虛擬目錄下,當使用新的存儲媒體之前,需要把它放到虛擬目錄下,這項工作稱為掛載(mounting) 1.1 mount 命令 在linux上用來掛載媒體的命令叫做mount. 預設情況下,mount命令會輸出當前系統上的掛載的設備列表。 1. ...
  • 系統 [root@Gao conf.d]# uname -a 工具 1、Final Shell 2、工具截圖 需要下載的部分 node.js npm cnpm vue-cli 安裝node.js 1、下載對應的版本 2、上傳到伺服器 3、解壓 # tar -xvf 4、重命名 # mv 5、進入bi ...
  • 實驗環境:Centos7虛擬機 首先創建一個普通用戶 。 [root@localhost ~] useradd gubeiqing [root@localhost ~] passwd gubeiqing Changing password for user gubeiqing. New passwo ...
  • Windows文件共用使用了SMB協議(又稱CIFS協議),該協議主要提供了文件共用和列印共用功能,分別使用TCP 139和445埠。UNIX、Linux系統提供了該協議的開源實現samba。為了方便開發和調試,在Windows宿主機和CentOS虛擬機之間通過SMB協議共用文件夾,在Window ...
  • 一、執行命令 二、根據提示輸入基本信息 三、生成的證書申請文件和私鑰文件(.key文件千萬別丟非常重要!!!美文) 四、複製csr內容粘貼到name.com的申請框中(註意包括BEGIN和END兩行) ...
  • yum install java-1.8.0-openjdk* -y ...
  • 對於每一個 Linux 學習者來說,瞭解 Linux 的文件系統結構是十分有必要的,因為在 Linux 的學習中一直流傳著這樣一句話:一切皆文件,可以說只有深入瞭解了 Linux 的文件系統,才會對 Linux 有更深刻的理解 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...