MongoDB索引已创建但在查询过程中未被使用。

4

希望这不是一个完全愚蠢的问题。 我正在 MongoDB 上创建一个包含字符串字段的复合索引。 在运行索引创建之后,通过 db.currentOp() 命令没有看到创建索引的进程,但是如果我运行 getIndexes() 命令,我可以看到该索引。

db.sampledb.createIndex(
    {
        "DevIdent.ParametersExt. ID": 1,
        "DevIdent.Parameters.Type": 1,
        "MetaData.SessionName": 1
    },
    {
        background: false,
        name: "sample",
    }
)

使用explain运行此查询时,我总是会得到COLLSCAN,而不是索引扫描:

db.sampledb.aggregate([
    {
        "$match": {
            "DevIdent.ParametersExt.ID": { "$regex": ".*22~44.*" },
            "DevIdent.Parameters.Type": { "$ne": "TYPICAL" },
            "MetaData.SessionName": "2021_02_09_13_31_03"
        }
    }
])

我有什么基础知识没掌握到吗?

3个回答

2

这个匹配过滤器是问题所在:

"DevIdent.ParametersExt.ID": { "$regex": ".*22~44.*" }

您正在搜索DevIdent.ParametersExt.ID字段中22~44任何位置的文本。问题在于,基于B树的索引只能在从字段开头开始搜索时使用,而这里并不是这种情况。
但是,没有理由不能在其他两个字段上使用索引。但是,由于您在无法使用的字段上启动了多列索引,因此Mongo选择根本不使用此索引。
您可以考虑仅在其他两个字段上定义索引:
db.sampledb.createIndex({
    "DevIdent.Parameters.Type": 1,
    "MetaData.SessionName": 1
},
{
    background:false,
    name: "sample",
});

假设这两个字段的基数很高(即它们非常具有限制性),那么Mongo可能会选择使用索引,尽管第三个字段上存在正则表达式条件。


我之前在文档中没有遇到过正则表达式前缀问题,谢谢你指出来,这对我非常有帮助! - Anatoly Zaretsky

1

无论其他人回答的是什么,对于索引的低效使用都是正确的,但在解释计划中它仍应该显示为IXSCAN,因此我进行了一项测试。

db.sampledb.createIndex(
    {
        "DevIdent.ParametersExt. ID": 1,
        "DevIdent.Parameters.Type": 1,
        "MetaData.SessionName": 1
    },
    {
        background: false,
        name: "sample",
    }
)

这是您的命令,DevIdent.ParametersExt. & ID之间有一些空格,导致问题,但如果没有空格,您仍然无法创建新索引,会出现重复错误。完全删除此索引,并创建一个没有空格的新索引。

编辑:初中生错误,请删除名称部分,您应该能够创建两个索引,在树中,字段名称只是存储在相应BSON类型中的字符串,因此带空格和不带空格是两个单独的索引。

db.sampledb.createIndex(
    {
        "DevIdent.ParametersExt.ID": 1,
        "DevIdent.Parameters.Type": 1,
        "MetaData.SessionName": 1
    },
    {
        background: false,
        name: "sample",
    }
)  

现在尝试使用聚合命令,对我有效。

{
"queryPlanner" : {
    "plannerVersion" : 1,
    "namespace" : "test_content.test4",
    "indexFilterSet" : false,
    "parsedQuery" : {
        "$and" : [ 
            {
                "MetaData.SessionName" : {
                    "$eq" : "2021_02_09_13_31_03"
                }
            }, 
            {
                "DevIdent.ParametersExt.ID" : {
                    "$regex" : ".*22~44.*"
                }
            }, 
            {
                "DevIdent.Parameters.Type" : {
                    "$not" : {
                        "$eq" : "TYPICAL"
                    }
                }
            }
        ]
    },
    "optimizedPipeline" : true,
    "winningPlan" : {
        "stage" : "FETCH",
        "inputStage" : {
            "stage" : "IXSCAN",
            "filter" : {
                "DevIdent.ParametersExt.ID" : {
                    "$regex" : ".*22~44.*"
                }
            },
            "keyPattern" : {
                "DevIdent.ParametersExt.ID" : 1.0,
                "DevIdent.Parameters.Type" : 1.0,
                "MetaData.SessionName" : 1.0
            },
            "indexName" : "sample",
            "isMultiKey" : false,
            "multiKeyPaths" : {
                "DevIdent.ParametersExt.ID" : [],
                "DevIdent.Parameters.Type" : [],
                "MetaData.SessionName" : []
            },
            "isUnique" : false,
            "isSparse" : false,
            "isPartial" : false,
            "indexVersion" : 2,
            "direction" : "forward",
            "indexBounds" : {
                "DevIdent.ParametersExt.ID" : [ 
                    "[\"\", {})", 
                    "[/.*22~44.*/, /.*22~44.*/]"
                ],
                "DevIdent.Parameters.Type" : [ 
                    "[MinKey, \"TYPICAL\")", 
                    "(\"TYPICAL\", MaxKey]"
                ],
                "MetaData.SessionName" : [ 
                    "[\"2021_02_09_13_31_03\", \"2021_02_09_13_31_03\"]"
                ]
            }
        }
    },

你是完全正确的,因为索引是在不同的集合上创建的,所以它不起作用。但我遇到的下一个问题是正则表达式问题,所以我们需要重新设计数据,以便在字段开头显示以进行有效的索引。最终大家都是正确的并且非常有帮助,非常感谢! - Anatoly Zaretsky
@AnatolyZaretsky 如果您不介意,请接受正确答案并标记它。 - Gandalf the White

1

索引只能用于“前缀”^....正则表达式。我发现https://docs.mongodb.com/manual/reference/operator/query/regex/#index-use上的文档非常清晰,所以我在这里引用它:

对于区分大小写的正则表达式查询,如果字段存在索引,则MongoDB将正则表达式与索引中的值进行匹配,这可能比集合扫描更快。如果正则表达式是“前缀表达式”,则可以进一步优化,这意味着所有潜在匹配都以相同的字符串开头。这使MongoDB可以从该前缀构造一个“范围”,并仅针对落在该范围内的索引值进行匹配。
如果正则表达式以插入符号 (^) 或左锚点 (\A) 开头,后跟一系列简单符号,则它是“前缀表达式”。例如,正则表达式 /^abc.*/ 将通过仅与以 abc 开头的索引值匹配来进行优化。
此外,虽然 /^a/、/^a./ 和 /^a.$/ 匹配等效字符串,但它们具有不同的性能特征。如果适当的索引存在,则所有这些表达式都使用索引;但是,/^a./ 和 /^a.$/ 较慢。/^a/ 可以在匹配前缀后停止扫描。

这是正确的,但请注意,如果 OP 将正则表达式字段放在索引的最后一个位置,Mongo 仍然可以通过选择在前两个字段上过滤后扫描来使用这个多列索引。因此,如果措辞不同,索引是可以被使用的。 - Tim Biegeleisen
@TimBiegeleisen,“DevIdent.ParametersExt.ID”对我来说似乎是当前问题的首选。我完全同意,如果它是索引定义中的最后一个,那么索引就可以使用。当然,假设空格是问题中的一个打字错误。 - Alex Blex
是的,空格只是我在StackOverflow上复制时犯的错误。我将字段名称更改为更短,以使问题不那么令人困惑,结果却造成了更多的混乱,对此感到抱歉。 - Anatoly Zaretsky
我之前在文档中没有遇到过正则表达式前缀问题,谢谢你指出来,这对我很有帮助。 - Anatoly Zaretsky

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接