MongoDB開發(fā)規(guī)范與數(shù)據(jù)建模詳解
MongoDB開發(fā)規(guī)范
1.命名原則
- 數(shù)據(jù)庫名使用小寫字符,集合名稱使用統(tǒng)一命名風格。可以統(tǒng)一大小寫或者駝峰命名。數(shù)據(jù)庫名和集合名均不能超過64個字符
2.集合設計
- 對于少量數(shù)據(jù)的包含關系,使用嵌套模式有利于讀寫性能和保證原子性的寫入。對于復雜的關系,以及后期可能發(fā)生演進變化的情況,建議使用引用模式
3.文檔設計
- 避免使用大文檔,MongoDB的文檔最大不能超過64MB。
- 如果使用了內嵌子文檔或數(shù)組,應該保證內嵌數(shù)據(jù)不能無限增長。在文檔結構上,盡可能減少字段名的長度,MongoDB會保存文檔中的字段名,因此整個字段名的長度會影響整個集合的大小和內存的需求。一般建議將字段名控制住32字符以內
4.索引設計
- 在必要時使用索引加速查詢。避免建立過多的索引,單個集合不建議超過10個索引。mongodb對數(shù)據(jù)的寫入很可能會觸發(fā)索引的寫入,從而觸發(fā)更多的I/O操作。無效的索引會操作內存空間的浪費,需及時清理不需要的索引。
- 遵循索引優(yōu)化原則,如覆蓋索引、優(yōu)先前綴匹配等,使用explain()命令分析索引性能
5.分片設計
- 對可能出現(xiàn)快速增長或讀寫壓力較大的業(yè)務表考慮分片。分片建的設計滿足均衡分布的目標,業(yè)務上盡量避免廣播查詢。應盡早決定分片策略,建議在集合達到256GB之前進行分片,如果集合中存在唯一索引,則應該確保該索引覆蓋分片建,避免沖突。為了降低風險,單個分片集數(shù)據(jù)量不建議超過2TB
6.升級設計
- 應用上需支持對舊版本數(shù)據(jù)的兼容性,在添加唯一性索引約束之前,對數(shù)據(jù)表進行檢查并及時清理冗余的數(shù)據(jù)。新增/修改數(shù)據(jù)庫對象需經(jīng)過評審,并保持對數(shù)據(jù)字典進行更新
7.考慮數(shù)據(jù)老化問題
- 要及時清理無效、過期的數(shù)據(jù)。優(yōu)先考慮為系統(tǒng)日志、歷史數(shù)據(jù)表添加合理的數(shù)據(jù)老化策略
8.數(shù)據(jù)一致性方面
- 非關鍵業(yè)務使用writeConcern: 1 。 對于關鍵業(yè)務類使用writeConcern: majority 。如果業(yè)務上嚴格不允許臟讀,則使用ReadConcern:majority
9.使用update、findAndUpdate對數(shù)據(jù)進行更新時,如果使用過了upset: true,則必須使用唯一性索引避免產生重復數(shù)據(jù)
10.業(yè)務上盡量避免短連接,使用官方最新驅動的連接池實現(xiàn),控制客戶端連接的數(shù)量,最大不建議超過200
11.對大量數(shù)據(jù)寫入使用Bulk Write批量化API,建議使用無序批次更新
12.優(yōu)先使用單文檔事務保證原子性,如果需要使用多文檔事務,則必須保證事務盡可能小,一個事務的執(zhí)行時間最長不超過60s
13.在條件允許的情況下,使用讀寫分離降低primary節(jié)點的壓力。對于一些統(tǒng)計分析類的查詢可優(yōu)先從節(jié)點上讀取
14.考慮業(yè)務數(shù)據(jù)的隔離,例如將配置項數(shù)據(jù)、歷史數(shù)據(jù)存放在不同的數(shù)據(jù)庫中,微服務之間使用單獨的數(shù)據(jù)庫,盡量避免垮庫訪問
15.維護數(shù)據(jù)字典文檔,并保持更新,提前按不同的業(yè)務進行數(shù)據(jù)容量規(guī)劃
MongoDB數(shù)據(jù)建模
嵌入式文檔 一對一關系模型
嵌入式文檔模型
以下映射客戶和地址關系的示例。對于這種數(shù)據(jù)量較小的文檔使用嵌入式文檔更好
// patron document { _id: "joe", name: "Joe Bookreader" } // address document { patron_id: "joe", // reference to patron document street: "123 Fake Street", city: "Faketon", state: "MA", zip: "12345" }
如果經(jīng)常將address
數(shù)據(jù)與name
信息一起檢索,更好的Realm 數(shù)據(jù)模型是將address
數(shù)據(jù)嵌入到patron
數(shù)據(jù)中,如以下文檔所示:
{ _id: "joe", name: "Joe Bookreader", address: { street: "123 Fake Street", city: "Faketon", state: "MA", zip: "12345" } }
子集模式
嵌入式文檔模型的一個潛在問題是,它可能會導致大型文檔包含應用程序不需要的字段。 這些不必要的數(shù)據(jù)可能會給服務器造成額外負載,并減慢讀取操作的速度。相反,可以使用子集模式來檢索在單個數(shù)據(jù)庫調用中訪問最頻繁的數(shù)據(jù)子集。
考慮一個顯示電影信息的應用程序。 movie
數(shù)據(jù)庫包含具有以下模式的collection集合:
{ "_id": 1, "title": "The Arrival of a Train", "year": 1896, "runtime": 1, "released": ISODate("01-25-1896"), "poster": "http://ia.media-imdb.com/images/M/MV5BMjEyNDk5MDYzOV5BMl5BanBnXkFtZTgwNjIxMTEwMzE@._V1_SX300.jpg", "plot": "A group of people are standing in a straight line along the platform of a railway station, waiting for a train, which is seen coming at some distance. When the train stops at the platform, ...", "fullplot": "A group of people are standing in a straight line along the platform of a railway station, waiting for a train, which is seen coming at some distance. When the train stops at the platform, the line dissolves. The doors of the railway-cars open, and people on the platform help passengers to get off.", "lastupdated": ISODate("2015-08-15T10:06:53"), "type": "movie", "directors": [ "Auguste Lumière", "Louis Lumière" ], "imdb": { "rating": 7.3, "votes": 5043, "id": 12 }, "countries": [ "France" ], "genres": [ "Documentary", "Short" ], "tomatoes": { "viewer": { "rating": 3.7, "numReviews": 59 }, "lastUpdated": ISODate("2020-01-09T00:02:53") } }
如果應用程序顯示電影簡單概述時不需要的多個字段,我們就可以將該collection分割為兩個collection,而不是將所有電影數(shù)據(jù)存儲在單個collection中:
電影的基本信息。應用程序默認加載的數(shù)據(jù)如下:
// movie collection { "_id": 1, "title": "The Arrival of a Train", "year": 1896, "runtime": 1, "released": ISODate("1896-01-25"), "type": "movie", "directors": [ "Auguste Lumière", "Louis Lumière" ], "countries": [ "France" ], "genres": [ "Documentary", "Short" ], }
每部電影的其他不常訪問的數(shù)據(jù):
// movie_details collection { "_id": 156, "movie_id": 1, // 通過這個字段進行關聯(lián) "poster": "http://ia.media-imdb.com/images/M/MV5BMjEyNDk5MDYzOV5BMl5BanBnXkFtZTgwNjIxMTEwMzE@._V1_SX300.jpg", "plot": "A group of people are standing in a straight line along the platform of a railway station, waiting for a train, which is seen coming at some distance. When the train stops at the platform, ...", "fullplot": "A group of people are standing in a straight line along the platform of a railway station, waiting for a train, which is seen coming at some distance. When the train stops at the platform, the line dissolves. The doors of the railway-cars open, and people on the platform help passengers to get off.", "lastupdated": ISODate("2015-08-15T10:06:53"), "imdb": { "rating": 7.3, "votes": 5043, "id": 12 }, "tomatoes": { "viewer": { "rating": 3.7, "numReviews": 59 }, "lastUpdated": ISODate("2020-01-29T00:02:53") } }
嵌入式文檔 一對多關系模型
嵌入式文檔模型
客戶和多個地址關系的示例如下
// patron document { _id: "joe", name: "Joe Bookreader" } // address documents { patron_id: "joe", // reference to patron document street: "123 Fake Street", city: "Faketon", state: "MA", zip: "12345" } { patron_id: "joe", street: "1 Some Other Street", city: "Boston", state: "MA", zip: "12345" }
如果經(jīng)常檢索帶有name
信息的address
數(shù)據(jù),那么就需要發(fā)出多個查詢來解析引用。 更優(yōu)化的模式是將address
數(shù)據(jù)實體嵌入到patron
數(shù)據(jù)中,如以下文檔所示:
{ "_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" } ] }
借助嵌入式數(shù)據(jù)模型,應用程序可以通過一次查詢檢索完整的客戶信息。
子集模式
嵌入式文檔模式的一個潛在問題是,它可能導致文檔過大,尤其是在嵌入式字段沒有限制的情況下。在這種情況下,您可以使用子集模式僅訪問應用程序所需的數(shù)據(jù),而不是訪問整個嵌入數(shù)據(jù)集
例如,產品評論列表的電商站點,reviews字段中保存著所有的評論數(shù)據(jù):
{ "_id": 1, "name": "Super Widget", "description": "This is the most useful item in your toolbox.", "price": { "value": NumberDecimal("119.99"), "currency": "USD" }, "reviews": [ { "review_id": 786, "review_author": "Kristina", "review_text": "This is indeed an amazing widget.", "published_date": ISODate("2019-02-18") }, { "review_id": 785, "review_author": "Trina", "review_text": "Nice product. Slow shipping.", "published_date": ISODate("2019-02-17") }, ... { "review_id": 1, "review_author": "Hans", "review_text": "Meh, it's okay.", "published_date": ISODate("2017-12-06") } ] }
評論按時間倒序排列。用戶訪問產品頁面時,應用程序會加載最近十條評論。
您可以將該集合拆分為兩個集合,而不存儲該產品的所有評論:
product
collection 存儲每個產品的信息,包括該產品的 10 條最新評論:
{ "_id": 1, "name": "Super Widget", "description": "This is the most useful item in your toolbox.", "price": { "value": NumberDecimal("119.99"), "currency": "USD" }, "reviews": [ { "review_id": 786, "review_author": "Kristina", "review_text": "This is indeed an amazing widget.", "published_date": ISODate("2019-02-18") } ... { "review_id": 777, "review_author": "Pablo", "review_text": "Amazing!", "published_date": ISODate("2019-02-16") } ] }
review
collection 存儲所有評論。每條評論都包含對相應產品的引用。
{ "review_id": 786, "product_id": 1, // 通過該字段進行關聯(lián) "review_author": "Kristina", "review_text": "This is indeed an amazing widget.", "published_date": ISODate("2019-02-18") } { "review_id": 785, "product_id": 1, "review_author": "Trina", "review_text": "Nice product. Slow shipping.", "published_date": ISODate("2019-02-17") } ... { "review_id": 1, "product_id": 1, "review_author": "Hans", "review_text": "Meh, it's okay.", "published_date": ISODate("2017-12-06") }
文檔引用 一對多關系模型
以下示例展示如何映射出版商和圖書關系。該示例說明在避免出版商信息冗余方面,引用比嵌入更有優(yōu)勢。
將出版商文檔嵌入圖書文檔會導致出版商數(shù)據(jù)重復,如以下文檔所示:
// 各個文檔中都保存publisher 出版商信息,造成了出版商信息冗余 { 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" } }
使用引用并將出版商信息保存在圖書集合之外的單獨集合中。
使用引用時,關系的增長將決定引用的存儲方式。
如果每個出版商的圖書數(shù)量較少且增長有限,則將圖書引用存儲在出版商文檔中有時可能十分有用。相反,當每個出版商的圖書數(shù)量沒有限制時,此數(shù)據(jù)模型將導致可變且不斷增長的數(shù)組,如以下示例所示:
// 出版商信息 // 如果出版商的圖書數(shù)量沒有限制時,那么下面的books數(shù)組將會非常大 { name: "O'Reilly Media", founded: 1980, location: "CA", books: [123456789, 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" }
為避免出現(xiàn)可變且不斷增長的數(shù)組,請將出版商的引用存儲在圖書文檔中:
// 出版商信息 { _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" // 將出版商的引用存儲在圖書文檔中 }
物聯(lián)網(wǎng)時序數(shù)據(jù)建模
需求
美國州際公路的流量統(tǒng)計。數(shù)據(jù)庫需要提供的能力:
存儲事件數(shù)據(jù)
提供分析查詢能力
理想的平衡點:
- 內存使用
- 寫入性能
- 讀取分析性能
可以部署在常見的硬件平臺上
每個事件用一個獨立的文檔存儲
{ segId: "I80_mile23", speed: 63, ts: ISODate("2013-10-16T22:07:38.000-0500") }
- 非常“傳統(tǒng)”的設計思路,每個事件都會寫入一條同樣的信息。多少的信息,就有多少條數(shù)據(jù),數(shù)據(jù)量增長非???。
- 數(shù)據(jù)采集操作全部是Insert語句;
每分鐘的信息用一個獨立的文檔存儲(存儲平均值)
{ segId: "I80_mile23", speed_num: 18, speed_sum: 1134, ts: ISODate("2013-10-16T22:07:00.000-0500") }
- 對每分鐘的平均速度計算非常友好(speed_sum/speed_num);
- 數(shù)據(jù)采集操作基本是Update語句;
- 數(shù)據(jù)精度降為一分鐘;
每分鐘的信息用一個獨立的文檔存儲(秒級記錄)
{ segId: "I80_mile23", speed: {0:63, 1:58, ... , 58:66, 59:64}, ts: ISODate("2013-10-16T22:07:00.000-0500") }
- 每秒的數(shù)據(jù)都存儲在一個文檔中;
- 數(shù)據(jù)采集操作基本是Update語句;
每小時的信息用一個獨立的文檔存儲(秒級記錄)
{ segId: "I80_mile23", speed: {0:63, 1:58, ... , 3598:54, 3599:55}, ts: ISODate("2013-10-16T22:00:00.000-0500") }
相比上面的方案更進一步,從分鐘到小時:
- 每小時的數(shù)據(jù)都存儲在一個文檔中;
- 數(shù)據(jù)采集操作基本是Update語句;
- 更新最后一個時間點(第3599秒),需要3599次迭代(雖然是在同一個文檔中)
進一步優(yōu)化
{ segId: "I80_mile23", speed: { 0: {0:47, ..., 59:45}, ..., 59: {0:65, ... , 59:56} } ts: ISODate("2013-10-16T22:00:00.000-0500") }
- 用了嵌套的手法把秒級別的數(shù)據(jù)存儲在小時數(shù)據(jù)里;
- 數(shù)據(jù)采集操作基本是Update語句;
- 更新最后一個時間點(第3599秒),需要59+59次迭代;
嵌套結構正是MongoDB的魅力所在,稍動腦筋把一維拆成二維,大幅度減少了迭代次數(shù);
每個事件用一個獨立的文檔存儲VS每分鐘的信息用一個獨立的文檔存儲
從寫入上看:后者每次修改的數(shù)據(jù)量要小很多,并且在WiredTiger引擎下,同一個文檔的修改一定時間窗口下是可以在內存中合并的;
從讀取上看:查詢一個小時的數(shù)據(jù),前者需要返回3600個文檔,而后者只需要返回60個文檔,效率上的差異顯而易見;
從索引上看:同樣,因為穩(wěn)定數(shù)量的大幅度減少,索引尺寸也是同比例降低的,并且segId,ts這樣的冗余數(shù)據(jù)也會減少冗余。容量的降低意味著內存命中率的上升,也就是性能的提高;
每小時的信息用一個獨立的文檔存儲VS每分鐘的信息用一個獨立的文檔存儲
從寫入上看:因為WiredTiger是每分鐘進行一次刷盤,所以每小時一個文檔的方案,在這一個小時內要被反復的load到PageCache中,再刷盤;所以,綜合來看后者相對更合理;
從讀取上看:前者的數(shù)據(jù)信息量較大,正常的業(yè)務請求未必需要這么多的數(shù)據(jù),有很大一部分是浪費的;
從索引上看:前者的索引更小,內存利用率更高;
總結
那么到底選擇哪個方案更合理呢?從理論分析上可以看出,不管是小時存儲,還是分鐘存儲,都是利用了MongoDB的信息聚合的能力。
- 每小時的信息用一個獨立的文檔存儲:設計上較極端,優(yōu)勢劣勢都很明顯;
- 每分鐘的信息用一個獨立的文檔存儲:設計上較平衡,不會與業(yè)務期望偏差較大;
落實到現(xiàn)實的業(yè)務上,哪種是最優(yōu)的?最好的解決方案就是根據(jù)自己的業(yè)務情況進行性能測試,以上的分析只是“理論”基礎,給出“實踐”的方向,但千萬不可以此論斷。
到此這篇關于MongoDB開發(fā)規(guī)范與數(shù)據(jù)建模的文章就介紹到這了,更多相關MongoDB數(shù)據(jù)建模內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
如何對 MongoDB 進行性能優(yōu)化(五個簡單步驟)
MongoDB一直是最流行的NoSQL,而根據(jù)DB-Engines Ranking最新的排行,時下MongoDB已經(jīng)擊敗PostgreSQL躍居數(shù)據(jù)庫總排行的第四位,僅次于Oracle、MySQL和Microsoft SQL Server。本文給大家介紹MongoDB性能優(yōu)化的簡單總結。2015-10-10window平臺安裝MongoDB數(shù)據(jù)庫圖文詳解
本篇文章主要介紹了window平臺安裝MongoDB數(shù)據(jù)庫圖文詳解,主要介紹window下面安裝mogod的步驟和使用細節(jié)。感興趣的小伙伴們可以參考一下。2016-11-11Mac下安裝配置mongodb并創(chuàng)建用戶的方法
最近在在學習nodejs,相比mysql,mongodb與nodejs搭配更合適,存儲數(shù)據(jù)格式也比較接近JS對象。下面這篇文章主要給大家介紹了關于在Mac下安裝配置mongodb并創(chuàng)建用戶的相關資料,需要的朋友可以參考下2018-05-05