MongoDB學習5:模型設計和設計模式
阿新 • • 發佈:2020-08-12
1.資料模型
1.1 什麼是資料模型?
資料模型是一組由符號、文字組成的集合,用以準確表達資訊,達到有效交流、溝通的目的
1.2 資料模型設計元素
- 實體 Entity
- 描述業務的主要資料集合
- 屬性 Attribute
- 描述實體裡面的單個屬性
- 關係 Relationship
- 描述實體與實體之間的資料規則
- 結構規則:1-N N-1 N-N
- 引用規則:比如電話號碼不能單獨存在,必須依賴於具體的人
2.JSON文件模型設計特點
2.1 MongoDB文件模型設計的三個誤區
- 不需要模型設計
- MongoDB應該用一個超級大文件來組織所有資料
- MongoDB不支援關聯或者事務
2.2 為什麼說MongoDB是無模式的
- 嚴格來說,MongoDB同樣需要概念/邏輯建模
- 文件模型設計的物理層結構可以和邏輯層類似
- 可以省略物理建模的具體過程
2.3 文件設計模式原則:效能和易用
2.4 關係模型 vs 文件模型
關係資料庫 | JSON文件模型 | |
---|---|---|
模型設計層次 | 概念模型、邏輯模型、物理模型 | 概念模型、邏輯模型 |
模型實體 | 表 | 集合 |
模型屬性 | 列 | 欄位 |
模型關係 | 關聯關係,主外來鍵 | 內嵌陣列、引用欄位 |
3.MongoDB文件模型設計三部曲
3.1 建立基礎文件模型
-
根據概念模型或者業務需求推匯出邏輯模型 -找到物件
-
列出實體之間的關係 -明確關係
-
套用邏輯設計原則來決定內嵌方式 -進行建模
-
完成基礎模型構建
-
一個聯絡人管理應用的例子
- 1.找到物件
-- Contacts
-- Group
-- Address
-- Portraits - 2.明確關係
-- 一個聯絡人有1個頭像 (1-1)
-- 一個聯絡人可以有多個地址 (1-N)
-- 一個聯絡人可以屬於多個組,一個組可以有多個聯絡人(N-N) - 3.關係建模(1-1):Portraits
-- 基本原則:一對一關係以內嵌為主,作為子文件形式或者直接在頂級,不涉及到資料冗餘
-- 例外情況:如果內嵌後導致文件大小超過16MB
- 4.關係建模(1-N):Address
-- 基本原則:一對多關係同樣以內嵌為主,用陣列來表示一對多,不涉及到資料冗餘
-- 例外情況:內嵌後導致文件大小超過16MB、陣列長度太大(數萬或更多)、陣列長度不確定
- 5.關係建模(N-N):Groups
-- 基本原則:不需要對映表,一般用內嵌陣列來表示一對多,通過冗餘來實現N-N
-- 例外情況:內嵌後導致文件大小超過16MB、陣列長度太大(數萬或更多)、陣列長度不確定
- 1.找到物件
3.2 根據讀寫工況細化
-
聯絡管理應用的分組需求
Q : 假如有千萬級聯絡人;需要頻繁變動分組的資訊,比如增加分組及修改名稱及描述以及營銷狀態;一個分組有百萬級聯絡人,如何解決?
A : Group使用單獨的集合
-
什麼時候應該使用引用方式?
- 內嵌文件太大,數MB或者超過16MB
- 內嵌文件或陣列元素會頻繁修改
- 內嵌陣列元素會持續增長並且沒有封頂
-
MongoDB引用設計的限制
- MongoDB對使用引用的集合無主外來鍵檢查(需要程式自行判斷)
- MongoDB使用聚合框架的 $lookup 來模仿關聯查詢
- $lookup 只支援 left outer join
- $lookup 的關聯目標(from)不能是分片表
db.contacts.aggregate([
{
$lookup:{
from:"groups", #外聯表
localField:"group_ids", #外來鍵欄位
foreignField:"group_id", #外聯表主鍵
as:"groups" #查詢結果
}
}
])
3.3 套用設計模型
- 物聯網場景下的海量資料處理 - 飛機監控資料
-- 要求記錄飛機的實時位置,假設有10萬架飛機、1年的資料、每分鐘一條
-- 如果每架飛機每分鐘都往資料庫寫入一條資料,那麼資料量將非常龐大,如何解決呢?可以使用分桶設計解決:一個文件儲存一架飛機一小時的資料
每分鐘1個文件 | 每小時1個文件 | |
---|---|---|
文件條數 | 52.6B | 876M |
索引大小(_id index \ {ts:1,deviceId:1}) | 6364GB(1468GB \ 4895GB) | 106GB(24.5GB \ 81.6GB) |
文件平均大小 | 92 Bytes | 758 Bytes |
資料大小 | 4503GB | 618GB |
-
大文件,很多欄位,很多索引
-- 比如一個表中存在各種各樣的名字(chineseName,englishName,franchName...),而且每個名字會頻繁的查詢。可以使用列轉行,將多個相同的列轉化為一個數組(names:{ chinese:'',english:'',franch:'' }
) -
模型靈活了,如何管理文件的不同版本?
-- 增加一個版本欄位 -
統計網頁流量點選
-- 如果每次點選頁面都產生一個計數更新操作,那麼資料庫將大量由此操作佔據了
-- 這種統計數字準確性並不十分重要,可以使用近似計算優化:每10次操作計數一次寫入庫數值10
if random(0,9) == 0
increment by 10
- 業績排名、遊戲排名、商品銷售統計等精確統計
-- 如某個商品今天賣了多少、本週賣了多少、本月賣了多少
-- 傳統解決方案是通過聚合計算,但是消耗資源多,聚合計算時間長;使用聚合欄位
{
product:"洗衣服",
sku:"10000",
price:23.99,
stock:9999,
daily_sales:10,
weekly_sales:100,
monthly_sales:700
}
db.products.update({_id:123,{
$inc:{
stock:-1,
daily_sales:1,
weekly_sales:1,
monthly_sales:1
}
}})
- 模式小結
模式 | 場景 | 痛點 | 設計模式的方案及優點 |
---|---|---|---|
分桶 | 時序資料(物聯網、智慧城市、智慧交通) | 資料點採集頻繁,資料量太多 | 利用文件內嵌陣列,將一個時間段的資料聚合到一個文件裡 大量減少文件數量 大量減少索引佔用空間 |
列轉行 | 產品屬性(color、size...) 多語言(多國家)屬性 |
文件中有很多類似的欄位 會用於組合查詢搜尋,需要建立很多索引 |
轉化為陣列,一個索引解決所有查詢問題 |
版本欄位 | 任何有版本衍變的資料庫 | 文件模型格式多,無法知道其合理性 升級時需要更新太多文件 |
增加一個版本號欄位 快速過濾掉不需要升級的文件 升級時對不同版本的文件做不同的處理 |
近似計算 | 網頁計數,各種結果不需要準確的排名 | 寫入太頻繁,消耗系統資源 | 間隔寫入,每隔10次或100次寫入 大量減少寫入操作 |
預聚合 | 準確排名、排行榜 | 統計計算耗時,計算時間長 | 模型中直接增加統計欄位 每次更新資料的同時更新統計值 |