大型数据库中MongoDB计数非常慢

3 投票
1 回答
1252 浏览
提问于 2025-04-18 09:16

我有一个数据库,里面有很多文档(有好几百万个)。在这个数据库里,有一些字段,比如_VIOLATIONTYPE(整数类型)和_DURATION(整数类型)。现在我想统计一下,_VIOLATIONTYPE小于或等于15,并且_DURATION小于或等于10的文档数量。为此,我执行了以下Python脚本:

#!/usr/bin/env python
import pymongo
import timeit


client = pymongo.MongoClient('localhost', 27017)
database = client['bgp_route_leaks']

collection = database['valleys']

collection.ensure_index('_VIOLATIONTYPE', unique=False)
collection.ensure_index('_DURATION', unique=False)

start = timeit.default_timer()

cursor = collection.find({'$and': [{'_VIOLATIONTYPE': {'$lt': 16}}, {'_DURATION': {'$lt': 10}}]}, {'_DURATION': 1, '_id': 0})

print('Explain: {}'.format(cursor.explain()))
print('Count: {}'.format(cursor.count()))
print('Time: {}'.format(timeit.default_timer() - start))

这个脚本的输出结果是:

Explain: {u'nYields': 4, u'nscannedAllPlans': 6244545, u'allPlans': [{u'cursor': u'BtreeCursor _VIOLATIONTYPE_1', u'indexBounds': {u'_VIOLATIONTYPE': [[-1.7976931348623157e+308, 16]]}, u'nscannedObjects': 124, u'nscanned': 124, u'n': 34}, {u'cursor': u'BtreeCursor _DURATION_1', u'indexBounds': {u'_DURATION': [[-1.7976931348623157e+308, 10]]}, u'nscannedObjects': 6244298, u'nscanned': 6244298, u'n': 5678070}, {u'cursor': u'BasicCursor', u'indexBounds': {}, u'nscannedObjects': 123, u'nscanned': 123, u'n': 36}], u'millis': 30815, u'nChunkSkips': 0, u'server': u'area51:27017', u'n': 5678107, u'cursor': u'BtreeCursor _DURATION_1', u'scanAndOrder': False, u'indexBounds': {u'_DURATION': [[-1.7976931348623157e+308, 10]]}, u'nscannedObjectsAllPlans': 6244545, u'isMultiKey': False, u'indexOnly': True, u'nscanned': 6244298, u'nscannedObjects': 6244298}
Count: 5678107
Time: 52.4030768871

在运行这个脚本的时候,我在另一个窗口执行了db.currentOp(),返回了:

{
        "inprog" : [
                {
                        "opid" : 15,
                        "active" : true,
                        "secs_running" : 4,
                        "op" : "query",
                        "ns" : "bgp_route_leaks.valleys",
                        "query" : {
                                "$query" : {
                                        "$and" : [
                                                {
                                                        "_VIOLATIONTYPE" : {
                                                                "$lt" : 16
                                                        }
                                                },
                                                {
                                                        "_DURATION" : {
                                                                "$lt" : 10
                                                        }
                                                }
                                        ]
                                },
                                "$explain" : true
                        },
                        "client" : "127.0.0.1:46819",
                        "desc" : "conn1",
                        "threadId" : "0x7fd69b31c700",
                        "connectionId" : 1,
                        "locks" : {
                                "^" : "r",
                                "^bgp_route_leaks" : "R"
                        },
                        "waitingForLock" : false,
                        "numYields" : 5,
                        "lockStats" : {
                                "timeLockedMicros" : {
                                        "r" : NumberLong(8816104),
                                        "w" : NumberLong(0)
                                },
                                "timeAcquiringMicros" : {
                                        "r" : NumberLong(4408723),
                                        "w" : NumberLong(0)
                                }
                        }
                }
        ]
}

我了解到,MongoDB查询速度慢的常见原因是缺少索引。不过,我已经为_VIOLATIONTYPE和_DURATION确保了索引,并且解释结果显示u'indexOnly': True。我还读到,NUMA架构可能会导致速度变慢,所以我应该通过以下命令启动服务:

sudo numactl --interleave=all /usr/bin/mongod --dbpath=/var/lib/mongodb
(/proc/sys/vm/zone_reclaim_mode is already set to 0)

我知道我已经这样做了,但统计这个数量仍然需要大约一分钟,其他的查询甚至更久,所以我在想有什么办法可以让查询更快。

我也尝试在mongo shell中运行:

db.runCommand({compact: 'bgp_route_leaks'})

但没有成功。

有没有什么建议可以让统计速度快一些?

我的MongoDB版本是2.4.9。

1 个回答

1

如果你查看你的 explain 输出,你会发现使用 _VIOLATIONTYPE 的查询只扫描了124个对象,而使用 _DURATION 的查询则扫描了6244545个对象。

虽然MongoDB 2.6及以上版本可以使用索引交叉,但复合索引总是会更快。

你需要在这些字段上创建一个复合索引:

collection.create_index([("_VIOLATIONTYPE", ASCENDING),("_DURATION", ASCENDING)]);

编辑

在2.4版本中,MongoDB的性能得到了显著提升(JIRA-1752)。

另外,值得注意的是,explain 命令显示的是查询的详细信息,而不是计数命令的。

不幸的是,你不能在 count 命令上使用 explain,不过有一个问题单正在处理这个问题。

为了测量 count 命令的性能,你可能需要从测试中去掉 explain。此外,你还需要多次重复查询(比如100次、1000次……),然后取平均值来得到一个准确的结果。

撰写回答