MongoDB - count () занимает слишком много времени, несмотря на использование индекса - PullRequest
0 голосов
/ 09 марта 2020

У меня есть коллекция с 62 тыс. Документов. В той же коллекции также есть набор индексов, большинство из которых простые, с одним полем. Я наблюдаю, что следующий запрос занимает очень много времени, чтобы вернуть:

db.jobs.count({"status":"complete","$or":[{"groups":{"$exists":false}},{"groups":{"$size":0}},{"groups":{"$in":["5e65ffc2a1e6ef0007bc5fa8"]}}]})

Для этого запроса выполняются следующие строки:

{
"queryPlanner" : {
    "plannerVersion" : 1,
    "namespace" : "xxxxxx.jobs",
    "indexFilterSet" : false,
    "parsedQuery" : {
        "$and" : [
            {
                "$or" : [
                    {
                        "groups" : {
                            "$size" : 0
                        }
                    },
                    {
                        "groups" : {
                            "$eq" : "5e65ffc2a1e6ef0007bc5fa8"
                        }
                    },
                    {
                        "$nor" : [
                            {
                                "groups" : {
                                    "$exists" : true
                                }
                            }
                        ]
                    }
                ]
            },
            {
                "status" : {
                    "$eq" : "complete"
                }
            }
        ]
    },
    "winningPlan" : {
        "stage" : "FETCH",
        "filter" : {
            "$or" : [
                {
                    "groups" : {
                        "$size" : 0
                    }
                },
                {
                    "groups" : {
                        "$eq" : "5e65ffc2a1e6ef0007bc5fa8"
                    }
                },
                {
                    "$nor" : [
                        {
                            "groups" : {
                                "$exists" : true
                            }
                        }
                    ]
                }
            ]
        },
        "inputStage" : {
            "stage" : "IXSCAN",
            "keyPattern" : {
                "status" : 1,
                "groups" : 1
            },
            "indexName" : "status_1_groups_1",
            "isMultiKey" : true,
            "multiKeyPaths" : {
                "status" : [ ],
                "groups" : [
                    "groups"
                ]
            },
            "isUnique" : false,
            "isSparse" : false,
            "isPartial" : false,
            "indexVersion" : 2,
            "direction" : "forward",
            "indexBounds" : {
                "status" : [
                    "[\"complete\", \"complete\"]"
                ],
                "groups" : [
                    "[MinKey, MaxKey]"
                ]
            }
        }
    },
    "rejectedPlans" : [
        {
            "stage" : "FETCH",
            "filter" : {
                "$or" : [
                    {
                        "groups" : {
                            "$size" : 0
                        }
                    },
                    {
                        "groups" : {
                            "$eq" : "5e65ffc2a1e6ef0007bc5fa8"
                        }
                    },
                    {
                        "$nor" : [
                            {
                                "groups" : {
                                    "$exists" : true
                                }
                            }
                        ]
                    }
                ]
            },
            "inputStage" : {
                "stage" : "IXSCAN",
                "keyPattern" : {
                    "status" : 1
                },
                "indexName" : "status_1",
                "isMultiKey" : false,
                "multiKeyPaths" : {
                    "status" : [ ]
                },
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 2,
                "direction" : "forward",
                "indexBounds" : {
                    "status" : [
                        "[\"complete\", \"complete\"]"
                    ]
                }
            }
        }
    ]
},
"executionStats" : {
    "executionSuccess" : true,
    "nReturned" : 62092,
    "executionTimeMillis" : 9992,
    "totalKeysExamined" : 62092,
    "totalDocsExamined" : 62092,
    "executionStages" : {
        "stage" : "FETCH",
        "filter" : {
            "$or" : [
                {
                    "groups" : {
                        "$size" : 0
                    }
                },
                {
                    "groups" : {
                        "$eq" : "5e65ffc2a1e6ef0007bc5fa8"
                    }
                },
                {
                    "$nor" : [
                        {
                            "groups" : {
                                "$exists" : true
                            }
                        }
                    ]
                }
            ]
        },
        "nReturned" : 62092,
        "executionTimeMillisEstimate" : 9929,
        "works" : 62093,
        "advanced" : 62092,
        "needTime" : 0,
        "needYield" : 0,
        "saveState" : 682,
        "restoreState" : 682,
        "isEOF" : 1,
        "invalidates" : 0,
        "docsExamined" : 62092,
        "alreadyHasObj" : 0,
        "inputStage" : {
            "stage" : "IXSCAN",
            "nReturned" : 62092,
            "executionTimeMillisEstimate" : 60,
            "works" : 62093,
            "advanced" : 62092,
            "needTime" : 0,
            "needYield" : 0,
            "saveState" : 682,
            "restoreState" : 682,
            "isEOF" : 1,
            "invalidates" : 0,
            "keyPattern" : {
                "status" : 1,
                "groups" : 1
            },
            "indexName" : "status_1_groups_1",
            "isMultiKey" : true,
            "multiKeyPaths" : {
                "status" : [ ],
                "groups" : [
                    "groups"
                ]
            },
            "isUnique" : false,
            "isSparse" : false,
            "isPartial" : false,
            "indexVersion" : 2,
            "direction" : "forward",
            "indexBounds" : {
                "status" : [
                    "[\"complete\", \"complete\"]"
                ],
                "groups" : [
                    "[MinKey, MaxKey]"
                ]
            },
            "keysExamined" : 62092,
            "seeks" : 1,
            "dupsTested" : 62092,
            "dupsDropped" : 0,
            "seenInvalidated" : 0
        }
    }
},
"serverInfo" : {
    "host" : "xxxxxxx",
    "port" : 27017,
    "version" : "3.6.15",
    "gitVersion" : "xxxxxx"
},
"ok" : 1}

Я пытаюсь понять, почему занимает ли этап FETCH 10 секунд, когда сканирование индекса в INPUT_STAGE занимает 60 мс. Поскольку в конечном итоге я выполняю count (), мне действительно не нужна mongoDB для возврата документов, мне нужно только, чтобы он суммировал $ $ количество подходящих ключей и дал мне общий итог.

Любая идея, что Я делаю не так?

Ответы [ 3 ]

0 голосов
/ 09 марта 2020

Если вы можете как-то избежать пустого массива, то можно использовать следующий запрос: db.jobs.count({"status":"complete", "groups": { $in: [ null, "5e65ffc2a1e6ef0007bc5fa8" ] } })

null эквивалентно $exists: false.

Также: I ' Я предлагаю использовать ObjectId вместо строки в качестве типа для поля groups.

Обновление

$ размер никогда не попадет в индекс!

Вы можно использовать следующий запрос:

db.jobs.count({"status":"complete","$or":[
  {"groups":[],
  {"groups": {$in: [ null, "5e65ffc2a1e6ef0007bc5fa8" ]}
]})
0 голосов
/ 10 марта 2020

`

db.jobs.aggregate(
   .{$match: {"$or":[
   {"groups":{"$exists":false}},
   {"groups":{"$in":["5e65ffc2a1e6ef0007bc5fa8"]}},
   {"$size":0}
   ]}
   },
   .{$count:{"status":"complete"}
   )`
0 голосов
/ 09 марта 2020

Запрос объяснил, что счетчика не было, он вернул довольно много документов:

"nReturned" : 62092,

Расчетное выполнение для каждого этапа предполагает, что сканирование индекса, как ожидается, займет 60 мс, а выборка документов из Диск занял дополнительные 9,8 секунды.

Существует несколько причин, по которым это число требовало извлечения документов:

  • Существование ключа не может быть полностью определено из индекса
    {"$exists":false} Предикат также проблематичен. При построении индекса значение для документа содержит значение каждого индексированного поля. Для «несуществующего» значения нет, поэтому он использует null. Поскольку документ, содержащий поле, значение которого явно установлено на null, должен не соответствовать {"$exists":false}, исполнитель запроса должен загрузить каждый документ с диска, чтобы определить, было ли поле null или не существует. Это означает, что этап COUNTSCAN не может использоваться, что также означает, что все документов, подлежащих подсчету, должны быть загружены с диска.
  • Предикат $or не обеспечивает исключительность
    Исполнитель запроса не может заранее знать, что пункты в $or являются взаимоисключающими. Они есть в вашем запросе, но в общем случае один документ может соответствовать более чем одному предложению в $or, поэтому исполнитель запроса должен загрузить документы для обеспечения дедупликации.

Так как исключить этап выборки?

Если бы вы запрашивали только с предложением $in или только с предложением $size, вы должны найти, что счетчик получен из сканирования индекса, без необходимости для загрузки любых документов.

Если вы выполняете эти запросы отдельно от клиента и суммируете результаты, вы должны обнаружить, что общее время выполнения меньше, чем запрос, требующий выборки:

db.jobs.count({"status":"complete","groups":{"$size":0}})
db.jobs.count({"status":"complete","groups":{"$in":["5e65ffc2a1e6ef0007bc5fa8"]}})

Для предиката {"groups":{"$exists":false}} вы можете слегка изменить данные, например, убедиться, что поле всегда существует, но присвоить ему значение, которое означает «неопределенное», которое можно индексировать и запрашивать.

Например, если вы запустите следующее обновление, поле групп будет существовать во всех документах:

db.jobs.update({"groups":{"$exists":false}},{"$set":{"groups":false}})

И вы можете Получите эквивалент приведенного выше числа, выполнив эти 2 запроса, которые оба должны быть покрыты сканированием индекса, и должны выполняться быстрее, чем запрос, требующий загрузки документов:

db.jobs.count({"status":"complete","groups":{"$size":0}})
db.jobs.count({"status":"complete","groups":{"$in":[false, "5e65ffc2a1e6ef0007bc5fa8"]}})
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...