高性能mongodb之應用程序跑執行計劃

執行計劃

以前發了一篇關於mongodb執行計劃的說明。利用執行計劃,咱們能夠判斷每一次sql的執行狀況和mongodb給出的執行建議。在mongo shell中跑執行計劃的命令,舉個例子:java

db.collecitonName.find({}).explain("queryPlanner")

執行計劃的模式爲三種:queryPlanner executionStats allPlansExecution。第一種不會真正跑命令自己,只有響應命令分析後的報告。上面例子的響應結果就是對 db.collecitonName.find({}) 這個查詢語句的分析。python

程序中跑執行計劃

我使用的是java, mongodb庫用的是mongodb-java-driver。mongodb-java-driver的API提供了兩種方式去跑執行計劃:sql

方式一:

MongoClient mongoClient = new MongoClient(new ServerAddress(host, port));
mongoClient.getDB("xxx").getCollection("yyy").find(quert).explain();

這是一個便捷的方式。這種方式會真正執行命令,也就是說它使用的是executionStats模式。響應結果會有執行時間、掃描記錄數等真實的執行狀況。若是你的程序想要在命令執行前作一個預判,這個API不是你想要的。mongodb

方式二:

API沒有提供queryPlanner的方式。我花了一些時間去搜索資料,發現網上沒有跑queryPlanner的需求,至少我是沒有找到相似的發問和使用例子。糾結了一下子,最終發現庫裏有這樣一個api, mongoClient.getDB("xxx").command(BasicDBObject command),支持程序傳入一個命令。最後在官方文檔裏找到了這樣一個說明:shell

explain

New in version 3.0.

The explain command provides information on the execution of the following commands: aggregate, count, distinct, group, find, findAndModify, delete, and update.

Although MongoDB provides the explain command, the preferred method for running explain is to use the db.collection.explain() and cursor.explain() helpers.

The explain command has the following syntax:

語法以下:json

{
   explain: <command>,
   verbosity: <string>
}

explain: <command>。 支持  aggregate, count, distinct, group, find, findAndModify, delete, and update等等的命令。
verbosity: <string>。支持模式"queryPlanner" 、"executionStats"  、"allPlansExecution" (Default)

跟蹤find進去,find支持的字段以下,應有盡有。segmentfault

{
   "find": <string>,
   "filter": <document>,
   "sort": <document>,
   "projection": <document>,
   "hint": <document or string>,
   "skip": <int>,
   "limit": <int>,
   "batchSize": <int>,
   "singleBatch": <bool>,
   "comment": <string>,
   "maxScan": <int>,   // Deprecated in MongoDB 4.0
   "maxTimeMS": <int>,
   "readConcern": <document>,
   "max": <document>,
   "min": <document>,
   "returnKey": <bool>,
   "showRecordId": <bool>,
   "tailable": <bool>,
   "oplogReplay": <bool>,
   "noCursorTimeout": <bool>,
   "awaitData": <bool>,
   "allowPartialResults": <bool>,
   "collation": <document>
}

經過閱讀文檔,跑queryPlanner模式的執行計劃應該是這樣的:api

//查詢某個集合,queryCondition是查詢條件。

MongoClient mongoClient = MongoUtil.getConnection(mongodb.getHost(), mongodb.getPort(), "", "", mongodb.getDb());
BasicDBObject command = new BasicDBObject();
BasicDBObject find = new BasicDBObject();
find.put("find", "集合名");
find.put("filter", queryCondition);//查詢條件,是一個BasicDBObject
command.put("explain", find);
command.put("verbosity", "queryPlanner");
CommandResult explainResult = mongoClient.getDB(mongodb.getDb()).command(command);

python程序中跑執行計劃遇到的坑

使用 pymongo庫數據結構

import json
import pymongo

if __name__ == '__main__':
    client = pymongo.MongoClient(host='127.0.0.1', port=27017)
    #指定一個db
    db = client.get_database(name='datanamexxx')

    command = {}
    explain = {}
    #要操做的集合
    explain['find'] = "collectionnamexxx"
    #查詢的條件
    explain['filter'] = {"col1":"202060056"}
    verbosity = "executionStats"
    command['explain'] = explain
    command['verbosity'] = verbosity
    print json.dumps(db.command(command=command))

以上程序是有問題的,不能達到想要的目的(一次查詢的執行狀況)。後來通過查閱mongo文檔和嘗試,明確是使用方式不正確致使的。
錯誤緣由:mongo的command要求參數是有序的,由於首參數是命令名。正如上面的find命令:app

{
   "find": <string>, #命令名
   "filter": <document>,
   "sort": <document>,
   "projection": <document>,
   "hint": <document or string>,
   "skip": <int>,
   "limit": <int>,
   "batchSize": <int>,
   "singleBatch": <bool>,
    ...

mongo驅動在處理命令時首先要知道執行哪一個命令,然而 python的dict或者的java的map再或者全部的map數據結構都是無序的。咱們須要一個記錄參數的順序,使用者須要把首參數設置在最前面。咱們來看看驅動的源碼,原理實際上是對dict封裝一層,添加一個list來記錄參數順序:

#繼承dict
class SON(dict):
    def __init__(self, data=None, **kwargs):
        #__keys就是記錄參數順序的列表
        self.__keys = []
        dict.__init__(self)
        self.update(data)
        self.update(kwargs)
    #省略...
    #打印時,按__keys的順序拼字符串,合理
    def __repr__(self):
        result = []
        for key in self.__keys:
            result.append("(%r, %r)" % (key, self[key]))
        return "SON([%s])" % ", ".join(result)

    #設置元素時,先把key按順序保存下來
    def __setitem__(self, key, value):
        if key not in self.__keys:
            self.__keys.append(key)
        dict.__setitem__(self, key, value)

    def __delitem__(self, key):
        self.__keys.remove(key)
        dict.__delitem__(self, key)

    #省略...

pymongo正確的使用方式

import json
import pymongo

if __name__ == '__main__':
    client = pymongo.MongoClient(host='127.0.0.1', port=27017)
    #指定一個db
    db = client.get_database(name='datanamexxx')
    
    #注意順序
    explainSon = SON([("find", 'collectionnamexxx'),
               ("filter", {"uid": "202060056"})])
    cmd = SON([("explain", explainSon),
               ("verbosity", "queryPlanner")])
    print json.dumps(db.command(cmd))
相關文章
相關標籤/搜索