【问题标题】:MongoDB slow queriesMongoDB慢查询
【发布时间】:2014-09-26 09:41:31
【问题描述】:

我有一个非常简单的 mongo 查询,应该使用 _id 索引。 解释计划看起来不错:

> db.items.find({ deleted_at: null, _id: ObjectId('541fd8016d792e0804820100') }).sort({ positions: 1 }).explain()
{
    "cursor" : "BtreeCursor _id_",
    "isMultiKey" : false,
    "n" : 1,
    "nscannedObjects" : 1,
    "nscanned" : 1,
    "nscannedObjectsAllPlans" : 6,
    "nscannedAllPlans" : 7,
    "scanAndOrder" : true,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 0,
    "indexBounds" : {
        "_id" : [
            [
                ObjectId("541fd8016d792e0804820100"),
                ObjectId("541fd8016d792e0804820100")
            ]
        ]
    },
    "server" : "mydbserver:27017",
    "filterSet" : false
}

但是当我执行查询时,它会在 100-800 毫秒内执行:

> db.items.find({ deleted_at: null, _id: ObjectId('541fd8016d792e0804820100') }).sort({ positions: 1 })
2014-09-26T12:34:00.279+0300 [conn38926] query mydb.items query: { query: { deleted_at: null, _id: ObjectId('541fd8016d792e0804820100') }, orderby: { positions: 1.0 } } planSummary: IXSCAN { positions: 1 } ntoreturn:0 ntoskip:0 nscanned:70043 nscannedObjects:70043 keyUpdates:0 numYields:1 locks(micros) r:1391012 nreturned:1 reslen:814 761ms

为什么它报告nscanned:70043 nscannedObjects:70043 以及为什么这么慢?

我在 CentOS 6 上使用 MongoDB 2.6.4。

我尝试修复 MongoDB,完全转储/导入,没有帮助。

更新 1

> db.items.find({deleted_at:null}).count()
67327
> db.items.find().count()
70043

我在deleted_at 上没有索引,但我在_id 上有索引。

更新 2(2014-09-26 14:57 EET)

_id, deleted_at 上添加索引没有帮助,即使explain 也不使用该索引:(

> db.items.ensureIndex({ _id: 1, deleted_at: 1 }, { unique: true })
> db.items.getIndexes()
[
    {
        "v" : 1,
        "key" : {
            "_id" : 1
        },
        "name" : "_id_",
        "ns" : "mydb.items"
    },
    {
        "v" : 1,
        "unique" : true,
        "key" : {
            "_id" : 1,
            "deleted_at" : 1
        },
        "name" : "_id_1_deleted_at_1",
        "ns" : "mydb.items"
    }
]
> db.items.find({ deleted_at: null, _id: ObjectId('541fd8016d792e0804820100') }).sort({ positions: 1 }).explain()
{
    "cursor" : "BtreeCursor _id_",
    "isMultiKey" : false,
    "n" : 1,
    "nscannedObjects" : 1,
    "nscanned" : 1,
    "nscannedObjectsAllPlans" : 7,
    "nscannedAllPlans" : 8,
    "scanAndOrder" : true,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 0,
    "indexBounds" : {
        "_id" : [
            [
                ObjectId("541fd8016d792e0804820100"),
                ObjectId("541fd8016d792e0804820100")
            ]
        ]
    },
    "server" : "myserver:27017",
    "filterSet" : false
}

更新 3(2014-09-26 15:03:32 EET)

_id, deleted_at, positions 上添加索引有帮助。但是,以前的案例强制进行完整的收集扫描似乎仍然很奇怪。

> db.items.ensureIndex({ _id: 1, deleted_at: 1, positions: 1 })
> db.items.find({ deleted_at: null, _id: ObjectId('541fd8016d792e0804820100') }).sort({ positions: 1 }).explain()
{
    "cursor" : "BtreeCursor _id_1_deleted_at_1_positions_1",
    "isMultiKey" : false,
    "n" : 1,
    "nscannedObjects" : 1,
    "nscanned" : 1,
    "nscannedObjectsAllPlans" : 3,
    "nscannedAllPlans" : 3,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 0,
    "indexBounds" : {
        "_id" : [
            [
                ObjectId("541fd8016d792e0804820100"),
                ObjectId("541fd8016d792e0804820100")
            ]
        ],
        "deleted_at" : [
            [
                null,
                null
            ]
        ],
        "positions" : [
            [
                {
                    "$minElement" : 1
                },
                {
                    "$maxElement" : 1
                }
            ]
        ]
    },
    "server" : "myserver:27017",
    "filterSet" : false
}

【问题讨论】:

  • 您是否在字段deleted_at 上建立了索引?您可以在db.item.find({deleted_at:null}).count() 上进行测试以与70043 进行比较。
  • 我用计数更新了我的问题
  • 为什么要使用排序,当扫描条目的数量最多为 1 时,因为您是基于唯一的 id 进行过滤?
  • 排序部分是使用 mongoid ruby​​ gem 生成的。没有排序部分,它的工作速度很快。
  • 我有一个模型类 Item include Mongoid::Document include Mongoid::Paranoia self.default_scoping = -> { where(deleted_at: nil).asc(:positions) } end

标签: mongodb


【解决方案1】:

这看起来像一个错误。查询计划器应该选择_id 索引,而_id 索引应该是您所需要的,因为它必须立即将结果集减少到一个文档。排序应该是无关紧要的,因为它正在订购一个文档。这是一个奇怪的情况,因为您明确要求一个具有_id 匹配的文档,然后对其进行排序。您应该能够绕过 mongoid 并将排序作为一种解决方法。

.explain() 不会忽略排序。您可以像这样简单地测试它:

> for (var i = 0; i < 100; i++) { db.sort_test.insert({ "i" : i }) }
> db.sort_test.ensureIndex({ "i" : 1 })
> db.sort_test.find().sort({ "i" : 1 }).explain()

如果 MongoDB 不能使用索引进行排序,它将在内存中排序。解释输出中的字段scanAndOrder 告诉您MongoDB 是否无法使用索引对查询结果进行排序(即scanAndOrder : false 表示MongoDB 可以使用索引对查询结果进行排序)。

您能否在MongoDB SERVER project 中提交错误报告?也许工程师会说它按设计工作,但在我看来这种行为是错误的,并且在 2.6.4 中已经存在一些查询规划器陷阱。如果之前说的话我可能会漏掉,但是deleted_at : null的存在/不存在会影响问题吗?

此外,如果您确实提交了工单,请在您的问题中或作为对此答案的评论发布指向它的链接,以便其他人轻松关注。谢谢!

【讨论】:

  • 凭直觉,.explain() 应该包含.sort()。但实际上手册只是说明它做出了判断。否则,我认为您上面提供的示例无法证明.explain() 显然包含.sort()。我的理由是,根据提问者提供的结果,因为索引查询后的文档数只有一个,所以sort操作是不必要的。 scanAndOrder 应该是 false,但它是 true。为什么?我猜.explain() 只是做出判断,不管查询的最终结果如何。
【解决方案2】:

更新: 更正了我建议使用 (_id, deleted_at) 复合索引的答案。同样在 cmets 中,更清楚地说明了 explain() 可能无法反映查询计划器某些情况。

我们期望find() 将过滤结果,然后sort() 将应用于过滤后的集合。但是,根据this 文档,查询规划器将使用_id 上的索引,以及postion 上的索引(如果有)来进行此查询。现在,如果您在(_id, position) 上有一个复合索引,它应该能够使用该索引来处理查询。

要点是,如果您的查询具有covers 的索引,则可以确保查询计划器正在使用您的索引。在您的情况下,查询肯定没有被覆盖,如解释计划中的indexOnly : false 所示。

如果这是设计使然,那肯定是违反直觉的,作为wdberkely suggested,您应该提交错误报告,以便社区获得对这种特殊行为的更详细解释。

【讨论】:

  • 我尝试添加 id, deleted_at 索引,没有帮助,请参阅 更新 2
  • 它真的必须扫描整个集合而不是使用find 过滤的结果集吗?使用以前版本的 mongodb 我没有这个问题。
  • 关于你在.explain()操作期间的声明It ignores the sort() part,你能分享一些官方或可靠的证据吗?谢谢。
  • @na43251,我错误地建议 (id, deleted_at) 索引会有所帮助,请参阅我的更新答案以获得更好的解释。在特定情况下,它会扫描完整的集合而不是过滤的集合。我相信这是因为查询规划器现在也使用索引交集,无论是设计还是作为错误,它以一种违反直觉的方式使用索引。
  • @Wizard,我们观察到同一查询的 explain() 和计划摘要之间存在差异。在 explain() 中,n、nscannedObjects 和 nscanned 都是 1。但是,计划摘要显示 70043在实际查询中正在扫描对象。此外,如果您查看docs.mongodb.org/manual/reference/method/cursor.explain/…,尤其是第 2 段,我们可以得出结论,至少在这种情况下,explain() 忽略了 sort() 将如何影响索引的使用。
【解决方案3】:

我猜你有 70043 个 id 为 '541fd8016d792e0804820100' 的对象。你能简单地在那个 id 上做一个 find 并 count() 他们吗?索引并不意味着“唯一”索引——如果您有一个具有特定值的索引“存储桶”,一旦到达存储桶,它现在会在存储桶内进行扫描以查看每个 'deleted_at' 值以查看其是否'无效的'。要解决此问题,请使用 (id, deleted_at) 的复合索引。

【讨论】:

  • mongodb 中的 _id 是一种特殊的唯一索引,类似于关系数据库中的主键。不能有索引“桶”。事实上,据我所知,mongodb 中只有复合索引或 haystack 索引,没有桶。
  • 我有 1 个文档,ID 为 541fd8016d792e0804820100
  • @na43251,您能否在创建您创建的复合索引之前对db.items.find({ deleted_at: null, _id: ObjectId('541fd8016d792e0804820100') }).hint({_id:1}).sort({ positions: 1 }) 进行测试?如果还要扫描这么多文件,我认为这是一个错误!
  • 是的,hint 有帮助,我已经降级到 2.6.3,看起来问题已经消失了,但我会密切关注,也许加载后会出现这个问题。
猜你喜欢
  • 2017-01-18
  • 1970-01-01
  • 2014-06-14
  • 2019-06-25
  • 2020-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多