萬字詳解,吃透 MongoDB!
本文已經收錄進 JavaGuide(「Java學習+面試指南」一份涵蓋大部分 Java 程序員所需要掌握的核心知識。)
少部分內容參考了 MongoDB 官方文檔的描述,在此説明一下。
MongoDB 基礎
MongoDB 是什麼?
MongoDB 是一個基於 分佈式文件存儲 的開源 NoSQL 數據庫系統,由 C++ 編寫的。MongoDB 提供了 面向文檔 的存儲方式,操作起來比較簡單和容易,支持“無模式”的數據建模,可以存儲比較複雜的數據類型,是一款非常流行的 文檔類型數據庫 。
在高負載的情況下,MongoDB 天然支持水平擴展和高可用,可以很方便地添加更多的節點/實例,以保證服務性能和可用性。在許多場景下,MongoDB 可以用於代替傳統的關係型數據庫或鍵/值存儲方式,皆在為 Web 應用提供可擴展的高可用高性能數據存儲解決方案。
MongoDB 的存儲結構是什麼?
MongoDB 的存儲結構區別於傳統的關係型數據庫,主要由如下三個單元組成:
- 文檔(Document) :MongoDB 中最基本的單元,由 BSON 鍵值對(key-value)組成,類似於關係型數據庫中的行(Row)。
- 集合(Collection) :一個集合可以包含多個文檔,類似於關係型數據庫中的表(Table)。
- 數據庫(Database) :一個數據庫中可以包含多個集合,可以在 MongoDB 中創建多個數據庫,類似於關係型數據庫中的數據庫(Database)。
也就是説,MongoDB 將數據記錄存儲為文檔 (更具體來説是BSON 文檔),這些文檔在集合中聚集在一起,數據庫中存儲一個或多個文檔集合。
SQL 與 MongoDB 常見術語對比 :
| SQL | MongoDB | | ----------------------- | ------------------------------ | | 表(Table) | 集合(Collection) | | 行(Row) | 文檔(Document) | | 列(Col) | 字段(Field) | | 主鍵(Primary Key) | 對象 ID(Objectid) | | 索引(Index) | 索引(Index) | | 嵌套表(Embeded Table) | 嵌入式文檔(Embeded Document) | | 數組(Array) | 數組(Array) |
文檔
MongoDB 中的記錄就是一個 BSON 文檔,它是由鍵值對組成的數據結構,類似於 JSON 對象,是 MongoDB 中的基本數據單元。字段的值可能包括其他文檔、數組和文檔數組。
文檔的鍵是字符串。除了少數例外情況,鍵可以使用任意 UTF-8 字符。
- 鍵不能含有
\0
(空字符)。這個字符用來表示鍵的結尾。 .
和$
有特別的意義,只有在特定環境下才能使用。- 以下劃線
_
開頭的鍵是保留的(不是嚴格要求的)。
BSON [bee·sahn] 是 Binary JSON的簡稱,是 JSON 文檔的二進制表示,支持將文檔和數組嵌入到其他文檔和數組中,還包含允許表示不屬於 JSON 規範的數據類型的擴展。有關 BSON 規範的內容,可以參考 bsonspec.org,另見BSON 類型。
根據維基百科對 BJSON 的介紹,BJSON 的遍歷速度優於 JSON,這也是 MongoDB 選擇 BSON 的主要原因,但 BJSON 需要更多的存儲空間。
與 JSON 相比,BSON 着眼於提高存儲和掃描效率。BSON 文檔中的大型元素以長度字段為前綴以便於掃描。在某些情況下,由於長度前綴和顯式數組索引的存在,BSON 使用的空間會多於 JSON。
集合
MongoDB 集合存在於數據庫中,沒有固定的結構,也就是 無模式 的,這意味着可以往集合插入不同格式和類型的數據。不過,通常情況相愛插入集合中的數據都會有一定的關聯性。
集合不需要事先創建,當第一個文檔插入或者第一個索引創建時,如果該集合不存在,則會創建一個新的集合。
集合名可以是滿足下列條件的任意 UTF-8 字符串:
- 集合名不能是空字符串
""
。 - 集合名不能含有
\0
(空字符),這個字符表示集合名的結尾。 - 集合名不能以"system."開頭,這是為系統集合保留的前綴。例如
system.users
這個集合保存着數據庫的用户信息,system.namespaces
集合保存着所有數據庫集合的信息。 - 集合名必須以下劃線或者字母符號開始,並且不能包含
$
。
數據庫
數據庫用於存儲所有集合,而集合又用於存儲所有文檔。一個 MongoDB 中可以創建多個數據庫,每一個數據庫都有自己的集合和權限。
MongoDB 預留了幾個特殊的數據庫。
- admin : admin 數據庫主要是保存 root 用户和角色。例如,system.users 表存儲用户,system.roles 表存儲角色。一般不建議用户直接操作這個數據庫。將一個用户添加到這個數據庫,且使它擁有 admin 庫上的名為 dbAdminAnyDatabase 的角色權限,這個用户自動繼承所有數據庫的權限。一些特定的服務器端命令也只能從這個數據庫運行,比如關閉服務器。
- local : local 數據庫是不會被複制到其他分片的,因此可以用來存儲本地單台服務器的任意 collection。一般不建議用户直接使用 local 庫存儲任何數據,也不建議進行 CRUD 操作,因為數據無法被正常備份與恢復。
- config : 當 MongoDB 使用分片設置時,config 數據庫可用來保存分片的相關信息。
- test : 默認創建的測試庫,連接 mongod 服務時,如果不指定連接的具體數據庫,默認就會連接到 test 數據庫。
數據庫名可以是滿足以下條件的任意 UTF-8 字符串:
- 不能是空字符串
""
。 - 不得含有
' '
(空格)、.
、$
、/
、\
和\0
(空字符)。 - 應全部小寫。
- 最多 64 字節。
數據庫名最終會變成文件系統裏的文件,這也就是有如此多限制的原因。
MongoDB 有什麼特點?
- 數據記錄被存儲為文檔 :MongoDB 中的記錄就是一個 BSON 文檔,它是由鍵值對組成的數據結構,類似於 JSON 對象,是 MongoDB 中的基本數據單元。
- 模式自由 :集合的概念類似 MySQL 裏的表,但它不需要定義任何模式,能夠用更少的數據對象表現複雜的領域模型對象。
- 支持多種查詢方式 :MongoDB 查詢 API 支持讀寫操作 (CRUD)以及數據聚合、文本搜索和地理空間查詢。
- 支持 ACID 事務 :NoSQL 數據庫通常不支持事務,為了可擴展和高性能進行了權衡。不過,也有例外,MongoDB 就支持事務。與關係型數據庫一樣,MongoDB 事務同樣具有 ACID 特性。MongoDB 單文檔原生支持原子性,也具備事務的特性。MongoDB 4.0 加入了對多文檔事務的支持,但只支持複製集部署模式下的事務,也就是説事務的作用域限制為一個副本集內。MongoDB 4.2 引入了分佈式事務,增加了對分片集羣上多文檔事務的支持,併合並了對副本集上多文檔事務的現有支持。
- 高效的二進制存儲 :存儲在集合中的文檔,是以鍵值對的形式存在的。鍵用於唯一標識一個文檔,一般是 ObjectId 類型,值是以 BSON 形式存在的。BSON = Binary JSON, 是在 JSON 基礎上加了一些類型及元數據描述的格式。
- 自帶數據壓縮功能 :存儲同樣的數據所需的資源更少。
- 支持 mapreduce :通過分治的方式完成複雜的聚合任務。不過,從 MongoDB 5.0 開始,map-reduce 已經不被官方推薦使用了,替代方案是 聚合管道。聚合管道提供比 map-reduce 更好的性能和可用性。
- 支持多種類型的索引 :MongoDB 支持多種類型的索引,包括單字段索引、複合索引、多鍵索引、哈希索引、文本索引、 地理位置索引等,每種類型的索引有不同的使用場合。
- 支持 failover :提供自動故障恢復的功能,主節點發生故障時,自動從從節點中選舉出一個新的主節點,確保集羣的正常使用,這對於客户端來説是無感知的。
- 支持分片集羣 :MongoDB 支持集羣自動切分數據,讓集羣存儲更多的數據,具備更強的性能。在數據插入和更新時,能夠自動路由和存儲。
- 支持存儲大文件 :MongoDB 的單文檔存儲空間要求不超過 16MB。對於超過 16MB 的大文件,MongoDB 提供了 GridFS 來進行存儲,通過 GridFS,可以將大型數據進行分塊處理,然後將這些切分後的小文檔保存在數據庫中。
MongoDB 適合什麼應用場景?
MongoDB 的優勢在於其數據模型和存儲引擎的靈活性、架構的可擴展性以及對強大的索引支持。
選用 MongoDB 應該充分考慮 MongoDB 的優勢,結合實際項目的需求來決定:
- 隨着項目的發展,使用類 JSON 格式(BSON)保存數據是否滿足項目需求?MongoDB 中的記錄就是一個 BSON 文檔,它是由鍵值對組成的數據結構,類似於 JSON 對象,是 MongoDB 中的基本數據單元。
- 是否需要大數據量的存儲?是否需要快速水平擴展?MongoDB 支持分片集羣,可以很方便地添加更多的節點(實例),讓集羣存儲更多的數據,具備更強的性能。
- 是否需要更多類型索引來滿足更多應用場景?MongoDB 支持多種類型的索引,包括單字段索引、複合索引、多鍵索引、哈希索引、文本索引、 地理位置索引等,每種類型的索引有不同的使用場合。
- ......
MongoDB 存儲引擎
MongoDB 支持哪些存儲引擎?
存儲引擎(Storage Engine)是數據庫的核心組件,負責管理數據在內存和磁盤中的存儲方式。
與 MySQL 一樣,MongoDB 採用的也是 插件式的存儲引擎架構 ,支持不同類型的存儲引擎,不同的存儲引擎解決不同場景的問題。在創建數據庫或集合時,可以指定存儲引擎。
插件式的存儲引擎架構可以實現 Server 層和存儲引擎層的解耦,可以支持多種存儲引擎,如MySQL既可以支持B-Tree結構的InnoDB存儲引擎,還可以支持LSM結構的RocksDB存儲引擎。
在存儲引擎剛出來的時候,默認是使用 MMAPV1 存儲引擎,MongoDB4.x 版本不再支持 MMAPv1 存儲引擎。
現在主要有下面這兩種存儲引擎:
- WiredTiger 存儲引擎 :自 MongoDB 3.2 以後,默認的存儲引擎為 WiredTiger 存儲引擎 。非常適合大多數工作負載,建議用於新部署。WiredTiger 提供文檔級併發模型、檢查點和數據壓縮(後文會介紹到)等功能。
- In-Memory 存儲引擎 :In-Memory 存儲引擎在 MongoDB Enterprise 中可用。它不是將文檔存儲在磁盤上,而是將它們保留在內存中以獲得更可預測的數據延遲。
此外,MongoDB 3.0 提供了 可插拔的存儲引擎 API ,允許第三方為 MongoDB 開發存儲引擎,這點和 MySQL 也比較類似。
WiredTiger 基於 LSM Tree 還是 B+ Tree?
目前絕大部分流行的數據庫存儲引擎都是基於 B/B+ Tree 或者 LSM(Log Structured Merge) Tree 來實現的。對於 NoSQL 數據庫來説,絕大部分(比如 HBase、Cassandra、RocksDB)都是基於 LSM 樹,MongoDB 不太一樣。
上面也説了,自 MongoDB 3.2 以後,默認的存儲引擎為WiredTiger 存儲引擎。在 WiredTiger 引擎官網上,我們發現 WiredTiger 使用的是 B+ 樹作為其存儲結構:
WiredTiger maintains a table's data in memory using a data structure called a B-Tree ( B+ Tree to be specific), referring to the nodes of a B-Tree as pages. Internal pages carry only keys. The leaf pages store both keys and values.
此外,WiredTiger 還支持 LSM(Log Structured Merge) 樹作為存儲結構,MongoDB 在使用WiredTiger 作為存儲引擎時,默認使用的是 B+ 樹。
如果想要了解 MongoDB 使用 B 樹的原因,可以看看這篇文章:為什麼 MongoDB 使用 B 樹?。
使用 B+ 樹時,WiredTiger 以 page 為基本單位往磁盤讀寫數據。B+ 樹的每個節點為一個 page,共有三種類型的 page:
- root page(根節點) : B+ 樹的根節點。
- internal page(內部節點) :不實際存儲數據的中間索引節點。
- leaf page(葉子節點):真正存儲數據的葉子節點,包含一個頁頭(page header)、塊頭(block header)和真正的數據(key/value),其中頁頭定義了頁的類型、頁中實際載荷數據的大小、頁中記錄條數等信息;塊頭定義了此頁的checksum、塊在磁盤上的尋址位置等信息。
其整體結構如下圖所示:
如果想要深入研究學習 WiredTiger 存儲引擎,推薦閲讀 MongoDB 中文社區的 WiredTiger存儲引擎系列。
MongoDB 聚合
MongoDB 聚合有什麼用?
實際項目中,我們經常需要將多個文檔甚至是多個集合彙總到一起計算分析(比如求和、取最大值)並返回計算後的結果,這個過程被稱為 聚合操作 。
根據官方文檔介紹,我們可以使用聚合操作來:
- 將來自多個文檔的值組合在一起。
- 對集合中的數據進行的一系列運算。
- 分析數據隨時間的變化。
MongoDB 提供了哪幾種執行聚合的方法?
MongoDB 提供了兩種執行聚合的方法:
- 聚合管道(Aggregation Pipeline) :執行聚合操作的首選方法。
- 單一目的聚合方法(Single purpose aggregation methods) :也就是單一作用的聚合函數比如
count()
、distinct()
、estimatedDocumentCount()
。
絕大部分文章中還提到了 map-reduce 這種聚合方法。不過,從 MongoDB 5.0 開始,map-reduce 已經不被官方推薦使用了,替代方案是 聚合管道。聚合管道提供比 map-reduce 更好的性能和可用性。
MongoDB 聚合管道由多個階段組成,每個階段在文檔通過管道時轉換文檔。每個階段接收前一個階段的輸出,進一步處理數據,並將其作為輸入數據發送到下一個階段。
每個管道的工作流程是:
- 接受一系列原始數據文檔
- 對這些文檔進行一系列運算
- 結果文檔輸出給下一個階段
常用階段操作符 :
| 操作符 | 簡述 | | --------- | ------------------------------------------------------------ | | \$match | 匹配操作符,用於對文檔集合進行篩選 | | \$project | 投射操作符,用於重構每一個文檔的字段,可以提取字段,重命名字段,甚至可以對原有字段進行操作後新增字段 | | \$sort | 排序操作符,用於根據一個或多個字段對文檔進行排序 | | \$limit | 限制操作符,用於限制返回文檔的數量 | | \$skip | 跳過操作符,用於跳過指定數量的文檔 | | \$count | 統計操作符,用於統計文檔的數量 | | \$group | 分組操作符,用於對文檔集合進行分組 | | \$unwind | 拆分操作符,用於將數組中的每一個值拆分為單獨的文檔 | | \$lookup | 連接操作符,用於連接同一個數據庫中另一個集合,並獲取指定的文檔,類似於 populate |
更多操作符介紹詳見官方文檔:https://docs.mongodb.com/manual/reference/operator/aggregation/
階段操作符用於 db.collection.aggregate
方法裏面,數組參數中的第一層。
sql
db.collection.aggregate( [ { 階段操作符:表述 }, { 階段操作符:表述 }, ... ] )
下面是 MongoDB 官方文檔中的一個例子:
sql
db.orders.aggregate([
# 第一階段:$match階段按status字段過濾文檔,並將status等於"A"的文檔傳遞到下一階段。
{ $match: { status: "A" } },
# 第二階段:$group階段按cust_id字段將文檔分組,以計算每個cust_id唯一值的金額總和。
{ $group: { _id: "$cust_id", total: { $sum: "$amount" } } }
])
MongoDB 事務
MongoDB 事務想要搞懂原理還是比較花費時間的,我自己也沒有搞太明白。因此,我這裏只是簡單介紹一下 MongoDB 事務,想要了解原理的小夥伴,可以自行搜索查閲相關資料。
這裏推薦幾篇文章,供大家參考:
我們在介紹 NoSQL 數據的時候也説過,NoSQL 數據庫通常不支持事務,為了可擴展和高性能進行了權衡。不過,也有例外,MongoDB 就支持事務。
與關係型數據庫一樣,MongoDB 事務同樣具有 ACID 特性:
- 原子性(
Atomicity
) : 事務是最小的執行單位,不允許分割。事務的原子性確保動作要麼全部完成,要麼完全不起作用; - 一致性(
Consistency
): 執行事務前後,數據保持一致,例如轉賬業務中,無論事務是否成功,轉賬者和收款人的總額應該是不變的; - 隔離性(
Isolation
): 併發訪問數據庫時,一個用户的事務不被其他事務所幹擾,各併發事務之間數據庫是獨立的。WiredTiger 存儲引擎支持讀未提交( read-uncommitted )、讀已提交( read-committed )和快照( snapshot )隔離,MongoDB 啟動時默認選快照隔離。在不同隔離級別下,一個事務的生命週期內,可能出現髒讀、不可重複讀、幻讀等現象。 - 持久性(
Durability
): 一個事務被提交之後。它對數據庫中數據的改變是持久的,即使數據庫發生故障也不應該對其有任何影響。
關於事務的詳細介紹這篇文章就不多説了,感興趣的可以看看我寫的MySQL常見面試題總結這篇文章,裏面有詳細介紹到。
MongoDB 單文檔原生支持原子性,也具備事務的特性。當談論 MongoDB 事務的時候,通常指的是 多文檔 。MongoDB 4.0 加入了對多文檔 ACID 事務的支持,但只支持複製集部署模式下的 ACID 事務,也就是説事務的作用域限制為一個副本集內。MongoDB 4.2 引入了 分佈式事務 ,增加了對分片集羣上多文檔事務的支持,併合並了對副本集上多文檔事務的現有支持。
根據官方文檔介紹:
從 MongoDB 4.2 開始,分佈式事務和多文檔事務在 MongoDB 中是一個意思。分佈式事務是指分片集羣和副本集上的多文檔事務。從 MongoDB 4.2 開始,多文檔事務(無論是在分片集羣還是副本集上)也稱為分佈式事務。
在大多數情況下,多文檔事務比單文檔寫入會產生更大的性能成本。對於大部分場景來説, 非規範化數據模型(嵌入式文檔和數組) 依然是最佳選擇。也就是説,適當地對數據進行建模可以最大限度地減少對多文檔事務的需求。
注意 :
- 從MongoDB 4.2開始,多文檔事務支持副本集和分片集羣,其中:主節點使用WiredTiger存儲引擎,同時從節點使用WiredTiger存儲引擎或In-Memory存儲引擎。在MongoDB 4.0中,只有使用WiredTiger存儲引擎的副本集支持事務。
- 在MongoDB 4.2及更早版本中,你無法在事務中創建集合。從 MongoDB 4.4 開始,您可以在事務中創建集合和索引。有關詳細信息,請參閲 在事務中創建集合和索引。
MongoDB 數據壓縮
藉助 WiredTiger 存儲引擎( MongoDB 3.2 後的默認存儲引擎),MongoDB 支持對所有集合和索引進行壓縮。壓縮以額外的 CPU 為代價最大限度地減少存儲使用。
默認情況下,WiredTiger 使用 Snappy 壓縮算法(谷歌開源,旨在實現非常高的速度和合理的壓縮,壓縮比 3 ~ 5 倍)對所有集合使用塊壓縮,對所有索引使用前綴壓縮。
除了 Snappy 之外,對於集合還有下面這些壓縮算法:
- zlib:高度壓縮算法,壓縮比 5 ~ 7 倍
- Zstandard(簡稱 zstd):Facebook 開源的一種快速無損壓縮算法,針對 zlib 級別的實時壓縮場景和更好的壓縮比,提供更高的壓縮率和更低的 CPU 使用率,MongoDB 4.2 開始可用。
WiredTiger 日誌也會被壓縮,默認使用的也是 Snappy 壓縮算法。如果日誌記錄小於或等於 128 字節,WiredTiger 不會壓縮該記錄。
title: MongoDB常見面試題總結(下) category: 數據庫 tag: - NoSQL - MongoDB
MongoDB 索引
MongoDB 索引有什麼用?
和關係型數據庫類似,MongoDB 中也有索引。索引的目的主要是用來提高查詢效率,如果沒有索引的話,MongoDB 必須執行 集合掃描 ,即掃描集合中的每個文檔,以選擇與查詢語句匹配的文檔。如果查詢存在合適的索引,MongoDB 可以使用該索引來限制它必須檢查的文檔數量。並且,MongoDB 可以使用索引中的排序返回排序後的結果。
雖然索引可以顯著縮短查詢時間,但是使用索引、維護索引是有代價的。在執行寫入操作時,除了要更新文檔之外,還必須更新索引,這必然會影響寫入的性能。因此,當有大量寫操作而讀操作少時,或者不考慮讀操作的性能時,都不推薦建立索引。
MongoDB 支持哪些類型的索引?
MongoDB 支持多種類型的索引,包括單字段索引、複合索引、多鍵索引、哈希索引、文本索引、 地理位置索引等,每種類型的索引有不同的使用場合。
- 單字段索引: 建立在單個字段上的索引,索引創建的排序順序無所謂,MongoDB 可以頭/尾開始遍歷。
- 複合索引: 建立在多個字段上的索引,也可以稱之為組合索引、聯合索引。
- 多鍵索引 :MongoDB 的一個字段可能是數組,在對這種字段創建索引時,就是多鍵索引。MongoDB 會為數組的每個值創建索引。就是説你可以按照數組裏面的值做條件來查詢,這個時候依然會走索引。
- 哈希索引 :按數據的哈希值索引,用在哈希分片集羣上。
- 文本索引: 支持對字符串內容的文本搜索查詢。文本索引可以包含任何值為字符串或字符串元素數組的字段。一個集合只能有一個文本搜索索引,但該索引可以覆蓋多個字段。MongoDB 雖然支持全文索引,但是性能低下,暫時不建議使用。
- 地理位置索引: 基於經緯度的索引,適合 2D 和 3D 的位置查詢。
- 唯一索引 :確保索引字段不會存儲重複值。如果集合已經存在了違反索引的唯一約束的文檔,則後台創建唯一索引會失敗。
- TTL 索引 :TTL 索引提供了一個過期機制,允許為每一個文檔設置一個過期時間,當一個文檔達到預設的過期時間之後就會被刪除。
- ......
複合索引中字段的順序有影響嗎?
複合索引中字段的順序非常重要,例如下圖中的複合索引由{userid:1, score:-1}
組成,則該複合索引首先按照userid
升序排序;然後再每個userid
的值內,再按照score
降序排序。
在複合索引中,按照何種方式排序,決定了該索引在查詢中是否能被應用到。
走複合索引的排序:
sql
db.s2.find().sort({"userid": 1, "score": -1})
db.s2.find().sort({"userid": -1, "score": 1})
不走複合索引的排序:
sql
db.s2.find().sort({"userid": 1, "score": 1})
db.s2.find().sort({"userid": -1, "score": -1})
db.s2.find().sort({"score": 1, "userid": -1})
db.s2.find().sort({"score": 1, "userid": 1})
db.s2.find().sort({"score": -1, "userid": -1})
db.s2.find().sort({"score": -1, "userid": 1})
我們可以通過 explain 進行分析:
sql
db.s2.find().sort({"score": -1, "userid": 1}).explain()
複合索引遵循左前綴原則嗎?
MongoDB 的複合索引遵循左前綴原則 :擁有多個鍵的索引,可以同時得到所有這些鍵的前綴組成的索引,但不包括除左前綴之外的其他子集。比如説,有一個類似 {a: 1, b: 1, c: 1, ..., z: 1}
這樣的索引,那麼實際上也等於有了 {a: 1}
、{a: 1, b: 1}
、{a: 1, b: 1, c: 1}
等一系列索引,但是不會有 {b: 1}
這樣的非左前綴的索引。
什麼是 TTL 索引?
TTL 索引提供了一個過期機制,允許為每一個文檔設置一個過期時間 expireAfterSeconds
,當一個文檔達到預設的過期時間之後就會被刪除。TTL 索引除了有 expireAfterSeconds
屬性外,和普通索引一樣。
數據過期對於某些類型的信息很有用,比如機器生成的事件數據、日誌和會話信息,這些信息只需要在數據庫中保存有限的時間。
TTL 索引運行原理 :
- MongoDB 會開啟一個後台線程讀取該 TTL 索引的值來判斷文檔是否過期,但不會保證已過期的數據會立馬被刪除,因後台線程每 60 秒觸發一次刪除任務,且如果刪除的數據量較大,會存在上一次的刪除未完成,而下一次的任務已經開啟的情況,導致過期的數據也會出現超過了數據保留時間 60 秒以上的現象。
- 對於副本集而言,TTL 索引的後台進程只會在 Primary 節點開啟,在從節點會始終處於空閒狀態,從節點的數據刪除是由主庫刪除後產生的 oplog 來做同步。
TTL 索引限制 :
- TTL 索引是單字段索引。複合索引不支持 TTL
_id
字段不支持 TTL 索引。- 無法在上限集合(Capped Collection)上創建 TTL 索引,因為 MongoDB 無法從上限集合中刪除文檔。
- 如果某個字段已經存在非 TTL 索引,那麼在該字段上無法再創建 TTL 索引。
什麼是覆蓋索引查詢?
根據官方文檔介紹,覆蓋查詢是以下的查詢:
- 所有的查詢字段是索引的一部分。
- 結果中返回的所有字段都在同一索引中。
- 查詢中沒有字段等於
null
。
由於所有出現在查詢中的字段是索引的一部分, MongoDB 無需在整個數據文檔中檢索匹配查詢條件和返回使用相同索引的查詢結果。因為索引存在於內存中,從索引中獲取數據比通過掃描文檔讀取數據要快得多。
舉個例子:我們有如下 users
集合:
json
{
"_id": ObjectId("53402597d852426020000002"),
"contact": "987654321",
"dob": "01-01-1991",
"gender": "M",
"name": "Tom Benzamin",
"user_name": "tombenzamin"
}
我們在 users
集合中創建聯合索引,字段為 gender
和 user_name
:
sql
db.users.ensureIndex({gender:1,user_name:1})
現在,該索引會覆蓋以下查詢:
sql
db.users.find({gender:"M"},{user_name:1,_id:0})
為了讓指定的索引覆蓋查詢,必須顯式地指定 _id: 0
來從結果中排除 _id
字段,因為索引不包括 _id
字段。
MongoDB 高可用
複製集羣
什麼是複製集羣?
MongoDB 的複製集羣又稱為副本集羣,是一組維護相同數據集合的 mongod 進程。
客户端連接到整個 Mongodb 複製集羣,主節點機負責整個複製集羣的寫,從節點可以進行讀操作,但默認還是主節點負責整個複製集羣的讀。主節點發生故障時,自動從從節點中選舉出一個新的主節點,確保集羣的正常使用,這對於客户端來説是無感知的。
通常來説,一個複製集羣包含 1 個主節點(Primary),多個從節點(Secondary)以及零個或 1 個仲裁節點(Arbiter)。
- 主節點 :整個集羣的寫操作入口,接收所有的寫操作,並將集合所有的變化記錄到操作日誌中,即 oplog。主節點掛掉之後會自動選出新的主節點。
- 從節點 :從主節點同步數據,在主節點掛掉之後選舉新節點。不過,從節點可以配置成 0 優先級,阻止它在選舉中成為主節點。
- 仲裁節點 :這個是為了節約資源或者多機房容災用,只負責主節點選舉時投票不存數據,保證能有節點獲得多數贊成票。
下圖是一個典型的三成員副本集羣:
主節點與備節點之間是通過 oplog(操作日誌) 來同步數據的。oplog 是 local 庫下的一個特殊的 上限集合(Capped Collection) ,用來保存寫操作所產生的增量日誌,類似於 MySQL 中 的 Binlog。
上限集合類似於定長的循環隊列,數據順序追加到集合的尾部,當集合空間達到上限時,它會覆蓋集合中最舊的文檔。上限集合的數據將會被順序寫入到磁盤的固定空間內,所以,I/O 速度非常快,如果不建立索引,性能更好。
當主節點上的一個寫操作完成後,會向 oplog 集合寫入一條對應的日誌,而從節點則通過這個 oplog 不斷拉取到新的日誌,在本地進行回放以達到數據同步的目的。
副本集最多有一個主節點。 如果當前主節點不可用,一個選舉會抉擇出新的主節點。MongoDB 的節點選舉規則能夠保證在 Primary 掛掉之後選取的新節點一定是集羣中數據最全的一個。
為什麼要用複製集羣?
- 實現 failover :提供自動故障恢復的功能,主節點發生故障時,自動從從節點中選舉出一個新的主節點,確保集羣的正常使用,這對於客户端來説是無感知的。
- 實現讀寫分離 :我們可以設置從節點上可以讀取數據,主節點負責寫入數據,這樣的話就實現了讀寫分離,減輕了主節點讀寫壓力過大的問題。MongoDB 4.0 之前版本如果主庫壓力不大,不建議讀寫分離,因為寫會阻塞讀,除非業務對響應時間不是非常關注以及讀取歷史數據接受一定時間延遲。
分片集羣
什麼是分片集羣?
分片集羣是 MongoDB 的分佈式版本,相較副本集,分片集羣數據被均衡的分佈在不同分片中, 不僅大幅提升了整個集羣的數據容量上限,也將讀寫的壓力分散到不同分片,以解決副本集性能瓶頸的難題。
MongoDB 的分片集羣由如下三個部分組成(下圖來源於官方文檔對分片集羣的介紹):
- Config Servers:配置服務器,本質上是一個 MongoDB 的副本集,負責存儲集羣的各種元數據和配置,如分片地址、Chunks 等
- Mongos:路由服務,不存具體數據,從 Config 獲取集羣配置講請求轉發到特定的分片,並且整合分片結果返回給客户端。
- Shard:每個分片是整體數據的一部分子集,從MongoDB3.6版本開始,每個Shard必須部署為副本集(replica set)架構
為什麼要用分片集羣?
隨着系統數據量以及吞吐量的增長,常見的解決辦法有兩種:垂直擴展和水平擴展。
垂直擴展通過增加單個服務器的能力來實現,比如磁盤空間、內存容量、CPU 數量等;水平擴展則通過將數據存儲到多個服務器上來實現,根據需要添加額外的服務器以增加容量。
類似於 Redis Cluster,MongoDB 也可以通過分片實現 水平擴展 。水平擴展這種方式更靈活,可以滿足更大數據量的存儲需求,支持更高吞吐量。並且,水平擴展所需的整體成本更低,僅僅需要相對較低配置的單機服務器即可,代價是增加了部署的基礎設施和維護的複雜性。
也就是説當你遇到如下問題時,可以使用分片集羣解決:
- 存儲容量受單機限制,即磁盤資源遭遇瓶頸。
- 讀寫能力受單機限制,可能是 CPU、內存或者網卡等資源遭遇瓶頸,導致讀寫能力無法擴展。
什麼是分片鍵?
分片鍵(Shard Key) 是數據分區的前提, 從而實現數據分發到不同服務器上,減輕服務器的負擔。也就是説,分片鍵決定了集合內的文檔如何在集羣的多個分片間的分佈狀況。
分片鍵就是文檔裏面的一個字段,但是這個字段不是普通的字段,有一定的要求:
- 它必須在所有文檔中都出現。
- 它必須是集合的一個索引,可以是單索引或複合索引的前綴索引,不能是多索引、文本索引或地理空間位置索引。
- MongoDB 4.2 之前的版本,文檔的分片鍵字段值不可變。MongoDB 4.2 版本開始,除非分片鍵字段是不可變的
_id
字段,否則您可以更新文檔的分片鍵值。MongoDB 5.0 版本開始,實現了實時重新分片(live resharding),可以實現分片鍵的完全重新選擇。 - 它的大小不能超過 512 字節。
如何選擇分片鍵?
選擇合適的片鍵對 sharding 效率影響很大,主要基於如下四個因素(摘自分片集羣使用注意事項 - - 騰訊雲文檔):
- 取值基數 取值基數建議儘可能大,如果用小基數的片鍵,因為備選值有限,那麼塊的總數量就有限,隨着數據增多,塊的大小會越來越大,導致水平擴展時移動塊會非常困難。 例如:選擇年齡做一個基數,範圍最多隻有100個,隨着數據量增多,同一個值分佈過多時,導致 chunck 的增長超出 chuncksize 的範圍,引起 jumbo chunk,從而無法遷移,導致數據分佈不均勻,性能瓶頸。
- 取值分佈 取值分佈建議儘量均勻,分佈不均勻的片鍵會造成某些塊的數據量非常大,同樣有上面數據分佈不均勻,性能瓶頸的問題。
- 查詢帶分片 查詢時建議帶上分片,使用分片鍵進行條件查詢時,mongos 可以直接定位到具體分片,否則 mongos 需要將查詢分發到所有分片,再等待響應返回。
- 避免單調遞增或遞減 單調遞增的 sharding key,數據文件挪動小,但寫入會集中,導致最後一篇的數據量持續增大,不斷髮生遷移,遞減同理。
綜上,在選擇片鍵時要考慮以上4個條件,儘可能滿足更多的條件,才能降低 MoveChuncks 對性能的影響,從而獲得最優的性能體驗。
分片策略有哪些?
MongoDB 支持兩種分片算法來滿足不同的查詢需求(摘自MongoDB 分片集羣介紹 - 阿里雲文檔):
1、基於範圍的分片 :
MongoDB 按照分片鍵(Shard Key)的值的範圍將數據拆分為不同的塊(Chunk),每個塊包含了一段範圍內的數據。當分片鍵的基數大、頻率低且值非單調變更時,範圍分片更高效。
- 優點: Mongos 可以快速定位請求需要的數據,並將請求轉發到相應的 Shard 節點中。
- 缺點: 可能導致數據在 Shard 節點上分佈不均衡,容易造成讀寫熱點,且不具備寫分散性。
- 適用場景:分片鍵的值不是單調遞增或單調遞減、分片鍵的值基數大且重複的頻率低、需要範圍查詢等業務場景。
2、基於 Hash 值的分片
MongoDB 計算單個字段的哈希值作為索引值,並以哈希值的範圍將數據拆分為不同的塊(Chunk)。
- 優點:可以將數據更加均衡地分佈在各 Shard 節點中,具備寫分散性。
- 缺點:不適合進行範圍查詢,進行範圍查詢時,需要將讀請求分發到所有的 Shard 節點。
- 適用場景:分片鍵的值存在單調遞增或遞減、片鍵的值基數大且重複的頻率低、需要寫入的數據隨機分發、數據讀取隨機性較大等業務場景。
除了上述兩種分片策略,您還可以配置 複合片鍵 ,例如由一個低基數的鍵和一個單調遞增的鍵組成。
分片數據如何存儲?
Chunk(塊) 是 MongoDB 分片集羣的一個核心概念,其本質上就是由一組 Document 組成的邏輯數據單元。每個 Chunk 包含一定範圍片鍵的數據,互不相交且並集為全部數據,即離散數學中劃分的概念。
分片集羣不會記錄每條數據在哪個分片上,而是記錄 Chunk 在哪個分片上一級這個 Chunk 包含哪些數據。
默認情況下,一個 Chunk 的最大值默認為 64MB(可調整,取值範圍為 1~1024 MB。如無特殊需求,建議保持默認值),進行數據插入、更新、刪除時,如果此時 Mongos 感知到了目標 Chunk 的大小或者其中的數據量超過上限,則會觸發 Chunk **。
數據的增長會讓 Chunk 得越來越多。這個時候,各個分片上的 Chunk 數量可能會不平衡。Mongos 中的 均衡器(Balancer) 組件就會執行自動平衡,嘗試使各個 Shard 上 Chunk 的數量保持均衡,這個過程就是 再平衡(Rebalance)**。默認情況下,數據庫和集合的 Rebalance 是開啟的。
如下圖所示,隨着數據插入,導致 Chunk **,讓 AB 兩個分片有 3 個 Chunk,C 分片只有一個,這個時候就會把 B 分配的遷移一個到 C 分片實現集羣數據均衡。
Balancer 是 MongoDB 的一個運行在 Config Server 的 Primary 節點上(自 MongoDB 3.4 版本起)的後台進程,它監控每個分片上 Chunk 數量,並在某個分片上 Chunk 數量達到閾值進行遷移。
Chunk 只會分裂,不會合並,即使 chunkSize 的值變大。
Rebalance 操作是比較耗費系統資源的,我們可以通過在業務低峯期執行、預分片或者設置 Rebalance 時間窗等方式來減少其對 MongoDB 正常使用所帶來的影響。
Chunk 遷移原理是什麼?
關於 Chunk 遷移原理的詳細介紹,推薦閲讀 MongoDB 中文社區的一文讀懂 MongoDB chunk 遷移這篇文章。
學習資料推薦
- MongoDB 中文手冊|官方文檔中文版(推薦):基於 4.2 版本,不斷與官方最新版保持同步。
- MongoDB 初學者教程——7 天學習 MongoDB:快速入門。
- SpringBoot 整合 MongoDB 實戰 - 2022 :很不錯的一篇 MongoDB 入門文章,主要圍繞 MongoDB 的 Java 客户端使用進行基本的增刪改查操作介紹。
參考
- MongoDB 官方文檔(主要參考資料,以官方文檔為準):https://www.mongodb.com/docs/manual/
- 《MongoDB 權威指南》
- 技術乾貨| MongoDB 事務原理 - MongoDB 中文社區:https://mongoing.com/archives/82187
- Transactions - MongoDB 官方文檔:https://www.mongodb.com/docs/manual/core/transactions/
- WiredTiger Storage Engine - MongoDB 官方文檔:https://www.mongodb.com/docs/manual/core/wiredtiger/
- WiredTiger存儲引擎之一:基礎數據結構分析:https://mongoing.com/topic/archives-35143
- MongoDB 官方文檔(主要參考資料,以官方文檔為準):https://www.mongodb.com/docs/manual/
- 《MongoDB 權威指南》
- Indexes - MongoDB 官方文檔:https://www.mongodb.com/docs/manual/indexes/
- MongoDB - 索引知識 - 程序員翔仔 - 2022:https://fatedeity.cn/posts/database/mongodb-index-knowledge.html
- MongoDB - 索引: https://www.cnblogs.com/Neeo/articles/14325130.html
- Sharding - MongoDB 官方文檔:https://www.mongodb.com/docs/manual/sharding/
- MongoDB 分片集羣介紹 - 阿里雲文檔:https://help.aliyun.com/document_detail/64561.html
- 分片集羣使用注意事項 - - 騰訊雲文檔:https://cloud.tencent.com/document/product/240/44611
- 攜程Java三面面經,已拿 offer!!
- 萬字詳解,吃透 MongoDB!
- 1.2 w字 !Java IO 基礎知識系統總結 | JavaGuide
- 面試常問:UDP 和 TCP 的區別
- 32 道 Spring 常見面試總結(附詳細參考答案),我經常拿來面試別人
- Redis 5 種基本數據結構(String、List、Hash、Set、Sorted Set)詳解 | JavaGuide
- Java 集合常見知識點&面試題總結(上),2022 最新版!
- MySQL 鎖常見知識點&面試題總結
- SpringBoot JWT Redis 開源知識社區系統
- Java 基礎常見知識點&面試題總結(下),2022 最新版!
- Java 基礎常見知識點&面試題總結(下),2022 最新版!
- 一文帶你搞懂 JWT 常見概念 & 優缺點
- 我的 Java 學習&面試網站又又又升級了!
- Java 基礎常見知識點&面試題總結(中),2022 最新版!
- Java 基礎常見知識點&面試題總結(上),2022 最新版!| JavaGuide
- 我常用的兩個翻譯神器!程序員必備 | JavaGuide
- 推薦一個基於 Spring Boot MyBatis Plus JWT 的問卷系統!
- 我常用的兩個翻譯神器!程序員必備 | JavaGuide
- MySQL 事務常見面試題總結 | JavaGuide
- MySQL 事務常見面試題總結 | JavaGuide 審核中