Mongodb 为什么覆盖计数查询很慢?

Mongodb 为什么覆盖计数查询很慢?,mongodb,mongodb-query,Mongodb,Mongodb Query,我正在运行mongodb v3.2.12分片集群。切分键是\u id,它是md5散列。 问题是,覆盖计数查询需要花费很多时间 每个mongodb节点上使用的索引大约为5GB。所有索引的总大小为32GB,完全适合RAM,因为每个节点都有128GB的RAM 查询是:db.offer.count({“shopId”:275419,“missingSince”:null}) 使用的索引已创建为:db.offer.createIndex({shopId:1,missingFrom:1,merchantId

我正在运行mongodb v3.2.12分片集群。切分键是
\u id
,它是md5散列。 问题是,覆盖计数查询需要花费很多时间

每个mongodb节点上使用的索引大约为5GB。所有索引的总大小为32GB,完全适合RAM,因为每个节点都有128GB的RAM

查询是:
db.offer.count({“shopId”:275419,“missingSince”:null})

使用的索引已创建为:
db.offer.createIndex({shopId:1,missingFrom:1,merchantId:1,{u id:1},{background:true})

如您所见,索引不是稀疏的,因此索引中甚至存在空值

在查询运行时运行
db.currentOp()
表明查询使用了正确的索引,但是,它已经运行了2814秒以上:

    {
        "desc" : "conn56062",
        "threadId" : "140131556767488",
        "connectionId" : 56062,
        "client_s" : "x.x.x.x:39177",
        "active" : true,
        "opid" : "offerStoreIT02:1075309911",
        "secs_running" : 2814,
        "microsecs_running" : NumberLong("2814791918"),
        "op" : "command",
        "ns" : "offerStore.offer",
        "query" : {
                "query" : {
                        "count" : "offer",
                        "query" : {
                                "missingSince" : null,
                                "shopId" : 275419
                        }
                },
                "$readPreference" : {
                        "mode" : "primaryPreferred"
                }
        },
        "planSummary" : "IXSCAN { shopId: 1.0, missingSince: 1.0, merchantId: 1.0, _id: 1.0 }",
        "numYields" : 249244,
        "locks" : {
                "Global" : "r",
                "Database" : "r",
                "Collection" : "r"
        },
        "waitingForLock" : false,
        "lockStats" : {
                "Global" : {
                        "acquireCount" : {
                                "r" : NumberLong(498490)
                        }
                },
                "Database" : {
                        "acquireCount" : {
                                "r" : NumberLong(249245)
                        }
                },
                "Collection" : {
                        "acquireCount" : {
                                "r" : NumberLong(249245)
                        }
                }
        }
}
在内存中迭代5GB的索引从来不会花费这么多时间。在查询运行时,每个mongodb主服务器都在以75-100 MB/秒的速度不断地从磁盘读取数据。当查询未运行时,从磁盘读取的速度只有5-10 MB/秒,因此我的假设是mongodb将文档从SSD提取到内存中,以便对它们进行计数

但为什么会这样呢?索引应涵盖查询,因为索引中存在所有字段,包括shardkey,根据mongodb文档,这些字段应足以涵盖查询:

跟进:

我把这个问题分解成一个简单的、不分块的设置。我插入了以下类型的文件:

  • a) 3个文档,没有两个字段
    shopId
    missingSing
  • b) 5个带有字段
    shopId的文档:1个
    没有字段
    丢失,因为
  • c) 7个带有字段
    shopId:1
    missingSince:null
  • d) 13个带有字段
    shopId:1
    丢失的文档自:ISODate(“2017-05-22T07:52:40.831Z”)
我创建了索引
{shopId:1,missingSince:1}
。 查询
计数({“shopId”:1,“missingSince”:null})的执行计划指示
“totalDocsExamined”:12
,这意味着必须获取12个文档。这些文件必须是b)的5份文件加上c)的7份文件。所有这12个文档都应该位于索引中,索引中的shopId为1,missingSince:null
,因此满足查询要求

但是为什么mongodb仍然需要获取和检查这12个文档?

这是我的测试集:

rs1:PRIMARY> db.offer.find()
{ "_id" : 1, "v" : 1 }
{ "_id" : 2, "v" : 1 }
{ "_id" : 3, "v" : 1 }
{ "_id" : 4, "shopId" : 1, "v" : 1 }
{ "_id" : 5, "shopId" : 1, "v" : 1 }
{ "_id" : 6, "shopId" : 1, "v" : 1 }
{ "_id" : 7, "shopId" : 1, "v" : 1 }
{ "_id" : 8, "shopId" : 1, "v" : 1 }
{ "_id" : 9, "shopId" : 1, "missingSince" : null, "v" : 1 }
{ "_id" : 10, "shopId" : 1, "missingSince" : null, "v" : 1 }
{ "_id" : 11, "shopId" : 1, "missingSince" : null, "v" : 1 }
{ "_id" : 12, "shopId" : 1, "missingSince" : null, "v" : 1 }
{ "_id" : 13, "shopId" : 1, "missingSince" : null, "v" : 1 }
{ "_id" : 14, "shopId" : 1, "missingSince" : null, "v" : 1 }
{ "_id" : 15, "shopId" : 1, "missingSince" : null, "v" : 1 }
{ "_id" : 16, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 17, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 18, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 19, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 20, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 21, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 22, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 23, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 24, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 25, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 26, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 27, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 28, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
db.offer.createIndex({missingSince:1,shopId:1}, {background:true})
以下是explain()的输出:

请尝试以下索引:

如果您现有的索引
db.offer.createIndex({shopId:1,missingFrient:1,merchantId:1,{background:true})
用于另一个目的,请尝试在提供的集合中创建以下索引:

rs1:PRIMARY> db.offer.find()
{ "_id" : 1, "v" : 1 }
{ "_id" : 2, "v" : 1 }
{ "_id" : 3, "v" : 1 }
{ "_id" : 4, "shopId" : 1, "v" : 1 }
{ "_id" : 5, "shopId" : 1, "v" : 1 }
{ "_id" : 6, "shopId" : 1, "v" : 1 }
{ "_id" : 7, "shopId" : 1, "v" : 1 }
{ "_id" : 8, "shopId" : 1, "v" : 1 }
{ "_id" : 9, "shopId" : 1, "missingSince" : null, "v" : 1 }
{ "_id" : 10, "shopId" : 1, "missingSince" : null, "v" : 1 }
{ "_id" : 11, "shopId" : 1, "missingSince" : null, "v" : 1 }
{ "_id" : 12, "shopId" : 1, "missingSince" : null, "v" : 1 }
{ "_id" : 13, "shopId" : 1, "missingSince" : null, "v" : 1 }
{ "_id" : 14, "shopId" : 1, "missingSince" : null, "v" : 1 }
{ "_id" : 15, "shopId" : 1, "missingSince" : null, "v" : 1 }
{ "_id" : 16, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 17, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 18, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 19, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 20, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 21, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 22, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 23, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 24, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 25, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 26, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 27, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
{ "_id" : 28, "shopId" : 1, "missingSince" : ISODate("2017-05-22T07:52:40.831Z"), "v" : 1 }
db.offer.createIndex({missingSince:1,shopId:1}, {background:true})

这将优化查询本身,从而计算结果。

由于没有人能找到导致此问题的有效原因,我昨天打开了一份mongodb错误报告:


Mongodb工程师确认这是一个bug。不幸的是,mongodb的文档中没有提到这一点,这将节省我们很多时间追踪问题,并从一开始就部署另一个架构设计。

offer collection中有多少文档?与包含计数时相比,不包含计数的查询是否运行得更快?为什么此索引中有
\u id
字段?这基本上是多余的,因为
\u id
字段上已经有了索引。我认为只要删除这个索引,使用一个没有包含
\u id
字段的索引,您就会得到很大的提升。出于测试目的,只为您想要作为标准的字段创建一个索引,并使用
.hint()
强制选择该索引.FYI。“覆盖查询”的一般概念是,所需的所有数据都包含在所选索引中。10次中有9次,这里的大杀手希望检索所用索引字段中包含的更多数据。但是如果您只是执行
.count()
,那么这基本上否定了文档检索的效果。由于实际上没有检索到任何文档。@Astro,因此该集合中有2.5亿个文档。“查找”与“计数”一样慢。您可以尝试下面提到的索引吗?此索引的效果会差得多,因为第一个字段“missingSince”的选择性不如第二个字段“shopId”。使用这样的索引查询
missingSince:null
,将导致几乎完全的集合扫描。