小李是這家公司的後端負責人,忽然有一天下午,收到大量客服反饋用戶沒法使用咱們的APP,不少操做與加載都是網絡等待超時。數據庫
收到信息後,小李立馬排查問題緣由,不過多一會,定位到數據庫出現大量慢查詢致使服務器超負荷負載狀態,CPU居高不下,那麼爲何會出現這個狀況呢,此時小李很慌,通過查詢資料,開始往慢查詢方向探究,果不其然,因爲業務數據增加迅猛,對應的數據表沒有相應查詢的索引數據,此刻小李嘴角上揚,面露微笑,信心百倍上手的給數據庫相關數據表加上了索引字段。可是狀況並無好轉,線上依舊沒有恢復,經驗使然,最後只能採起降級的方案(關閉此表的相關查詢業務)臨時先恢復線上正常。後端
可是事情並無結束,問題沒有根本性的解決,公司和本身依舊很是在乎這個問題的解決,晚上吃飯的時候,小李忽然想起了本身有認識一個行業大佬(老白)。把問題跟老白說了一遍,老白並沒過多久,很快就專業的告訴了小白哪些操做存在問題,怎麼樣能夠正確的解決這個問題,加索引的時候首先要學會作查詢分析,而後瞭解ESR最佳實踐規則(下面會作說明),小李沒有由於本身的不足感到失落,反而是由於本身的不足更是充滿了求知慾。設計模式
數據庫索引的應用有哪些優秀的姿式呢?數組
db.user.createIndex({createdAt: 1})
createdAt
建立了單字段索引,能夠快速檢索createdAt
字段的各類查詢請求,比較常見{createdAt: 1}
升序索引,也能夠經過{createdAt: -1}
來降序索引,對於單字段索引,
升序/降序效果是同樣的。安全
db.user.createIndex({age: 1, createdAt: 1})
能夠對多個字段聯合建立索引,先按第一個字段排序,第一個字段相同的文檔按第二個字段排序,依次類推,因此在作查詢的時候排序與索引的應用也是很是重要。服務器
實際場景,使用最多的也是這類索引,在MongoDB中是知足因此能匹配符合索引前綴的查詢,例如已經存在db.user.createIndex({age: 1, createdAt: 1})
,
咱們就不須要單獨爲db.user.createIndex({age: 1})
創建索引,由於單獨使用age作查詢條件的時候,也是能夠命中db.user.createIndex({age: 1, createdAt: 1})
,可是使用createdAt
單獨做爲查詢條件的時候是不能匹配db.user.createIndex({age: 1, createdAt: 1})
網絡
當索引的字段爲數組時,建立出的索引稱爲多key索引,多key索引會爲數組的每一個元素創建一條索引app
// 用戶的社交登陸信息, schema = { … snsPlatforms:[{ platform:String, // 登陸平臺 openId:String, // 登陸惟一標識符 }] } // 這也是一個列轉行文檔設計,後面會說 db.user.createIndex({snsPlatforms.openId:1})
能夠針對某個時間字段,指定文檔的過時時間(用於僅在一段時間有效的數據存儲,文檔達到指定時間就會被刪除,這樣就能夠完成自動刪除數據)
這個刪除操做是安全的,數據會選擇在應用的低峯期執行,因此不會由於刪除大量文件形成高額IO嚴重影響數據性能。函數
3.2版本
才支持該特性,給符合條件的數據文檔創建索引,意在節約索引存儲空間與寫入成本性能
db.user.createIndex({sns.qq.openId:1}) /** * 給qq登陸openid加索引,系統其實只有不多一部分用到qq登陸 ,而後纔會存在這個數據字段,這個時 * 候就沒有必要給全部文檔加上這個索引,僅須要知足條件才加索引 */ db.user.createIndex({sns.qq.openId:1} ,{partialFilterExpression:{$exists:1}})
稀疏索引僅包含具備索引字段的文檔條目,即便索引字段包含空值也是如此。
索引會跳過缺乏索引字段的全部文檔。
db.user.createIndex({sns.qq.openId:1} ,{sparse:true})
注:3.2版本開始,提供了部分索引,能夠當作稀疏索引的超集,官方推薦優先使用部分索引而不是稀疏索引。
索引字段順序: equal(精準匹配) > sort (排序條件)> range (範圍查詢)
精確(Equal)
匹配的字段放最前面,排序(Sort)
條件放中間,範圍(Range)
匹配的字段放最後面,也適用於ES,ER。
實際例子:獲取成績表中,高2班中數學分數大於120的學生,按照分數從大到小排序
不難看出,班級和學科(數學)能夠是精準匹配,分數是一個範圍查詢,同時也是排序條件
那麼按照ESR規則咱們能夠這樣創建索引
{"班級":1,"學科":1,"分數":1}
db.collection.explain()
函數能夠輸出文檔查找執行計劃,能夠幫助咱們作更正確的選擇。
分析函數返回的數據不少,但咱們主要能夠關注這個字段
{ "queryPlanner": { "plannerVersion": 1, "namespace": "test.user", "indexFilterSet": false, "parsedQuery": { "age": { "$eq": 13 } }, "winningPlan": { ... }, "rejectedPlans": [] }, "executionStats": { "executionSuccess": true, "nReturned": 100, "executionTimeMillis": 137, "totalKeysExamined": 48918, "totalDocsExamined": 48918, "allPlansExecution": [] }, "ok": 1, }
nReturned
實際返回數據行數
executionTimeMillis
命令執行總時間,單位毫秒
totalKeysExamined
表示MongoDB 掃描了N個索引數據。 檢查的鍵數與返回的文檔數相匹配,這意味着mongod只需檢查索引鍵便可返回結果。mongod沒必要掃描全部文檔,只有N個匹配的文檔被拉入內存。 這個查詢結果是很是高效的。
totalDocsExamined
文檔掃描數
這幾個字段的值越小說明效率越好,最佳狀態是nReturned
= totalKeysExamined
= totalDocsExamined
若是相差很大,說明還有很大優化空間,當具體業務還要酌情分析。
查詢優化器針對該query所返回的最優執行計劃的詳細內容(queryPlanne.winningPlan)
stage
COLLSCAN:全表掃描,這個狀況是最糟糕的 IXSCAN:索引掃描 FETCH:根據索引去檢索指定document SHARD_MERGE:將各個分片返回數據進行merge SORT:代表在內存中進行了排序 LIMIT:使用limit限制返回數 SKIP:使用skip進行跳過 IDHACK:針對_id進行查詢 SHARDING_FILTER:經過mongos對分片數據進行查詢 COUNT:利用db.coll.explain().count()之類進行count運算 COUNTSCAN: count不使用Index進行count時的stage返回 COUNT_SCAN: count使用了Index進行count時的stage返回 SUBPLA:未使用到索引的$or查詢的stage返回 TEXT:使用全文索引進行查詢時候的stage返回 PROJECTION:限定返回字段時候stage的返回
COLLSCAN(全表掃描) SORT可是沒有相關的索引 超大的SKIP SUBPLA在使用$or的時候沒有命中索引 COUNTSCAN 執行count沒有命中索引
db.user.find({age:13}).skip(100).limit(100).sort({createdAt:-1})
圖中能夠看出,首先是IXSCAN索引掃描,最後是SKIP跳過數據進行過濾。
在executionStats每個項都有nReturned 與 executionTimeMillisEstimate,這樣咱們能夠由內向外查看整個查詢執行狀況,在哪一步出現執行慢的問題。
首先數據庫索引並非越多越好,在MongoDB單文檔索引上限,集合中索引不能超過64個,一些知名大廠推薦不超過10個。
而在一個主表中,因爲冗餘文檔設計,就會存在很是多信息須要增長索引,咱們仍是以社交登陸爲例子
schema = { … qq:{ openId:String }, wxapp:{ openId:String, }, weibo:{ openId:String, } … } // 每次增長新的登陸類型,須要修改文檔schema和增長索引 db.user.createIndex({qq.openId:1}) db.user.createIndex({wxapp.openId:1}) db.user.createIndex({weibo.openId:1})
schema = { … snsPlatforms:[{ platform:String, // 登陸平臺 openId:String, // 登陸惟一標識符 }] } // 此時不管是新增登陸平臺仍是刪除,都不須要變動索引設計,一個索引解決全部同類型問題 db.user.createIndex({snsPlatforms.openId:1,snsPlatforms.platform:1})
提問:爲何openId要放在plaform前面呢?
這個小故事講述了小李在遇到自身知識不能解決的問題,而後事情的處理思路與過程。每一個人都有本身能力所不及的地方,那麼這種狀況要優先解決問題,或者下降事故的影響範圍。