多键索引上的慢速范围查询

Posted

技术标签:

【中文标题】多键索引上的慢速范围查询【英文标题】:Slow range query on a multikey index 【发布时间】:2013-05-03 20:26:50 【问题描述】:

我有一个名为 post 的 MongoDB 集合,其中包含 3500 万 个对象。该集合有两个二级索引,定义如下。

> db.post.getIndexKeys()
[
    
        "_id" : 1
    ,
    
        "namespace" : 1,
        "domain" : 1,
        "post_id" : 1
    ,
    
        "namespace" : 1,
        "post_time" : 1,
        "tags" : 1  // this is an array field
    
]

我希望以下查询(仅按 namespacepost_time 过滤)在合理的时间内运行而不扫描所有对象。

>db.post.find(post_time: "$gte" : ISODate("2013-04-09T00:00:00Z"), "$lt" : ISODate("2013-04-09T01:00:00Z"), namespace: "my_namespace").count()
7408

然而,MongoDB 至少需要十分钟来检索结果,而且奇怪的是,它设法扫描 7000 万 个对象以根据 explain 函数完成这项工作。

> db.post.find(post_time: "$gte" : ISODate("2013-04-09T00:00:00Z"), "$lt" : ISODate("2013-04-09T01:00:00Z"), namespace: "my_namespace").explain()

    "cursor" : "BtreeCursor namespace_1_post_time_1_tags_1",
    "isMultiKey" : true,
    "n" : 7408,
    "nscannedObjects" : 69999186,
    "nscanned" : 69999186,
    "nscannedObjectsAllPlans" : 69999186,
    "nscannedAllPlans" : 69999186,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 378967,
    "nChunkSkips" : 0,
    "millis" : 290048,
    "indexBounds" : 
        "namespace" : [
            [
                "my_namespace",
                "my_namespace"
            ]
        ],
        "post_time" : [
            [
                ISODate("2013-04-09T00:00:00Z"),
                ISODate("292278995-01--2147483647T07:12:56.808Z")
            ]
        ],
        "tags" : [
            [
                
                    "$minElement" : 1
                ,
                
                    "$maxElement" : 1
                
            ]
        ]
    ,
    "server" : "localhost:27017"

物体个数和扫描个数的差异肯定是标签数组的长度(都等于2)造成的。不过,我不明白为什么post_time 过滤器不使用索引。

你能告诉我我可能缺少什么吗?

(我正在使用 24 核和 96 GB RAM 的下降机器。我使用的是 MongoDB 2.2.3。)

【问题讨论】:

命名空间的基数很低吗? 目前,只有一个不同的namespace 值,这是我正在使用的。 是的,这就是为什么,MongoDB 必须首先限制第一个字段,所以它会获取所有 my_namespace,然后获取该日期之间的所有文档,等等,尝试重新排序索引,以便 post_time 是第一个 索引树是否在my_namespace 分支下也包含post_time 值?为什么在缩小范围之前开始扫描? 即使 btree 包含两个字段,复合索引在 MongoDB 中以某种方式工作,这意味着它将扫描所有命名空间以查找该值,然后缩小日期范围。嗯,试图找到一个解释得很好的文档页面,但是在谷歌搜索的索引内部没有真正好的文档页面,尽管这篇文章可能会有所帮助:emptysquare.net/blog/optimizing-mongodb-compound-indexes 【参考方案1】:

在这个问题中找到了我的答案:Order of $lt and $gt in MongoDB range query

我的索引是一个多键索引(tags),我正在运行一个范围查询(post_time)。 Apparently,在这种情况下,MongoDB 不能使用范围的两边作为过滤器,所以它只选择 $gte 子句,它是第一个出现的。由于我的下限恰好是最低的post_time 值,MongoDB 开始扫描所有对象。

不幸的是,这并不是故事的全部。为了解决这个问题,我也创建了非多键索引,但 MongoDB 坚持使用坏的索引。这让我觉得问题出在其他地方。最后,我不得不删除多键索引并创建一个没有tags 字段的索引。现在一切都很好。

【讨论】:

该死,我从来不知道 $gt$lt 和 miltikeys,很好的发现! 使用 cursor.hint 也可以是让 mongodb 使用其他索引的解决方案 (docs.mongodb.org/manual/reference/method/cursor.hint/…)

以上是关于多键索引上的慢速范围查询的主要内容,如果未能解决你的问题,请参考以下文章

即使正在使用索引,MySQL 也会在极少数情况下进行慢速查询

在 WHERE 子句中使用 OR 的慢速 JOIN 查询 - 缺少可能的索引?

Sybase 上的慢速 SQL 查询

了解子文档数组的索引

MongoDB高效查询之索引机制

具有许多索引的表的慢速批量插入