3

我有一个大型数据库和许多复合索引。问题是,mongo 不使用索引中的所有字段进行查询,它只使用 4 个字段中的 2 个字段,即使所有 4 个字段都在复合索引中。或者它使用 3 字段复合索引而不是 4 字段。

这是我的架构(数据和元数据中还有其他字段,但在此查询中未使用它们):

{
    store_id: {type: String},
    data: {
        id: {type: Number},
    },
    meta: {
        is_published: {type: Boolean},
        lowercase_sku: {type: String}
    }
}

我有这些索引:

db.products.createIndex({'store_id':1,'meta.is_published':1,'data.id':1})
db.products.createIndex({'store_id':1,'meta.is_published':1,'data.id':1,'meta.lowercase_sku':1})

这是我的查询:

db.products.find({
    'store_id': 'my_domain.com',
    'meta.lowercase_sku': 'go-a83-3034/8+4',
    'meta.is_published': true, 
    'data.id': {'$ne': 7801040323}
}).explain('executionStats')

这是我从解释中得到的:

{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "my_database.products",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "$and" : [
                {
                    "meta.is_published" : {
                        "$eq" : true
                    }
                },
                {
                    "meta.lowercase_sku" : {
                        "$eq" : "go-a83-3034/8+4"
                    }
                },
                {
                    "store_id" : {
                        "$eq" : "my_domain.com"
                    }
                },
                {
                    "$not" : {
                        "data.id" : {
                            "$eq" : 7801040323
                        }
                    }
                }
            ]
        },
        "winningPlan" : {
            "stage" : "FETCH",
            "filter" : {
                "meta.lowercase_sku" : {
                    "$eq" : "go-a83-3034/8+4"
                }
            },
            "inputStage" : {
                "stage" : "IXSCAN",
                "keyPattern" : {
                    "store_id" : 1,
                    "meta.is_published" : 1,
                    "data.id" : 1
                },
                "indexName" : "store_id_1_meta.is_published_1_data.id_1",
                "isMultiKey" : false,
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 1,
                "direction" : "forward",
                "indexBounds" : {
                    "store_id" : [
                        "[\"my_domain.com\", \"my_domain.com\"]"
                    ],
                    "meta.is_published" : [
                        "[true, true]"
                    ],
                    "data.id" : [
                        "[MinKey, 7801040323.0)",
                        "(7801040323.0, MaxKey]"
                    ]
                }
            }
        },
        "rejectedPlans" : [
            {
                "stage" : "FETCH",
                "filter" : {
                    "$and" : [
                        {
                            "$not" : {
                                "data.id" : {
                                    "$eq" : 7801040323
                                }
                            }
                        },
                        {
                            "meta.lowercase_sku" : {
                                "$eq" : "go-a83-3034/8+4"
                            }
                        }
                    ]
                },
                "inputStage" : {
                    "stage" : "IXSCAN",
                    "keyPattern" : {
                        "store_id" : 1,
                        "meta.is_published" : 1,
                        "data.id" : 1,
                        "meta.lowercase_sku" : 1
                    },
                    "indexName" : "store_id_1_meta.is_published_1_data.id_1_meta.lowercase_sku_1",
                    "isMultiKey" : true,
                    "isUnique" : false,
                    "isSparse" : false,
                    "isPartial" : false,
                    "indexVersion" : 1,
                    "direction" : "forward",
                    "indexBounds" : {
                        "store_id" : [
                            "[\"my_domain.com\", \"my_domain.com\"]"
                        ],
                        "meta.is_published" : [
                            "[true, true]"
                        ],
                        "data.id" : [
                            "[MinKey, 7801040323.0)",
                            "(7801040323.0, MaxKey]"
                        ],
                        "meta.lowercase_sku" : [
                            "[MinKey, MaxKey]"
                        ]
                    }
                }
            }
        ]
    },
    "executionStats" : {
        "executionSuccess" : true,
        "nReturned" : 1,
        "executionTimeMillis" : 4590,
        "totalKeysExamined" : 28527,
        "totalDocsExamined" : 28525,
        "executionStages" : {
            "stage" : "FETCH",
            "filter" : {
                "meta.lowercase_sku" : {
                    "$eq" : "go-a83-3034/8+4"
                }
            },
            "nReturned" : 1,
            "executionTimeMillisEstimate" : 130,
            "works" : 28528,
            "advanced" : 1,
            "needTime" : 28525,
            "needYield" : 0,
            "saveState" : 5351,
            "restoreState" : 5351,
            "isEOF" : 1,
            "invalidates" : 0,
            "docsExamined" : 28525,
            "alreadyHasObj" : 0,
            "inputStage" : {
                "stage" : "IXSCAN",
                "nReturned" : 28525,
                "executionTimeMillisEstimate" : 70,
                "works" : 28527,
                "advanced" : 28525,
                "needTime" : 1,
                "needYield" : 0,
                "saveState" : 5351,
                "restoreState" : 5351,
                "isEOF" : 1,
                "invalidates" : 0,
                "keyPattern" : {
                    "store_id" : 1,
                    "meta.is_published" : 1,
                    "data.id" : 1
                },
                "indexName" : "store_id_1_meta.is_published_1_data.id_1",
                "isMultiKey" : false,
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 1,
                "direction" : "forward",
                "indexBounds" : {
                    "store_id" : [
                        "[\"my_domain.com\", \"my_domain.com\"]"
                    ],
                    "meta.is_published" : [
                        "[true, true]"
                    ],
                    "data.id" : [
                        "[MinKey, 7801040323.0)",
                        "(7801040323.0, MaxKey]"
                    ]
                },
                "keysExamined" : 28527,
                "dupsTested" : 0,
                "dupsDropped" : 0,
                "seenInvalidated" : 0
            }
        }
    },
    "serverInfo" : {
        "version" : "3.2.18",
        "gitVersion" : "4c1bae566c0c00f996a2feb16febf84936ecaf6f"
    },
    "ok" : 1
}

如您所见,出于某种原因,mongo 不使用 4 字段索引,即使我提示索引,它也只使用两个第一个字段(“store_id”和“meta.is_published”)。它倾向于选择仅涵盖 3 个字段(“store_id”、“meta.is_published”和“data.id”)的 3 字段索引,这有点道理,前提是从 4 字段索引中只使用 2 个字段。但为什么?

有人可以向我解释一下,也许可以提示我如何让查询更快地工作?

该数据库很大(大约 11 G)并且它最近被迁移(使用索引导入),如果有帮助的话。但是,有问题的索引是在导入之后创建的。

UPD (2):我还创建了一个具有相同架构和索引的文档测试集合。同样的查询确实正确地使用了索引。所以我倾向于认为索引大小或值类型存在某种问题。但我仍然无法确定它。

对此我非常感谢任何帮助或见解。


以下是对 4 字段索引的提示和结果供您参考的查询。

db.products.find({
    'store_id': 'my_domain.com',
    'meta.lowercase_sku': 'go-a83-3034/8+4',
    'meta.is_published': true, 
    'data.id': {'$ne': 7801040323}
}).hint('store_id_1_meta.is_published_1_data.id_1_meta.lowercase_sku_1').explain('executionStats')

结果:

{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "sharp_production.products",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "$and" : [
                {
                    "meta.is_published" : {
                        "$eq" : true
                    }
                },
                {
                    "meta.lowercase_sku" : {
                        "$eq" : "go-a83-3034/8+4"
                    }
                },
                {
                    "store_id" : {
                        "$eq" : "my_domain.com"
                    }
                },
                {
                    "$not" : {
                        "data.id" : {
                            "$eq" : 7801040323
                        }
                    }
                }
            ]
        },
        "winningPlan" : {
            "stage" : "FETCH",
            "filter" : {
                "$and" : [
                    {
                        "$not" : {
                            "data.id" : {
                                "$eq" : 7801040323
                            }
                        }
                    },
                    {
                        "meta.lowercase_sku" : {
                            "$eq" : "go-a83-3034/8+4"
                        }
                    }
                ]
            },
            "inputStage" : {
                "stage" : "IXSCAN",
                "keyPattern" : {
                    "store_id" : 1,
                    "meta.is_published" : 1,
                    "data.id" : 1,
                    "meta.lowercase_sku" : 1
                },
                "indexName" : "store_id_1_meta.is_published_1_data.id_1_meta.lowercase_sku_1",
                "isMultiKey" : true,
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 1,
                "direction" : "forward",
                "indexBounds" : {
                    "store_id" : [
                        "[\"my_domain.com\", \"my_domain.com\"]"
                    ],
                    "meta.is_published" : [
                        "[true, true]"
                    ],
                    "data.id" : [
                        "[MinKey, 7801040323.0)",
                        "(7801040323.0, MaxKey]"
                    ],
                    "meta.lowercase_sku" : [
                        "[MinKey, MaxKey]"
                    ]
                }
            }
        },
        "rejectedPlans" : [ ]
    },
    "executionStats" : {
        "executionSuccess" : true,
        "nReturned" : 1,
        "executionTimeMillis" : 213,
        "totalKeysExamined" : 28630,
        "totalDocsExamined" : 28525,
        "executionStages" : {
            "stage" : "FETCH",
            "filter" : {
                "$and" : [
                    {
                        "$not" : {
                            "data.id" : {
                                "$eq" : 7801040323
                            }
                        }
                    },
                    {
                        "meta.lowercase_sku" : {
                            "$eq" : "go-a83-3034/8+4"
                        }
                    }
                ]
            },
            "nReturned" : 1,
            "executionTimeMillisEstimate" : 210,
            "works" : 28630,
            "advanced" : 1,
            "needTime" : 28628,
            "needYield" : 0,
            "saveState" : 223,
            "restoreState" : 223,
            "isEOF" : 1,
            "invalidates" : 0,
            "docsExamined" : 28525,
            "alreadyHasObj" : 0,
            "inputStage" : {
                "stage" : "IXSCAN",
                "nReturned" : 28525,
                "executionTimeMillisEstimate" : 110,
                "works" : 28630,
                "advanced" : 28525,
                "needTime" : 104,
                "needYield" : 0,
                "saveState" : 223,
                "restoreState" : 223,
                "isEOF" : 1,
                "invalidates" : 0,
                "keyPattern" : {
                    "store_id" : 1,
                    "meta.is_published" : 1,
                    "data.id" : 1,
                    "meta.lowercase_sku" : 1
                },
                "indexName" : "store_id_1_meta.is_published_1_data.id_1_meta.lowercase_sku_1",
                "isMultiKey" : true,
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 1,
                "direction" : "forward",
                "indexBounds" : {
                    "store_id" : [
                        "[\"my_domain.com\", \"my_domain.com\"]"
                    ],
                    "meta.is_published" : [
                        "[true, true]"
                    ],
                    "data.id" : [
                        "[MinKey, 7801040323.0)",
                        "(7801040323.0, MaxKey]"
                    ],
                    "meta.lowercase_sku" : [
                        "[MinKey, MaxKey]"
                    ]
                },
                "keysExamined" : 28630,
                "dupsTested" : 28628,
                "dupsDropped" : 103,
                "seenInvalidated" : 0
            }
        }
    },
    "serverInfo" : {
        "version" : "3.2.18",
        "gitVersion" : "4c1bae566c0c00f996a2feb16febf84936ecaf6f"
    },
    "ok" : 1
}

更新 (1)


好的,显然“$ne”运算符可能会破坏索引,就像@JohnnyHK 所说的那样。所以我创建了另一个这样的:

db.products.createIndex({'store_id':1,'meta.is_published':1,'meta.lowercase_sku':1})

...并在删除 "$ne" 后尝试将其用于我的查询(它只是一个文档,并没有真正改变任何东西):

db.products.find({
    'store_id': 'my_domain.com',
    'meta.is_published': true,
    'meta.lowercase_sku': 'go-a83-3034/8+4',
}).hint('store_id_1_meta.is_published_1_meta.lowercase_sku_1').explain('executionStats')

但这是我这次得到的:

{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "sharp_production.products",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "$and" : [
                {
                    "meta.is_published" : {
                        "$eq" : true
                    }
                },
                {
                    "meta.lowercase_sku" : {
                        "$eq" : "go-a83-3034/8+4"
                    }
                },
                {
                    "store_id" : {
                        "$eq" : "my_domain.com"
                    }
                }
            ]
        },
        "winningPlan" : {
            "stage" : "FETCH",
            "filter" : {
                "meta.lowercase_sku" : {
                    "$eq" : "go-a83-3034/8+4"
                }
            },
            "inputStage" : {
                "stage" : "IXSCAN",
                "keyPattern" : {
                    "store_id" : 1,
                    "meta.is_published" : 1,
                    "meta.lowercase_sku" : 1
                },
                "indexName" : "store_id_1_meta.is_published_1_meta.lowercase_sku_1",
                "isMultiKey" : true,
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 1,
                "direction" : "forward",
                "indexBounds" : {
                    "store_id" : [
                        "[\"my_domain.com\", \"my_domain.com\"]"
                    ],
                    "meta.is_published" : [
                        "[true, true]"
                    ],
                    "meta.lowercase_sku" : [
                        "[MinKey, MaxKey]"
                    ]
                }
            }
        },
        "rejectedPlans" : [ ]
    },
    "executionStats" : {
        "executionSuccess" : true,
        "nReturned" : 1,
        "executionTimeMillis" : 136,
        "totalKeysExamined" : 28629,
        "totalDocsExamined" : 28526,
        "executionStages" : {
            "stage" : "FETCH",
            "filter" : {
                "meta.lowercase_sku" : {
                    "$eq" : "go-a83-3034/8+4"
                }
            },
            "nReturned" : 1,
            "executionTimeMillisEstimate" : 120,
            "works" : 28630,
            "advanced" : 1,
            "needTime" : 28628,
            "needYield" : 0,
            "saveState" : 223,
            "restoreState" : 223,
            "isEOF" : 1,
            "invalidates" : 0,
            "docsExamined" : 28526,
            "alreadyHasObj" : 0,
            "inputStage" : {
                "stage" : "IXSCAN",
                "nReturned" : 28526,
                "executionTimeMillisEstimate" : 30,
                "works" : 28630,
                "advanced" : 28526,
                "needTime" : 103,
                "needYield" : 0,
                "saveState" : 223,
                "restoreState" : 223,
                "isEOF" : 1,
                "invalidates" : 0,
                "keyPattern" : {
                    "store_id" : 1,
                    "meta.is_published" : 1,
                    "meta.lowercase_sku" : 1
                },
                "indexName" : "store_id_1_meta.is_published_1_meta.lowercase_sku_1",
                "isMultiKey" : true,
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 1,
                "direction" : "forward",
                "indexBounds" : {
                    "store_id" : [
                        "[\"my_domain.com\", \"my_domain.com\"]"
                    ],
                    "meta.is_published" : [
                        "[true, true]"
                    ],
                    "meta.lowercase_sku" : [
                        "[MinKey, MaxKey]"
                    ]
                },
                "keysExamined" : 28629,
                "dupsTested" : 28629,
                "dupsDropped" : 103,
                "seenInvalidated" : 0
            }
        }
    },
    "serverInfo" : {
        "version" : "3.2.18",
        "gitVersion" : "4c1bae566c0c00f996a2feb16febf84936ecaf6f"
    },
    "ok" : 1
}

...仍然保持 FETCH 阶段的“meta.lowercase_sku”条件,即使它就在索引中。还有什么想法吗?

4

1 回答 1

3

这是由于查询计划器如何选择要使用的索引。

问题的要点是您有多个可以满足查询的索引。假设您有两个具有相同前缀的索引:

db.test.createIndex({a:1, b:1})

db.test.createIndex({a:1, b:1, c:1})

现在,如果你这样做:

db.test.find({a:1, b:1})

MongoDB 如何知道哪个索引可以满足该查询?根据磁盘中的布局(在许多方面),一个索引可能比另一个更快。

因此,首先它将使用以下公式对计划进行排名:

score = baseScore + productivity + tieBreakers

目前在 MongoDB 3.6.3 中,baseScore = 1productivity取决于计划需要多少工作才能返回尽可能多的结果,并且tieBreakers取决于索引是否需要FETCH,没有SORT阶段,或有索引交集。

如果两个计划给出相同的分数(就像上面的两个计划一样),那么它会让它们竞争更快地获得 101 个结果。获胜的计划将被缓存,并执行该计划。正如您可以想象的那样,这可能是不确定的,并且还会浪费服务器的时间。最好是服务器可以只根据评分系统选择一个索引。

索引集合的最佳方法是删除非必要索引,以确保 MongoDB 可以立即选择索引。

注意:如果您对细节感兴趣,您可以:

  • 通过使用记录查询计划方法db.setLogLevel(5,'query'),查看日志中打印的过程mongod
  • db.collection.getPlanCache().clear()清除集合的计划缓存。
  • 检查计划排名器源代码
  • 查看博客文章优化 MongoDB 复合索引
于 2018-03-12T07:40:47.560 回答