本文介紹云數據庫 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,說明查詢是由索引覆蓋的。
在嘗試實現覆蓋查詢時,有一個常見的陷阱是_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()
來支持性能測試,或將其用于必須選擇某一字段或包含在多個索引中的某一字段的某些查詢。
使用部分索引
通過僅包含將通過索引訪問的文檔來減小索引的大小和性能開銷。
例如,在orderID字段上創建部分索引,只包括orderStatus為“正在進行中”的訂單文檔,或者僅在文檔中存在emailAddress字段時創建索引。