MongoDB - 在对远程查询进行排序和限制时未使用索引

时间:2018-02-12 04:36:29

标签: mongodb mongodb-query morphia

我正在尝试使用包含公告板数据的集合上的远程查询来获取已排序的项目列表。 “线程”文档的数据结构是:

{
    "_id" : ObjectId("5a779b47f4fa72412126526a"),
    "title" : "necessitatibus tincidunt libris assueverit",
    "content" : "Corrumpitvenenatis cubilia adipiscing sollicitudin",
    "flagged" : false,
    "locked" : false,
    "sticky" : false,
    "lastPostAt" : ISODate("2018-02-05T06:35:24.656Z"),
    "postCount" : 42,
    "user" : ObjectId("5a779b46f4fa72412126525a"),
    "category" : ObjectId("5a779b31f4fa724121265164"),
    "createdAt" : ISODate("2018-02-04T23:46:15.852Z"),
    "updatedAt" : ISODate("2018-02-05T06:35:24.656Z")
}

查询是:

db.threads.find({
    category: ObjectId('5a779b31f4fa724121265142'), 
    _id : { $gt: ObjectId('5a779b5cf4fa724121269be8') }
}).sort({ sticky: -1, lastPostAt: -1, _id: 1 }).limit(25)

我设置了以下索引来支持它:

{ category: 1, _id: 1 }
{ category: 1, _id: 1, sticky: 1, lastPostAt: 1 }
{ sticky: 1, lastPostAt: 1, _id: 1 }

尽管如此,它仍然根据执行统计数据扫描数百个文件/密钥:

{
    "executionStats" : {
    "executionSuccess" : true,
    "nReturned" : 772,
    "executionTimeMillis" : 17,
    "totalKeysExamined" : 772,
    "totalDocsExamined" : 772,
    "executionStages" : {
        "stage" : "SORT",
        "nReturned" : 772,
        "executionTimeMillisEstimate" : 0,
        "works" : 1547,
        "advanced" : 772,
        "needTime" : 774,
        "needYield" : 0,
        "saveState" : 33,
        "restoreState" : 33,
        "isEOF" : 1,
        "invalidates" : 0,
        "sortPattern" : {
            "sticky" : -1,
            "lastPostAt" : -1,
            "_id" : 1
        },
        "memUsage" : 1482601,
        "memLimit" : 33554432,
        "inputStage" : {
            "stage" : "SORT_KEY_GENERATOR",
            "nReturned" : 772,
            "executionTimeMillisEstimate" : 0,
            "works" : 774,
            "advanced" : 772,
            "needTime" : 1,
            "needYield" : 0,
            "saveState" : 33,
            "restoreState" : 33,
            "isEOF" : 1,
            "invalidates" : 0,
            "inputStage" : {
                "stage" : "FETCH",
                "nReturned" : 772,
                "executionTimeMillisEstimate" : 0,
                "works" : 773,
                "advanced" : 772,
                "needTime" : 0,
                "needYield" : 0,
                "saveState" : 33,
                "restoreState" : 33,
                "isEOF" : 1,
                "invalidates" : 0,
                "docsExamined" : 772,
                "alreadyHasObj" : 0,
                "inputStage" : {
                    "stage" : "IXSCAN",
                    "nReturned" : 772,
                    "executionTimeMillisEstimate" : 0,
                    "works" : 773,
                    "advanced" : 772,
                    "needTime" : 0,
                    "needYield" : 0,
                    "saveState" : 33,
                    "restoreState" : 33,
                    "isEOF" : 1,
                    "invalidates" : 0,
                    "keyPattern" : {
                        "category" : 1,
                        "_id" : 1,
                        "sticky" : 1,
                        "lastPostAt" : 1
                    },
                    "indexName" : "category_1__id_1_sticky_1_lastPostAt_1",
                    "isMultiKey" : false,
                    "multiKeyPaths" : {
                        "category" : [ ],
                        "_id" : [ ],
                        "sticky" : [ ],
                        "lastPostAt" : [ ]
                    },
                    "isUnique" : false,
                    "isSparse" : false,
                    "isPartial" : false,
                    "indexVersion" : 2,
                    "direction" : "forward",
                    "indexBounds" : {
                        "category" : [
                            "[ObjectId('5a779b31f4fa724121265142'), ObjectId('5a779b31f4fa724121265142')]"
                        ],
                        "_id" : [
                            "(ObjectId('5a779b5cf4fa724121269be8'), ObjectId('ffffffffffffffffffffffff')]"
                        ],
                        "sticky" : [
                            "[MinKey, MaxKey]"
                        ],
                        "lastPostAt" : [
                            "[MinKey, MaxKey]"
                        ]
                    },
                    "keysExamined" : 772,
                    "seeks" : 1,
                    "dupsTested" : 0,
                    "dupsDropped" : 0,
                    "seenInvalidated" : 0
                }
            }
        }
    }
}

当我取出排序阶段时,它只能正确扫描25个文档。无论我在sort函数中放置哪个字段,检查的键(772)都保持不变。

以下是已排序查询的完整explain()

{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "database.threads",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "$and" : [
                {
                    "category" : {
                        "$eq" : ObjectId("5a779b31f4fa724121265142")
                    }
                },
                {
                    "_id" : {
                        "$gt" : ObjectId("5a779b5cf4fa724121269be8")
                    }
                }
            ]
        },
        "winningPlan" : {
            "stage" : "SORT",
            "sortPattern" : {
                "sticky" : -1,
                "lastPostAt" : -1,
                "_id" : 1
            },
            "limitAmount" : 25,
            "inputStage" : {
                "stage" : "SORT_KEY_GENERATOR",
                "inputStage" : {
                    "stage" : "FETCH",
                    "inputStage" : {
                        "stage" : "IXSCAN",
                        "keyPattern" : {
                            "category" : 1,
                            "_id" : 1,
                            "sticky" : 1,
                            "lastPostAt" : 1
                        },
                        "indexName" : "category_1__id_1_sticky_1_lastPostAt_1",
                        "isMultiKey" : false,
                        "multiKeyPaths" : {
                            "category" : [ ],
                            "_id" : [ ],
                            "sticky" : [ ],
                            "lastPostAt" : [ ]
                        },
                        "isUnique" : false,
                        "isSparse" : false,
                        "isPartial" : false,
                        "indexVersion" : 2,
                        "direction" : "forward",
                        "indexBounds" : {
                            "category" : [
                                "[ObjectId('5a779b31f4fa724121265142'), ObjectId('5a779b31f4fa724121265142')]"
                            ],
                            "_id" : [
                                "(ObjectId('5a779b5cf4fa724121269be8'), ObjectId('ffffffffffffffffffffffff')]"
                            ],
                            "sticky" : [
                                "[MinKey, MaxKey]"
                            ],
                            "lastPostAt" : [
                                "[MinKey, MaxKey]"
                            ]
                        }
                    }
                }
            }
        },
        "rejectedPlans" : [
            {
                "stage" : "SORT",
                "sortPattern" : {
                    "sticky" : -1,
                    "lastPostAt" : -1,
                    "_id" : 1
                },
                "limitAmount" : 25,
                "inputStage" : {
                    "stage" : "SORT_KEY_GENERATOR",
                    "inputStage" : {
                        "stage" : "FETCH",
                        "filter" : {
                            "_id" : {
                                "$gt" : ObjectId("5a779b5cf4fa724121269be8")
                            }
                        },
                        "inputStage" : {
                            "stage" : "IXSCAN",
                            "keyPattern" : {
                                "category" : 1
                            },
                            "indexName" : "category_1",
                            "isMultiKey" : false,
                            "multiKeyPaths" : {
                                "category" : [ ]
                            },
                            "isUnique" : false,
                            "isSparse" : false,
                            "isPartial" : false,
                            "indexVersion" : 2,
                            "direction" : "forward",
                            "indexBounds" : {
                                "category" : [
                                    "[ObjectId('5a779b31f4fa724121265142'), ObjectId('5a779b31f4fa724121265142')]"
                                ]
                            }
                        }
                    }
                }
            },
            {
                "stage" : "SORT",
                "sortPattern" : {
                    "sticky" : -1,
                    "lastPostAt" : -1,
                    "_id" : 1
                },
                "limitAmount" : 25,
                "inputStage" : {
                    "stage" : "SORT_KEY_GENERATOR",
                    "inputStage" : {
                        "stage" : "FETCH",
                        "inputStage" : {
                            "stage" : "IXSCAN",
                            "keyPattern" : {
                                "category" : 1,
                                "_id" : 1
                            },
                            "indexName" : "category_1__id_1",
                            "isMultiKey" : false,
                            "multiKeyPaths" : {
                                "category" : [ ],
                                "_id" : [ ]
                            },
                            "isUnique" : false,
                            "isSparse" : false,
                            "isPartial" : false,
                            "indexVersion" : 2,
                            "direction" : "forward",
                            "indexBounds" : {
                                "category" : [
                                    "[ObjectId('5a779b31f4fa724121265142'), ObjectId('5a779b31f4fa724121265142')]"
                                ],
                                "_id" : [
                                    "(ObjectId('5a779b5cf4fa724121269be8'), ObjectId('ffffffffffffffffffffffff')]"
                                ]
                            }
                        }
                    }
                }
            },
            {
                "stage" : "SORT",
                "sortPattern" : {
                    "sticky" : -1,
                    "lastPostAt" : -1,
                    "_id" : 1
                },
                "limitAmount" : 25,
                "inputStage" : {
                    "stage" : "SORT_KEY_GENERATOR",
                    "inputStage" : {
                        "stage" : "FETCH",
                        "filter" : {
                            "category" : {
                                "$eq" : ObjectId("5a779b31f4fa724121265142")
                            }
                        },
                        "inputStage" : {
                            "stage" : "IXSCAN",
                            "keyPattern" : {
                                "_id" : 1
                            },
                            "indexName" : "_id_",
                            "isMultiKey" : false,
                            "multiKeyPaths" : {
                                "_id" : [ ]
                            },
                            "isUnique" : true,
                            "isSparse" : false,
                            "isPartial" : false,
                            "indexVersion" : 2,
                            "direction" : "forward",
                            "indexBounds" : {
                                "_id" : [
                                    "(ObjectId('5a779b5cf4fa724121269be8'), ObjectId('ffffffffffffffffffffffff')]"
                                ]
                            }
                        }
                    }
                }
            }
        ]
    },
    "serverInfo" : {
        "host" : "CRF-MBP.local",
        "port" : 27017,
        "version" : "3.6.2",
        "gitVersion" : "489d177dbd0f0420a8ca04d39fd78d0a2c539420"
    },
    "ok" : 1
}

以下是非排序查询的完整explain()

{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "database.threads",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "$and" : [
                {
                    "category" : {
                        "$eq" : ObjectId("5a779b31f4fa724121265142")
                    }
                },
                {
                    "_id" : {
                        "$gt" : ObjectId("5a779b5cf4fa724121269be8")
                    }
                }
            ]
        },
        "winningPlan" : {
            "stage" : "LIMIT",
            "limitAmount" : 25,
            "inputStage" : {
                "stage" : "FETCH",
                "inputStage" : {
                    "stage" : "IXSCAN",
                    "keyPattern" : {
                        "category" : 1,
                        "_id" : 1,
                        "sticky" : 1,
                        "lastPostAt" : 1
                    },
                    "indexName" : "category_1__id_1_sticky_1_lastPostAt_1",
                    "isMultiKey" : false,
                    "multiKeyPaths" : {
                        "category" : [ ],
                        "_id" : [ ],
                        "sticky" : [ ],
                        "lastPostAt" : [ ]
                    },
                    "isUnique" : false,
                    "isSparse" : false,
                    "isPartial" : false,
                    "indexVersion" : 2,
                    "direction" : "forward",
                    "indexBounds" : {
                        "category" : [
                            "[ObjectId('5a779b31f4fa724121265142'), ObjectId('5a779b31f4fa724121265142')]"
                        ],
                        "_id" : [
                            "(ObjectId('5a779b5cf4fa724121269be8'), ObjectId('ffffffffffffffffffffffff')]"
                        ],
                        "sticky" : [
                            "[MinKey, MaxKey]"
                        ],
                        "lastPostAt" : [
                            "[MinKey, MaxKey]"
                        ]
                    }
                }
            }
        },
        "rejectedPlans" : [
            {
                "stage" : "LIMIT",
                "limitAmount" : 25,
                "inputStage" : {
                    "stage" : "FETCH",
                    "filter" : {
                        "_id" : {
                            "$gt" : ObjectId("5a779b5cf4fa724121269be8")
                        }
                    },
                    "inputStage" : {
                        "stage" : "IXSCAN",
                        "keyPattern" : {
                            "category" : 1
                        },
                        "indexName" : "category_1",
                        "isMultiKey" : false,
                        "multiKeyPaths" : {
                            "category" : [ ]
                        },
                        "isUnique" : false,
                        "isSparse" : false,
                        "isPartial" : false,
                        "indexVersion" : 2,
                        "direction" : "forward",
                        "indexBounds" : {
                            "category" : [
                                "[ObjectId('5a779b31f4fa724121265142'), ObjectId('5a779b31f4fa724121265142')]"
                            ]
                        }
                    }
                }
            },
            {
                "stage" : "LIMIT",
                "limitAmount" : 25,
                "inputStage" : {
                    "stage" : "FETCH",
                    "inputStage" : {
                        "stage" : "IXSCAN",
                        "keyPattern" : {
                            "category" : 1,
                            "_id" : 1
                        },
                        "indexName" : "category_1__id_1",
                        "isMultiKey" : false,
                        "multiKeyPaths" : {
                            "category" : [ ],
                            "_id" : [ ]
                        },
                        "isUnique" : false,
                        "isSparse" : false,
                        "isPartial" : false,
                        "indexVersion" : 2,
                        "direction" : "forward",
                        "indexBounds" : {
                            "category" : [
                                "[ObjectId('5a779b31f4fa724121265142'), ObjectId('5a779b31f4fa724121265142')]"
                            ],
                            "_id" : [
                                "(ObjectId('5a779b5cf4fa724121269be8'), ObjectId('ffffffffffffffffffffffff')]"
                            ]
                        }
                    }
                }
            },
            {
                "stage" : "LIMIT",
                "limitAmount" : 25,
                "inputStage" : {
                    "stage" : "FETCH",
                    "filter" : {
                        "category" : {
                            "$eq" : ObjectId("5a779b31f4fa724121265142")
                        }
                    },
                    "inputStage" : {
                        "stage" : "IXSCAN",
                        "keyPattern" : {
                            "_id" : 1
                        },
                        "indexName" : "_id_",
                        "isMultiKey" : false,
                        "multiKeyPaths" : {
                            "_id" : [ ]
                        },
                        "isUnique" : true,
                        "isSparse" : false,
                        "isPartial" : false,
                        "indexVersion" : 2,
                        "direction" : "forward",
                        "indexBounds" : {
                            "_id" : [
                                "(ObjectId('5a779b5cf4fa724121269be8'), ObjectId('ffffffffffffffffffffffff')]"
                            ]
                        }
                    }
                }
            }
        ]
    },
    "serverInfo" : {
        "host" : "CRF-MBP.local",
        "port" : 27017,
        "version" : "3.6.2",
        "gitVersion" : "489d177dbd0f0420a8ca04d39fd78d0a2c539420"
    },
    "ok" : 1
}

有没有人知道为什么这可能无法完全使用索引?

2 个答案:

答案 0 :(得分:2)

问题是您的索引实际上没有帮助排序查询。这就是扫描对象数量较多且存在SORT_KEY_GENERATOR阶段(内存中排序,限制为32MB)的原因。

另一方面,非排序查询可以使用{ category: 1, _id: 1 }{ category: 1, _id: 1, sticky: 1, lastPostAt: 1 }索引。请注意,使用其中任何一个都非常有效,因为其中一个包含另一个的前缀。有关详细信息,请参阅Prefixes

MongoDB find()查询通常只使用一个索引,因此单个compound index应该满足查询的所有参数。这将包括find()sort()的参数。

Optimizing MongoDB Compound Indexes中提供了有关如何创建索引的详细说明。让我们来看一篇文章的主要观点,其中复合索引排序应该是相等 - >排序 - >范围

您的查询"形状"是:

db.collection.find({category:..., _id: {$gt:...}})
             .sort({sticky:-1, lastPostAt:-1, _id:1})
             .limit(25)

我们看到了:

  • category:... 平等
  • sticky:-1, lastPostAt:-1, _id:1 排序
  • _id: {$gt:...} 范围

所以你需要的复合索引是:

{category:1, sticky:-1, lastPostAt:-1, _id:1}

具有上述索引的查询的explain()输出的获胜计划显示:

"winningPlan": {
      "stage": "LIMIT",
      "limitAmount": 25,
      "inputStage": {
        "stage": "FETCH",
        "inputStage": {
          "stage": "IXSCAN",
          "keyPattern": {
            "category": 1,
            "sticky": -1,
            "lastPostAt": -1,
            "_id": 1
          },
          "indexName": "category_1_sticky_-1_lastPostAt_-1__id_1",
          "isMultiKey": false,
          "multiKeyPaths": {
            "category": [ ],
            "sticky": [ ],
            "lastPostAt": [ ],
            "_id": [ ]
          },
          "isUnique": false,
          "isSparse": false,
          "isPartial": false,
          "indexVersion": 2,
          "direction": "forward",
          "indexBounds": {
            "category": [
              "[ObjectId('5a779b31f4fa724121265142'), ObjectId('5a779b31f4fa724121265142')]"
            ],
            "sticky": [
              "[MaxKey, MinKey]"
            ],
            "lastPostAt": [
              "[MaxKey, MinKey]"
            ],
            "_id": [
              "(ObjectId('5a779b5cf4fa724121269be8'), ObjectId('ffffffffffffffffffffffff')]"
            ]
          }
        }
      }
    }

请注意,获胜计划不包含SORT_KEY_GENERATOR阶段。这意味着可以充分利用索引来响应已排序的查询。

答案 1 :(得分:1)

我相信有2个问题都与你的排序有关。这些问题直接来自documentations,但如果你发表评论我会帮助解释(并且可能会自己学习一些东西)

第一个也是最大的问题是你必须按照索引给出的顺序排序。来自docs:

  

您可以对索引的所有键或子集指定排序;   但是,排序键必须按它们出现的顺序列出   在索引中。例如,索引键模式{a:1,b:1}可以   支持对{a:1,b:1}进行排序,但不对{b:1,a:1}进行排序。

这意味着您必须按照获胜计划给出的顺序排序:category, _id, sticky, lastPostAt(或该订单的任何前缀,例如category, _id, stickycategory _id)。如果不是mongodb将识别使用您的获胜计划索引的772文档,但是必须梳理每个密钥以评估值并提供所需的排序顺序。如果您想按顺序排序,那么查询必须按顺序提供索引:

第二个问题是您必须按索引(或反方向)提供的方向排序。

  

对于使用复合索引进行排序的查询,指定的排序   cursor.sort()文档中所有键的方向必须与   索引键模式或匹配索引键模式的反转。对于   例如,索引键模式{a:1,b:-1}可以支持{   a:1,b:-1}和{a:-1,b:1}但不在{a:-1,b:-1}或{a:   1,b:1}。

由于索引都是按升序排列的,因此您必须按升序对所有索引进行排序,或者按降序排序所有索引。如果不是,我们遇到了mongo找到所有相关文档的相同问题,但必须梳理它们以提供所需的顺序。

我相信你会通过提供额外的索引来获得改进的功能:

{ sticky: -1, lastPostAt: -1, _id: 1 }

或其反过来:

{ sticky: 1, lastPostAt: 1, _id: -1 }

这会产生mongo使用你的第一个索引的情况

{ category: 1, _id: 1 }

要识别潜在的未排序文档,然后使用新索引之一(上面提供),因为它们已经被排序。然后限制将照顾给你25个文档。

我很确定这会创建一个覆盖查询(没有检查过文档的查询)。让我知道它是怎么回事,干杯!