mongodb性能調優初探

> show dbs
admin  0.000GB
local  0.000GB
yang   0.615GB
> use yang
switched to db yang
> show collections
system.profile
users
> db.users.find().count()
10000000

這里選取一千萬條數據來說明問題,這些數據都是沒有索引的,首先我們要做的是找出慢查詢。
1.開啟慢查詢分析器
db.setProfilingLevel(1,300)
第一個參數“1”表示記錄慢查詢,還可以選擇“0”或者“2”,分別代表不記錄數據和記錄所有讀寫操作,我們一般會設置成“1”,第二個參數代表慢查詢閾值,只有查詢執行時間超過300ms才會記錄。
2.查詢所記錄的慢查詢
監控結果保存在一個特殊的蓋子集合system.profile里,這個集合分配了128kb的空間,要確保監控分析數據不會消耗太多的系統性資源;蓋子集合維護了自然的插入順序,可以使用$natural操作符進行排序,
> db.system.profile.find().sort({'$natural':-1}).limit(5).pretty()會打印出最耗時的5條查詢,在這里我們選取其中的一條來看:

{
        "op" : "command",
        "ns" : "yang.users",
        "command" : {
                "explain" : {
                        "find" : "users",        //這里是所查詢的集合
                       //重點在這里,查詢條件是 "username" = "dede"
                        "filter" : {
                                "username" : "dede"
                        }
                },
                "verbosity" : "allPlansExecution"
        },
        "numYield" : 78375,
        "locks" : {
                "Global" : {
                        "acquireCount" : {
                                "r" : NumberLong(156752)
                        }
                },
                "Database" : {
                        "acquireCount" : {
                                "r" : NumberLong(78376)
                        }
                },
                "Collection" : {
                        "acquireCount" : {
                                "r" : NumberLong(78376)
                        }
                }
        },
        "responseLength" : 848,
        "protocol" : "op_command",
        "millis" : 6800,
        "ts" : ISODate("2018-02-05T10:35:07.576Z"),
        "client" : "127.0.0.1",
        "appName" : "MongoDB Shell",
        "allUsers" : [ ],
        "user" : ""
}

3.上面的還不夠直觀,我們現在構造這個查詢并用explain來分析具體慢在了那里:
> db.users.find({"username":"dede"}).explain('executionStats')
explain的入參可選值為:
?"queryPlanner" 是默認值,表示僅僅展示執行計劃信息;
?"executionStats" 表示展示執行計劃信息同時展示被選中的執行計劃的執行情況信息;
? "allPlansExecution" 表示展示執行計劃信息,并展示被選中的執行計劃的執行情況信息,還展示備選的執行計劃的執行情況信息;
我們一般會選用executionStats,打印出來的內容如下:

> db.users.find({"username":"dede"}).explain('executionStats')
{
        "queryPlanner" : {
                "plannerVersion" : 1,
                "namespace" : "yang.users",
                "indexFilterSet" : false,
                "parsedQuery" : {
                        "username" : {
                                "$eq" : "dede"
                        }
                },
         //執行計劃會有多種,這里列出的是勝出的執行計劃
                "winningPlan" : {
         //重點看下面的stage,collscan代表全表掃描,如果命中索引這里應該是ixscan,index scan
                        "stage" : "COLLSCAN",
                        "filter" : {
                                "username" : {
                                        "$eq" : "dede"
                                }
                        },
                        "direction" : "forward"
                },
         //下面列舉的是沒有使用的執行計劃
                "rejectedPlans" : [ ]
        },
        "executionStats" : {
                "executionSuccess" : true,
                "nReturned" : 1428802,
                "executionTimeMillis" : 7080,
                "totalKeysExamined" : 0,
                "totalDocsExamined" : 10000000,
                "executionStages" : {
                        "stage" : "COLLSCAN",
                        "filter" : {
                                "username" : {
                                        "$eq" : "dede"
                                }
                        },
                        "nReturned" : 1428802,//查詢所返回的條數
                        "executionTimeMillisEstimate" : 6633,//執行總時間
                        "works" : 10000002,
                        "advanced" : 1428802,
                        "needTime" : 8571199,
                        "needYield" : 0,
                        "saveState" : 78368,
                        "restoreState" : 78368,
                        "isEOF" : 1,
                        "invalidates" : 0,
                        "direction" : "forward",
                        "docsExamined" : 10000000//總共掃描了一千萬條數據
                }
        },
        "serverInfo" : {
                "host" : "localhost.localdomain",
                "port" : 27022,
                "version" : "3.4.10",
                "gitVersion" : "078f28920cb24de0dd479b5ea6c66c644f6326e9"
        },
        "ok" : 1  
}

講上面的結果簡化如下:

queryPlanner(執行計劃描述)
        winningPlan(被選中的執行計劃) 
                stage(可選項:COLLSCAN 沒有走索引;IXSCAN使用了索引) 
        rejectedPlans(候選的執行計劃) 
 executionStats(執行情況描述)
        nReturned (返回的文檔個數) 
        executionTimeMillis(執行時間ms) 
        totalKeysExamined (檢查的索引鍵值個數) 
        totalDocsExamined (檢查的文檔個數)

我們最終的優化目標

1.根據需求建立索引
2.每個查詢都要使用索引以提高查詢效率, winningPlan. stage 必須為IXSCAN ;
3.追求totalDocsExamined = nReturned
屬性名 類型 說明
background boolean 是否后臺構建索引,在生產環境中,如果數據量太大,構建索引可能會消耗很長時間,為了不影響業務,可以加上此參數,后臺運行同時還會為其他讀寫操作讓路
unique boolean 是否為唯一索引
name string 索引名字
sparse boolean 是否為稀疏索引,索引僅引用具有指定字段的文檔。

那么mongodb的索引又是怎么建立的呢?
首先,mongodb的索引分為單建索引db.users. createIndex({age:-1}); 、復合索引db.users. createIndex({username:1,age:-1,country:1});、多鍵索引(在數組的屬性上建立索引)db.users. createIndex({favorites.city:1}),
創建索引的語法如下:
db.collection.createIndex(keys, options)
?語法中 Key 值為要創建的索引字段,1為指定按升序創建索引,如果你想按降序來創建索引指定為-1,也可以指定為hashed(哈希索引)。
?語法中options為索引的屬性,屬性說明見下表;

屬性名 類型 說明
background boolean 是否后臺構建索引,在生產環境中,如果數據量太大,構建索引可能會消耗很長時間,為了不影響業務,可以加上此參數,后臺運行同時還會為其他讀寫操作讓路
unique boolean 是否為唯一索引
name string 索引名字
sparse boolean 是否為稀疏索引,索引僅引用具有指定字段的文檔。

舉例如下:
?單鍵唯一索引:db.users. createIndex({username :1},{unique:true});
?單鍵唯一稀疏索引:db.users. createIndex({username :1},{unique:true,sparse:true});
?復合唯一稀疏索引:db.users. createIndex({username:1,age:-1},{unique:true,sparse:true});
?創建哈希索引并后臺運行:db.users. createIndex({username :'hashed'},{background:true});
?刪除索引
?根據索引名字刪除某一個指定索引:db.users.dropIndex("username_1");
?刪除某集合上所有索引:db.users.dropIndexs();
?重建某集合上所有索引:db.users.reIndex();
?查詢集合上所有索引:db.users.getIndexes();
既然已經知道了如何創建索引,那么我們就給users表的username字段創建索引
> db.users.createIndex({"username":1},{"name":"username_1","sparse":true,"background":true});
看看有沒有創建成功

> db.users.getIndexes()
[
        {
                "v" : 2,
                "key" : {
                        "_id" : 1
                },
                "name" : "_id_",
                "ns" : "yang.users"
        },
        {
                "v" : 2,
                "key" : {
                        "username" : 1
                },
                "name" : "username_1",
                "ns" : "yang.users",
                "sparse" : true,
                "background" : true
        }
]

可以看到索引已經創建成功,索引得名字就是username_1,我們再查看一下執行計劃

> db.users.find({"username":"dede"}).explain("executionStats")
{
        "queryPlanner" : {
                "plannerVersion" : 1,
                "namespace" : "yang.users",
                "indexFilterSet" : false,
                "parsedQuery" : {
                        "username" : {
                                "$eq" : "dede"
                        }
                },
                "winningPlan" : {
                        "stage" : "FETCH",
                        "inputStage" : {
                                "stage" : "IXSCAN",
                                "keyPattern" : {
                                        "username" : 1
                                },
                                "indexName" : "username_1",
                                "isMultiKey" : false,
                                "multiKeyPaths" : {
                                        "username" : [ ]
                                },
                                "isUnique" : false,
                                "isSparse" : true,
                                "isPartial" : false,
                                "indexVersion" : 2,
                                "direction" : "forward",
                                "indexBounds" : {
                                        "username" : [
                                                "[\"dede\", \"dede\"]"
                                        ]
                                }
                        }
                },
                "rejectedPlans" : [ ]
        },
        "executionStats" : {
                "executionSuccess" : true,
                "nReturned" : 1428802,
                "executionTimeMillis" : 4399,
                "totalKeysExamined" : 1428802,
                "totalDocsExamined" : 1428802,
                "executionStages" : {
                        "stage" : "FETCH",
                        "nReturned" : 1428802,
                        "executionTimeMillisEstimate" : 4002,
                        "works" : 1428803,
                        "advanced" : 1428802,
                        "needTime" : 0,
                        "needYield" : 0,
                        "saveState" : 11308,
                        "restoreState" : 11308,
                        "isEOF" : 1,
                        "invalidates" : 0,
                        "docsExamined" : 1428802,
                        "alreadyHasObj" : 0,
                        "inputStage" : {
                                "stage" : "IXSCAN",
                                "nReturned" : 1428802,
                                "executionTimeMillisEstimate" : 765,
                                "works" : 1428803,
                                "advanced" : 1428802,
                                "needTime" : 0,
                                "needYield" : 0,
                                "saveState" : 11308,
                                "restoreState" : 11308,
                                "isEOF" : 1,
                                "invalidates" : 0,
                                "keyPattern" : {
                                        "username" : 1
                                },
                                "indexName" : "username_1",
                                "isMultiKey" : false,
                                "multiKeyPaths" : {
                                        "username" : [ ]
                                },
                                "isUnique" : false,
                                "isSparse" : true,
                                "isPartial" : false,
                                "indexVersion" : 2,
                                "direction" : "forward",
                                "indexBounds" : {
                                        "username" : [
                                                "[\"dede\", \"dede\"]"
                                        ]
                                },
                                "keysExamined" : 1428802,
                                "seeks" : 1,
                                "dupsTested" : 0,
                                "dupsDropped" : 0,
                                "seenInvalidated" : 0
                        }
                }
        },
        "serverInfo" : {
                "host" : "localhost.localdomain",
                "port" : 27022,
                "version" : "3.4.10",
                "gitVersion" : "078f28920cb24de0dd479b5ea6c66c644f6326e9"
        },
        "ok" : 1
}

我們可以看到,查詢時間從原來的6s變成了765ms,并且執行計劃中的stage:IXSCAN,再看看"docsExamined" : 1428802,"nReturned" : 1428802,,兩者相等,至此我們完成了mongodb的初步調優,這里只是列舉的非常簡單的場景,具體的調優還是要看各位的實際情況去分析。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,868評論 18 139
  • 學習《MongoDB 權威指南·第2版》的筆記,結合 MongoDB 官方最新文檔(v3.6),簡單記錄一些概念、...
    小魚愛小蝦閱讀 6,083評論 0 5
  • 目錄 查詢操作 集合查詢方法 find() 查詢內嵌文檔 查詢操作符(內含 數組查詢) "$gt" 、"$gte"...
    彩虹之夢閱讀 1,043評論 0 1
  • 睡醒找何必帶我過了突變,看完了ig的比賽后安利大哥來打合作模式,把沃拉尊升到5級了。菜如我們也是困難隨便擼了!晚飯...
    AJI米閱讀 126評論 0 0
  • 歷經人生滄桑時,或許有種失落感,或許感到形單影只,這時,總會有一種朋友,無須形影相隨,無須感天動地,無須多言,便心...
    蔣書丹閱讀 197評論 0 0