萬字詳解,吃透 MongoDB!

語言: CN / TW / HK

本文已經收錄進 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 中的基本資料單元。欄位的值可能包括其他文件、陣列和文件陣列。

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。

BSON 官網首頁

集合

MongoDB 集合存在於資料庫中,沒有固定的結構,也就是 無模式 的,這意味著可以往集合插入不同格式和型別的資料。不過,通常情況相愛插入集合中的資料都會有一定的關聯性。

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 B+樹整體結構

如果想要深入研究學習 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 聚合管道由多個階段組成,每個階段在文件通過管道時轉換文件。每個階段接收前一個階段的輸出,進一步處理資料,並將其作為輸入資料傳送到下一個階段。

每個管道的工作流程是:

  1. 接受一系列原始資料文件
  2. 對這些文件進行一系列運算
  3. 結果文件輸出給下一個階段

管道的工作流程

常用階段操作符

| 操作符 | 簡述 | | --------- | ------------------------------------------------------------ | | \$match | 匹配操作符,用於對文件集合進行篩選 | | \$project | 投射操作符,用於重構每一個文件的欄位,可以提取欄位,重新命名欄位,甚至可以對原有欄位進行操作後新增欄位 | | \$sort | 排序操作符,用於根據一個或多個欄位對文件進行排序 | | \$limit | 限制操作符,用於限制返回文件的數量 | | \$skip | 跳過操作符,用於跳過指定數量的文件 | | \$count | 統計操作符,用於統計文件的數量 | | \$group | 分組操作符,用於對文件集合進行分組 | | \$unwind | 拆分操作符,用於將陣列中的每一個值拆分為單獨的文件 | | \$lookup | 連線操作符,用於連線同一個資料庫中另一個集合,並獲取指定的文件,類似於 populate |

更多操作符介紹詳見官方文件:http://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 集合中建立聯合索引,欄位為 genderuser_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 得越來越多。這個時候,各個分片上的 Chunk 數量可能會不平衡。Mongos 中的 均衡器(Balancer) 元件就會執行自動平衡,嘗試使各個 Shard 上 Chunk 的數量保持均衡,這個過程就是 再平衡(Rebalance)**。預設情況下,資料庫和集合的 Rebalance 是開啟的。

如下圖所示,隨著資料插入,導致 Chunk **,讓 AB 兩個分片有 3 個 Chunk,C 分片只有一個,這個時候就會把 B 分配的遷移一個到 C 分片實現叢集資料均衡。

Chunk 遷移

Balancer 是 MongoDB 的一個執行在 Config Server 的 Primary 節點上(自 MongoDB 3.4 版本起)的後臺程序,它監控每個分片上 Chunk 數量,並在某個分片上 Chunk 數量達到閾值進行遷移。

Chunk 只會分裂,不會合並,即使 chunkSize 的值變大。

Rebalance 操作是比較耗費系統資源的,我們可以通過在業務低峰期執行、預分片或者設定 Rebalance 時間窗等方式來減少其對 MongoDB 正常使用所帶來的影響。

Chunk 遷移原理是什麼?

關於 Chunk 遷移原理的詳細介紹,推薦閱讀 MongoDB 中文社群的一文讀懂 MongoDB chunk 遷移這篇文章。

學習資料推薦

參考

  • MongoDB 官方文件(主要參考資料,以官方文件為準):http://www.mongodb.com/docs/manual/
  • 《MongoDB 權威指南》
  • 技術乾貨| MongoDB 事務原理 - MongoDB 中文社群:http://mongoing.com/archives/82187
  • Transactions - MongoDB 官方文件:http://www.mongodb.com/docs/manual/core/transactions/
  • WiredTiger Storage Engine - MongoDB 官方文件:http://www.mongodb.com/docs/manual/core/wiredtiger/
  • WiredTiger儲存引擎之一:基礎資料結構分析:http://mongoing.com/topic/archives-35143
  • MongoDB 官方文件(主要參考資料,以官方文件為準):http://www.mongodb.com/docs/manual/
  • 《MongoDB 權威指南》
  • Indexes - MongoDB 官方文件:http://www.mongodb.com/docs/manual/indexes/
  • MongoDB - 索引知識 - 程式設計師翔仔 - 2022:http://fatedeity.cn/posts/database/mongodb-index-knowledge.html
  • MongoDB - 索引: http://www.cnblogs.com/Neeo/articles/14325130.html
  • Sharding - MongoDB 官方文件:http://www.mongodb.com/docs/manual/sharding/
  • MongoDB 分片叢集介紹 - 阿里雲文件:http://help.aliyun.com/document_detail/64561.html
  • 分片叢集使用注意事項 - - 騰訊雲文件:http://cloud.tencent.com/document/product/240/44611