本文介紹ApsaraDB for MongoDB建立索引的最佳實務,包括分析索引效率、最佳化索引選項以及如何針對具體查詢建立高效索引。
如何選擇索引
MongoDB支援多種類型的索引,您需要根據您的使用情境選擇合適的索引類型:
使用單鍵索引
如果您在使用MongoDB時所有的查詢都是單鍵查詢,建議您建立單鍵索引。
使用複合索引
如果您在使用MongoDB時,有時使用單鍵查詢,有時使用包含多個鍵的查詢,建議建立複合索引(至多可支援三十二個鍵的組合)。例如,您可以同時建立category和item複合式索引。
db.products.createIndex( { "category": 1, "item": 1 } )
使用文本(text)索引
常規索引用於匹配欄位的整個值。如果您只想在包含大量文本的欄位中匹配特定單詞,則應使用文本索引來支援文本匹配。更多關於文本索引的介紹,請參見文本索引。
索引定序(Collation)
如果您需要使用索引進行字串比較,則查詢操作必須指定相同的定序。如果查詢操作指定了不同的定序,則具有定序的索引無法支援對索引欄位執行字串比較的操作。
例如,集合在具有定序語言環境的字串myColl
欄位上具有索引category "fr"
,樣本如下:
db.myColl.createIndex( { category: 1 }, { collation: { locale: "fr" } } )
下面的查詢操作,指定了與索引相同的定序,就可以使用索引:
db.myColl.find( { category: "cafe" } ).collation( { locale: "fr" } )
以下預設使用"simple"
二進位定序的查詢操作,則不能使用索引:
db.myColl.find( { category: "cafe" } )
對於索引首碼鍵不是字串、數組和嵌入文檔的複合索引,指定不同定序的操作仍然可以使用該索引來支援對索引首碼鍵的比較。更多關於定序的介紹,請參見Collation。
根據慢日誌分析索引情況
MongoDB索引最佳化主要是為了降低集合的掃描量,所以主要關注慢日誌中的DocsExamined和KeysExamined指標。如何查看慢日誌,請參見查看慢日誌。
DocsExamined:表示該條查詢掃描的文檔個數。如果數量很大,表示資料庫需要掃描大量的非索引條目,這種情況一般建議給掃描數量大的欄位建索引。
KeysExamined:表示在該索引中掃描的key的個數。如果數量很大,但是返回的nreturned很小,則表示資料庫掃描了大量的索引keys來得到結果文檔,說明該索引不夠高效,需要調整索引或建立其他索引。
索引分析思路如下:
全表掃描(關鍵字:COLLSCAN、DocsExamined)
全集合(表)掃描COLLSCAN 。當執行一個操作請求(如查詢、更新、刪除等)後,您在查看慢請求日誌時發現COLLSCAN關鍵字,建議對查詢的欄位建立索引的方式來最佳化。
通過查看DocsExamined的值,可以瞭解到一個查詢掃描了多少文檔。該值越大,請求所佔用的CPU開銷越大,後續排隊的阻塞越嚴重。
不合理的索引(關鍵字:IXSCAN、keysExamined)
通過查看keysExamined欄位,可以查看到一個使用了索引的查詢,掃描了多少條索引。該值越大,CPU開銷越大。
如果索引建立得不太合理,或者是匹配的結果很多,即使使用索引,請求開銷也不會最佳化很多,執行的速度也會很慢。
當在慢日誌裡發現SORT關鍵字時,可以考慮通過索引來最佳化排序。更多說明,請參見The ESR (Equality, Sort, Range) Rule。
索引最佳化建議
盡量使用覆蓋查詢(Covered Queries)
覆蓋查詢是直接從索引中返回結果,無需訪問來源文件,非常高效。要確定查詢是否是覆蓋查詢,可以使用explain()
。如果explain()
的輸出顯示totalDocsExamined為0,說明查詢是由索引覆蓋的。
如果explain()
的輸出結果中沒有totalDocsExamined欄位,建議您使用executionStats
或allPlansExecution
模式進行查詢,例如explain("executionStats")
或explain("allPlansExecution")
。
在嘗試實現覆蓋查詢時,有一個常見的陷阱是_id欄位預設始終返回。您需要明確地將其從查詢結果中排除,或者將其添加到索引中。
在分區叢集中,MongoDB內部需要訪問分區鍵的欄位。因此,只有在分區鍵是索引的一部分時,覆蓋查詢才可行。通常情況下,最好將分區鍵也作為索引的一部分。
去除冗餘索引
索引是資源密集型的,即使在MongoDB的WiredTiger儲存引擎中使用壓縮,它們也會消耗RAM和磁碟。此外,隨著欄位的更新,相關的索引也必須進行維護,這會增加額外的CPU和磁碟I/O負載。因此,我們應該謹慎評估和刪除不再需要的索引。
複合索引建議
多個欄位的複合查詢,欄位順序不一樣時,也都是屬於一類查詢,您只需要建一個。比如索引
{a:1, b:1}
和{b:1, a:1}
只需存在一個。內含項目關聯性引起的冗餘索引,比如有如下兩個查詢:
db.myCol.find({"b": 2, "c": 3})
db.myCol.find({"a": 1, "b": 2, "c": 3})
查詢2中包含查詢1中的欄位,您可以只用一個索引滿足這兩個查詢要求,並且將被包含的查詢的欄位放到最左邊,即索引應為
{b: 1, c: 1, a: 1}
。唯一索引和其它欄位組合導致的冗餘索引,比如有如下兩個查詢:
db.myCol.find({"a": 1, "b": 1})
db.myCol.find({"a": 1, "c": 1})
如果a欄位取值是唯一的,那麼這兩個查詢中除a外的欄位建索引沒用,只需要建索引
{a: 1}
。
非等值索引建議
非等值組合查詢索引建立不合理,比如如下查詢:
db.myCol.find({"a": {$gte: 1} , "b": {$lte: 1}})
這種多欄位的非等值查詢,只有最左邊的欄位才能走索引,這裡只會走a欄位的索引,您只需對a欄位建索引。
等值+非等值查詢組合,比如如下查詢:
db.myCol.find({"a": {$gte: 1} , "b": 1})
這種情況最優索引應該把等值查詢放到左邊,即應該建索引
{b: 1, a: 1}
。
$or類查詢索引建議
$or類查詢需要對各個條件分別建立索引,比如如下查詢:
db.myCol.find({$or: [{"a": 1, "b": 1}, {"c": 1, "d": 1}]})
您需要針對$or中兩個條件f分別建立最優索引,即{a: 1, b: 1}
和{c: 1, d: 1}
,而不是{a: 1, b: 1, c: 1, d: 1}
。
Sort類查詢索引建議
同一欄位不同Sort查詢只需要建一個索引,比如如下查詢:
db.myCol.find({}).sort({"a":1})
db.myCol.find({}).sort({"a":-1})
您只需要建索引
{a: 1}
。多欄位Sort查詢,比如如下查詢:
db.myCol.find({}).sort({"a":1, "b": -1})
索引
{a: 1, b: 1}
是無效的,必須要建立{a: 1, b: -1}
的索引才有效。等值查詢+非等值查詢+Sort查詢,比如如下查詢:
db.myCol.find({"a": 1, "b": 2, "c": {$gte: 1}}).sort({"d": 1, "e": -1})
建設索引的欄位順序為:
等值->sort->非等值
,即{a: 1, b: 1, d: 1, e: -1, c: 1}
。$or+Sort查詢,比如如下查詢:
db.myCol.find({$or: [{"a": 1, "b": 1}, {"c": 1, "d": 1}]}).sort({"e": -1})
該查詢可以拆分為
db.myCol.find({"a": 1, "b": 1}).sort({"e":-1})
和db.myCol.find({"c": 1, "d": 1}).sort({"e":-1})
兩個查詢,按照等值查詢+非等值查詢+Sort查詢中規則,應該建索引{a: 1, b: 1, e: -1}
和{c: 1, d: 1, e: -1}
。
使用映射僅返回需要的欄位
當您只需要文檔中的部分欄位時,可通過僅返回所需欄位來實現更優效能。
例如,在針對posts集合的查詢中,您僅需timestamp、title、author和abstract欄位,則可使用以下查詢命令:
db.posts.find( {}, { timestamp : 1 , title : 1 , author : 1 , abstract : 1} ).sort( { timestamp : -1 } )
使用hint()返回特定索引
大多數情況下,查詢最佳化工具會為特定操作選擇最佳索引。特殊情況下,您也可以使用hint()
方法強制MongoDB使用特定索引。
例如,使用hint()
來支援效能測試,或將其用於必須選擇某一欄位或包含在多個索引中的某一欄位的某些查詢。
使用部分索引
使用部分索引來減小索引的大小和效能開銷,即構建的索引只包含會被查詢到的欄位。
例如,集合中包含a, b, c
三個欄位,如果查詢條件中只包含了a
欄位,就只對a
欄位構建索引。