避免mongo shell的查询扫表问题-阿里云开发者社区

开发者社区> 数据库> 正文
登录阅读全文

避免mongo shell的查询扫表问题

简介: 主要是解决防止人为误操作导致mongo shell查询的时候发生扫描表的事件,导致雪崩问题

如何避免mongo shell 查询扫描文档

问题来源

Mongodb数据库的查询基于索引的匹配,当一个查询事件没有匹配索引的时候就会扫描整个collection的所有文档,导致效率非常低下,如下:

测试使用的一个用户表,5161条数据,可以看到有Pid的索引,没有login_account的索引

>db.persons.getIndexes()
[
{
    "v" : 1,
    "key" : {
        "_id" : 1
    },
    "name" : "_id_",
    "ns" : "roster.persons"
},
{
    "v" : 1,
    "unique" : true,
    "key" : {
        "did" : 1,
        "pid" : 1
    },
    "name" : "did_1_pid_1",
    "ns" : "roster.persons",
    "background" : true
},
{
    "v" : 1,
    "key" : {
        "pid" : 1
    },
    "name" : "pid_1",
    "ns" : "roster.persons",
    "background" : true
}
]

我们这里用匹配索引的字段和不匹配索引的字段做查询,并执行查询分析,限于篇幅我删掉了其他跟本文无关的输出,也不过多的讲解mongdb的查询分析:

>db.persons.find({pid:1774},{did:1,pid:1,name:1,login_account:1})
{ "id" : ObjectId("55e6c83583cdaea2c239db8b"), "pid" : NumberLong(1774), "name" : "llh安卓马甲", "login_account" : "23728632463", "did" : NumberLong(10000) }
>db.persons.find({login_account:"23728632463"},{did:1,pid:1,name:1,login_account:1})
{ "id" : ObjectId("55e6c83583cdaea2c239db8b"), "pid" : NumberLong(1774), "name" : "llh安卓马甲", "login_account" : "23728632463", "did" : NumberLong(10000) }
>db.persons.find({login_account:23728632463},{did:1,pid:1,name:1,login_account:1}).explain('allPlansExecution')
{

..........

"executionStats" : {
    "executionSuccess" : true,
    "nReturned" : 0,
    "executionTimeMillis" : 8,
    "totalKeysExamined" : 0,
    "totalDocsExamined" : 5161,

    ...........

},

...............

}
>db.persons.find({pid:1774},{did:1,pid:1,name:1,login_account:1}).explain('allPlansExecution')
{
................
"executionStats" : {
    "executionSuccess" : true,
    "nReturned" : 1,
    "executionTimeMillis" : 0,
    "totalKeysExamined" : 1,
    "totalDocsExamined" : 1,
    ..............
},
..............
}

可以明显看出匹配索引的查询很快处理,只需要读一个文档,但是没有匹配索引的会扫描整个表得所有文档,虽然这里看到时间相差不大,但当一个表达到上亿的时候,一次非匹配索引的查询会让整个数据库卡死,线上数据库做一些异常数据查询的操作时候,还可能导致业务雪崩。可能会说都匹配索引的时候就好了?但是谁能保证每次都敲的命令没有问题呢,比如上面查询pid的时候,我敲错了一个字母变成了aid:

>db.persons.find({aid:1774},{did:1,pid:1,name:1,login_account:1}).explain('allPlansExecution')
{
......
"executionStats" : {
    "executionSuccess" : true,
    "nReturned" : 0,
    "executionTimeMillis" : 8,
    "totalKeysExamined" : 0,
    "totalDocsExamined" : 5161,
    .......
},
.......
}

也会变成扫描整个表,导致性能急剧下降,另外这个扫描事件不太好中断(ctrl+c是无法结束的,mongo数据库还是会继续执行这条命令,具体的结束方案另外再论)。

解决方法

mongo查询是通过一个mongo的客户端工具,这个工具是开源的,修改代码在查询的时候发现没有匹配索引的查询拒绝查询,具体的代码请先自行GitHub上面下载,下面以mongo-3.2版本的代码为例子修改。

代码路劲 mongo-3.2/srv/shell/collection.js,shell是客户端工具的代码路劲,collection.js是表的操作合集,find,findOne,count等都是通过find查询,所以我们在find函数这里加上一个对查询条件匹配索引的校验,具体代码如下:

DBCollection.prototype.__checkIndexesMatch = function(query) {

    if (query == null) { //没有查询条件的,建立迭代器,不需要校验
        return true;
    }
    
    var count = this.count(null, null);
    if (count < 1000) {// 如果表里面的数据太少,不做校验
        return true;
    }
    
    var indexes = this.getIndexes(null, null); //获取所有的索引
        var maxMatch = 0; 
        //遍历索引,看是否都是否查询条件里面是不是能匹配上索引
        for (var index in indexes) {  
            var Match = 0;
            for (var i in indexes[index].key) {
                if (query.hasOwnProperty(i)) {
                    Match++;
                } else {
                    break;
                }
            }
            if (maxMatch < Match) {
                maxMatch = Match;
            }
        }
        // 最少要匹配一个索引字段,否则不予查询,这里索引匹配度可以自行定义
        if (maxMatch > 0) {
            return true;
        }
        return false;
    }

DBCollection.prototype.find = function(query, fields, limit, skip, batchSize, options) {
    if (this.__checkIndexesMatch(query) == false) {
        throw Error("Find not match indexes ! Please use original mongo!!!");
    }

    var cursor = new DBQuery(this._mongo,
                             this._db,
                             this,
                             this._fullName,
                             this._massageObject(query),
                             fields,
                             limit,
                             skip,
                             batchSize,
                             options || this.getQueryOptions());
    
    var connObj = this.getMongo();
    var readPrefMode = connObj.getReadPrefMode();
    if (readPrefMode != null) {
        cursor.readPref(readPrefMode, connObj.getReadPrefTagSet());
    }
    
    return cursor;

};

其中__checkIndexesMatch()是新增的函数,修改好后编译成mongo客户端(具体的编译方法,本文不做描述,可以参考),替换后我们再次执行异常查询效果如下:

> db.persons.find({aid:1774},{did:1,pid:1,name:1,login_account:1})
2019-03-14T20:16:12.410+0800 E QUERY    [thread1] Error: Find not match indexes ! Please use original mongo!!! :
DBCollection.prototype.find@src/mongo/shell/collection.js:301:1
@(shell):1:1

> db.persons.find({login_account:"23728632463"},{did:1,pid:1,name:1,login_account:1})
2019-03-14T20:16:30.836+0800 E QUERY    [thread1] Error: Find not match indexes ! Please use original mongo!!! :
DBCollection.prototype.find@src/mongo/shell/collection.js:301:1
@(shell):1:1

> db.persons.find({pid:1774},{did:1,pid:1,name:1,login_account:1})
{ "_id" : ObjectId("55e6c83583cdaea2c239db8b"), "pid" : NumberLong(1774), "name" : "llh安卓马甲", "login_account" : "23728632463", "did" : NumberLong(10000) }

可以看到前俩个不匹配索引导致低效的查询会被拒绝,从而达到线上数据库查询维护的时候不会因为敲错命令导致整个服务器雪崩。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
数据库
使用钉钉扫一扫加入圈子
+ 订阅

分享数据库前沿,解构实战干货,推动数据库技术变革

其他文章