了解 Mongoid 查询计划
Understand Mongoid Query Plan
我对 Mongoid 和 Rails 没有什么经验。我正在尝试尽可能提高我在 Rails 应用程序中编写的查询的性能。
该应用有一个 User
模型和一个 WithdrawalHold
模型。一个用户可以有多个withdrawal_holds。
WithdrawalHold 有 user_id
和 hold_until
字段。 WithdrawalHold 具有以下指标:
index({ user_id: 1, hold_until: 1, _id: -1 }, background: true)
index({ hold_until: 1 }, background: true)
当我 运行 在 Rails 控制台中关注时:
WithdrawalHold.where(:user_id => 1, :hold_until.gte => Time.now).order_by(:hold_until => 'asc').explain
{"queryPlanner"=>
{"plannerVersion"=>1,
"namespace"=>"test.limits_withdrawal_holds",
"indexFilterSet"=>false,
"parsedQuery"=>{"$and"=>[{"user_id"=>{"$eq"=>1}}, {"hold_until"=>{"$gte"=>2018-02-25 21:53:24 UTC}}]},
"winningPlan"=>
{"stage"=>"FETCH",
"inputStage"=>
{"stage"=>"IXSCAN",
"keyPattern"=>{"user_id"=>1, "hold_until"=>1, "_id"=>-1},
"indexName"=>"user_id_1_hold_until_1__id_-1",
"isMultiKey"=>false,
"multiKeyPaths"=>{"user_id"=>[], "hold_until"=>[], "_id"=>[]},
"isUnique"=>false,
"isSparse"=>false,
"isPartial"=>false,
"indexVersion"=>2,
"direction"=>"forward",
"indexBounds"=>{"user_id"=>["[1, 1]"], "hold_until"=>["[new Date(1519595604368), new Date(9223372036854775807)]"], "_id"=>["[MaxKey, MinKey]"]}}},
"rejectedPlans"=>
[{"stage"=>"FETCH",
"filter"=>{"user_id"=>{"$eq"=>1}},
"inputStage"=>
{"stage"=>"IXSCAN",
"keyPattern"=>{"hold_until"=>1},
"indexName"=>"hold_until_1",
"isMultiKey"=>false,
"multiKeyPaths"=>{"hold_until"=>[]},
"isUnique"=>false,
"isSparse"=>false,
"isPartial"=>false,
"indexVersion"=>2,
"direction"=>"forward",
"indexBounds"=>{"hold_until"=>["[new Date(1519595604368), new Date(9223372036854775807)]"]}}}]},
"executionStats"=>
{"executionSuccess"=>true,
"nReturned"=>0,
"executionTimeMillis"=>0,
"totalKeysExamined"=>0,
"totalDocsExamined"=>0,
"executionStages"=>
{"stage"=>"FETCH",
"nReturned"=>0,
"executionTimeMillisEstimate"=>0,
"works"=>2,
"advanced"=>0,
"needTime"=>0,
"needYield"=>0,
"saveState"=>0,
"restoreState"=>0,
"isEOF"=>1,
"invalidates"=>0,
"docsExamined"=>0,
"alreadyHasObj"=>0,
"inputStage"=>
{"stage"=>"IXSCAN",
"nReturned"=>0,
"executionTimeMillisEstimate"=>0,
"works"=>1,
"advanced"=>0,
"needTime"=>0,
"needYield"=>0,
"saveState"=>0,
"restoreState"=>0,
"isEOF"=>1,
"invalidates"=>0,
"keyPattern"=>{"user_id"=>1, "hold_until"=>1, "_id"=>-1},
"indexName"=>"user_id_1_hold_until_1__id_-1",
"isMultiKey"=>false,
"multiKeyPaths"=>{"user_id"=>[], "hold_until"=>[], "_id"=>[]},
"isUnique"=>false,
"isSparse"=>false,
"isPartial"=>false,
"indexVersion"=>2,
"direction"=>"forward",
"indexBounds"=>{"user_id"=>["[1, 1]"], "hold_until"=>["[new Date(1519595604368), new Date(9223372036854775807)]"], "_id"=>["[MaxKey, MinKey]"]},
"keysExamined"=>0,
"seeks"=>1,
"dupsTested"=>0,
"dupsDropped"=>0,
"seenInvalidated"=>0}},
"allPlansExecution"=>
[{"nReturned"=>0,
"executionTimeMillisEstimate"=>0,
"totalKeysExamined"=>1,
"totalDocsExamined"=>1,
"executionStages"=>
{"stage"=>"FETCH",
"filter"=>{"user_id"=>{"$eq"=>1}},
"nReturned"=>0,
"executionTimeMillisEstimate"=>0,
"works"=>1,
"advanced"=>0,
"needTime"=>1,
"needYield"=>0,
"saveState"=>0,
"restoreState"=>0,
"isEOF"=>0,
"invalidates"=>0,
"docsExamined"=>1,
"alreadyHasObj"=>0,
"inputStage"=>
{"stage"=>"IXSCAN",
"nReturned"=>1,
"executionTimeMillisEstimate"=>0,
"works"=>1,
"advanced"=>1,
"needTime"=>0,
"needYield"=>0,
"saveState"=>0,
"restoreState"=>0,
"isEOF"=>0,
"invalidates"=>0,
"keyPattern"=>{"hold_until"=>1},
"indexName"=>"hold_until_1",
"isMultiKey"=>false,
"multiKeyPaths"=>{"hold_until"=>[]},
"isUnique"=>false,
"isSparse"=>false,
"isPartial"=>false,
"indexVersion"=>2,
"direction"=>"forward",
"indexBounds"=>{"hold_until"=>["[new Date(1519595604368), new Date(9223372036854775807)]"]},
"keysExamined"=>1,
"seeks"=>1,
"dupsTested"=>0,
"dupsDropped"=>0,
"seenInvalidated"=>0}}},
{"nReturned"=>0,
"executionTimeMillisEstimate"=>0,
"totalKeysExamined"=>0,
"totalDocsExamined"=>0,
"executionStages"=>
{"stage"=>"FETCH",
"nReturned"=>0,
"executionTimeMillisEstimate"=>0,
"works"=>1,
"advanced"=>0,
"needTime"=>0,
"needYield"=>0,
"saveState"=>0,
"restoreState"=>0,
"isEOF"=>1,
"invalidates"=>0,
"docsExamined"=>0,
"alreadyHasObj"=>0,
"inputStage"=>
{"stage"=>"IXSCAN",
"nReturned"=>0,
"executionTimeMillisEstimate"=>0,
"works"=>1,
"advanced"=>0,
"needTime"=>0,
"needYield"=>0,
"saveState"=>0,
"restoreState"=>0,
"isEOF"=>1,
"invalidates"=>0,
"keyPattern"=>{"user_id"=>1, "hold_until"=>1, "_id"=>-1},
"indexName"=>"user_id_1_hold_until_1__id_-1",
"isMultiKey"=>false,
"multiKeyPaths"=>{"user_id"=>[], "hold_until"=>[], "_id"=>[]},
"isUnique"=>false,
"isSparse"=>false,
"isPartial"=>false,
"indexVersion"=>2,
"direction"=>"forward",
"indexBounds"=>{"user_id"=>["[1, 1]"], "hold_until"=>["[new Date(1519595604368), new Date(9223372036854775807)]"], "_id"=>["[MaxKey, MinKey]"]},
"keysExamined"=>0,
"seeks"=>1,
"dupsTested"=>0,
"dupsDropped"=>0,
"seenInvalidated"=>0}}}]},
"serverInfo"=>{"host"=>"1cf25211760e", "port"=>27017, "version"=>"3.4.9", "gitVersion"=>"876ebee8c7dd0e2d992f36a848ff4dc50ee6603e"},
"ok"=>1.0}
结果表明使用了 { user_id: 1, hold_until: 1, _id: -1 }
索引。检查的文档数为 0,我认为这表明查询已被索引覆盖 (https://docs.mongodb.com/manual/reference/explain-results/)。
请注意,在上面的查询中 user_id 是 1,没有具有该 ID 的用户。
现在,如果我使用真实的 user_id(其类型是 BSON::ObjectId):
WithdrawalHold.where(:user_id => user.id, :hold_until.gte => Time.now).order_by(:hold_until => 'asc').explain
{"queryPlanner"=>
{"plannerVersion"=>1,
"namespace"=>"test.limits_withdrawal_holds",
"indexFilterSet"=>false,
"parsedQuery"=>{"$and"=>[{"user_id"=>{"$eq"=>BSON::ObjectId('5a932ba8476e0a3ee86a3b5e')}}, {"hold_until"=>{"$gte"=>2018-02-25 21:55:05 UTC}}]},
"winningPlan"=>
{"stage"=>"FETCH",
"filter"=>{"user_id"=>{"$eq"=>BSON::ObjectId('5a932ba8476e0a3ee86a3b5e')}},
"inputStage"=>
{"stage"=>"IXSCAN",
"keyPattern"=>{"hold_until"=>1},
"indexName"=>"hold_until_1",
"isMultiKey"=>false,
"multiKeyPaths"=>{"hold_until"=>[]},
"isUnique"=>false,
"isSparse"=>false,
"isPartial"=>false,
"indexVersion"=>2,
"direction"=>"forward",
"indexBounds"=>{"hold_until"=>["[new Date(1519595705482), new Date(9223372036854775807)]"]}}},
"rejectedPlans"=>
[{"stage"=>"FETCH",
"inputStage"=>
{"stage"=>"IXSCAN",
"keyPattern"=>{"user_id"=>1, "hold_until"=>1, "_id"=>-1},
"indexName"=>"user_id_1_hold_until_1__id_-1",
"isMultiKey"=>false,
"multiKeyPaths"=>{"user_id"=>[], "hold_until"=>[], "_id"=>[]},
"isUnique"=>false,
"isSparse"=>false,
"isPartial"=>false,
"indexVersion"=>2,
"direction"=>"forward",
"indexBounds"=>
{"user_id"=>["[ObjectId('5a932ba8476e0a3ee86a3b5e'), ObjectId('5a932ba8476e0a3ee86a3b5e')]"],
"hold_until"=>["[new Date(1519595705482), new Date(9223372036854775807)]"],
"_id"=>["[MaxKey, MinKey]"]}}}]},
"executionStats"=>
{"executionSuccess"=>true,
"nReturned"=>3,
"executionTimeMillis"=>0,
"totalKeysExamined"=>3,
"totalDocsExamined"=>3,
"executionStages"=>
{"stage"=>"FETCH",
"filter"=>{"user_id"=>{"$eq"=>BSON::ObjectId('5a932ba8476e0a3ee86a3b5e')}},
"nReturned"=>3,
"executionTimeMillisEstimate"=>0,
"works"=>5,
"advanced"=>3,
"needTime"=>0,
"needYield"=>0,
"saveState"=>0,
"restoreState"=>0,
"isEOF"=>1,
"invalidates"=>0,
"docsExamined"=>3,
"alreadyHasObj"=>0,
"inputStage"=>
{"stage"=>"IXSCAN",
"nReturned"=>3,
"executionTimeMillisEstimate"=>0,
"works"=>4,
"advanced"=>3,
"needTime"=>0,
"needYield"=>0,
"saveState"=>0,
"restoreState"=>0,
"isEOF"=>1,
"invalidates"=>0,
"keyPattern"=>{"hold_until"=>1},
"indexName"=>"hold_until_1",
"isMultiKey"=>false,
"multiKeyPaths"=>{"hold_until"=>[]},
"isUnique"=>false,
"isSparse"=>false,
"isPartial"=>false,
"indexVersion"=>2,
"direction"=>"forward",
"indexBounds"=>{"hold_until"=>["[new Date(1519595705482), new Date(9223372036854775807)]"]},
"keysExamined"=>3,
"seeks"=>1,
"dupsTested"=>0,
"dupsDropped"=>0,
"seenInvalidated"=>0}},
"allPlansExecution"=>
[{"nReturned"=>3,
"executionTimeMillisEstimate"=>0,
"totalKeysExamined"=>3,
"totalDocsExamined"=>3,
"executionStages"=>
{"stage"=>"FETCH",
"nReturned"=>3,
"executionTimeMillisEstimate"=>0,
"works"=>4,
"advanced"=>3,
"needTime"=>0,
"needYield"=>0,
"saveState"=>0,
"restoreState"=>0,
"isEOF"=>1,
"invalidates"=>0,
"docsExamined"=>3,
"alreadyHasObj"=>0,
"inputStage"=>
{"stage"=>"IXSCAN",
"nReturned"=>3,
"executionTimeMillisEstimate"=>0,
"works"=>4,
"advanced"=>3,
"needTime"=>0,
"needYield"=>0,
"saveState"=>0,
"restoreState"=>0,
"isEOF"=>1,
"invalidates"=>0,
"keyPattern"=>{"user_id"=>1, "hold_until"=>1, "_id"=>-1},
"indexName"=>"user_id_1_hold_until_1__id_-1",
"isMultiKey"=>false,
"multiKeyPaths"=>{"user_id"=>[], "hold_until"=>[], "_id"=>[]},
"isUnique"=>false,
"isSparse"=>false,
"isPartial"=>false,
"indexVersion"=>2,
"direction"=>"forward",
"indexBounds"=>
{"user_id"=>["[ObjectId('5a932ba8476e0a3ee86a3b5e'), ObjectId('5a932ba8476e0a3ee86a3b5e')]"],
"hold_until"=>["[new Date(1519595705482), new Date(9223372036854775807)]"],
"_id"=>["[MaxKey, MinKey]"]},
"keysExamined"=>3,
"seeks"=>1,
"dupsTested"=>0,
"dupsDropped"=>0,
"seenInvalidated"=>0}}},
{"nReturned"=>3,
"executionTimeMillisEstimate"=>0,
"totalKeysExamined"=>3,
"totalDocsExamined"=>3,
"executionStages"=>
{"stage"=>"FETCH",
"filter"=>{"user_id"=>{"$eq"=>BSON::ObjectId('5a932ba8476e0a3ee86a3b5e')}},
"nReturned"=>3,
"executionTimeMillisEstimate"=>0,
"works"=>4,
"advanced"=>3,
"needTime"=>0,
"needYield"=>0,
"saveState"=>0,
"restoreState"=>0,
"isEOF"=>1,
"invalidates"=>0,
"docsExamined"=>3,
"alreadyHasObj"=>0,
"inputStage"=>
{"stage"=>"IXSCAN",
"nReturned"=>3,
"executionTimeMillisEstimate"=>0,
"works"=>4,
"advanced"=>3,
"needTime"=>0,
"needYield"=>0,
"saveState"=>0,
"restoreState"=>0,
"isEOF"=>1,
"invalidates"=>0,
"keyPattern"=>{"hold_until"=>1},
"indexName"=>"hold_until_1",
"isMultiKey"=>false,
"multiKeyPaths"=>{"hold_until"=>[]},
"isUnique"=>false,
"isSparse"=>false,
"isPartial"=>false,
"indexVersion"=>2,
"direction"=>"forward",
"indexBounds"=>{"hold_until"=>["[new Date(1519595705482), new Date(9223372036854775807)]"]},
"keysExamined"=>3,
"seeks"=>1,
"dupsTested"=>0,
"dupsDropped"=>0,
"seenInvalidated"=>0}}}]},
"serverInfo"=>{"host"=>"1cf25211760e", "port"=>27017, "version"=>"3.4.9", "gitVersion"=>"876ebee8c7dd0e2d992f36a848ff4dc50ee6603e"},
"ok"=>1.0}
结果表明只使用了 { hold_until: 1 }
索引并且检查了一些文档。
我想了解为什么这两个查询的计划不同,以及是否可以编写此查询以使其完全被索引覆盖。
你问了快两年了
到目前为止还没有人回答这个问题,因为我猜这个答案会吸引潜在的反对票。
我想知道是否有任何答案仍然与您相关。
我既不知道你在collection中有什么,也不很了解MongoDB索引,但获胜的计划是"FETCH"是一个好兆头。这意味着您的索引正在运行。
相反,如果根本没有使用索引,则计划将是 "COLLSCAN".
现在解决剩下的问题:
- 为什么要审查一些文件?
- 至少,MongoDB 需要检查他们返回的内容。 "nReturned" 和 "docsExamined" 的相同值表示 MongoDB 除了返回的文档之外没有触及其他文档。
- 为什么它不选择另一个方案,而是拒绝了它?
- 我在这里无法确定确切原因,但数据库确定使用它选择的索引在统计上更有效。
它可能取决于 collection 上存储的数据,例如在 SQL 实现中,即使 table 包含 BTREE 索引,有时规划器拒绝使用 BTREE 并扫描 table 而不是因为 table 包含一行或仅几行数据。 BTREE 查找不值得。
MongoDB 也会发生类似的事情。
唯一能准确告诉你原因的是为这种情况优化数据库的人。
这里的主要take-home是:有时候你需要放手。如果你希望每一个细节都准确无误,你将无法像具有健康无知水平的人那样取得成就。如果数据库运行得足够好,那说明你做的足够好。
我对 Mongoid 和 Rails 没有什么经验。我正在尝试尽可能提高我在 Rails 应用程序中编写的查询的性能。
该应用有一个 User
模型和一个 WithdrawalHold
模型。一个用户可以有多个withdrawal_holds。
WithdrawalHold 有 user_id
和 hold_until
字段。 WithdrawalHold 具有以下指标:
index({ user_id: 1, hold_until: 1, _id: -1 }, background: true)
index({ hold_until: 1 }, background: true)
当我 运行 在 Rails 控制台中关注时:
WithdrawalHold.where(:user_id => 1, :hold_until.gte => Time.now).order_by(:hold_until => 'asc').explain
{"queryPlanner"=>
{"plannerVersion"=>1,
"namespace"=>"test.limits_withdrawal_holds",
"indexFilterSet"=>false,
"parsedQuery"=>{"$and"=>[{"user_id"=>{"$eq"=>1}}, {"hold_until"=>{"$gte"=>2018-02-25 21:53:24 UTC}}]},
"winningPlan"=>
{"stage"=>"FETCH",
"inputStage"=>
{"stage"=>"IXSCAN",
"keyPattern"=>{"user_id"=>1, "hold_until"=>1, "_id"=>-1},
"indexName"=>"user_id_1_hold_until_1__id_-1",
"isMultiKey"=>false,
"multiKeyPaths"=>{"user_id"=>[], "hold_until"=>[], "_id"=>[]},
"isUnique"=>false,
"isSparse"=>false,
"isPartial"=>false,
"indexVersion"=>2,
"direction"=>"forward",
"indexBounds"=>{"user_id"=>["[1, 1]"], "hold_until"=>["[new Date(1519595604368), new Date(9223372036854775807)]"], "_id"=>["[MaxKey, MinKey]"]}}},
"rejectedPlans"=>
[{"stage"=>"FETCH",
"filter"=>{"user_id"=>{"$eq"=>1}},
"inputStage"=>
{"stage"=>"IXSCAN",
"keyPattern"=>{"hold_until"=>1},
"indexName"=>"hold_until_1",
"isMultiKey"=>false,
"multiKeyPaths"=>{"hold_until"=>[]},
"isUnique"=>false,
"isSparse"=>false,
"isPartial"=>false,
"indexVersion"=>2,
"direction"=>"forward",
"indexBounds"=>{"hold_until"=>["[new Date(1519595604368), new Date(9223372036854775807)]"]}}}]},
"executionStats"=>
{"executionSuccess"=>true,
"nReturned"=>0,
"executionTimeMillis"=>0,
"totalKeysExamined"=>0,
"totalDocsExamined"=>0,
"executionStages"=>
{"stage"=>"FETCH",
"nReturned"=>0,
"executionTimeMillisEstimate"=>0,
"works"=>2,
"advanced"=>0,
"needTime"=>0,
"needYield"=>0,
"saveState"=>0,
"restoreState"=>0,
"isEOF"=>1,
"invalidates"=>0,
"docsExamined"=>0,
"alreadyHasObj"=>0,
"inputStage"=>
{"stage"=>"IXSCAN",
"nReturned"=>0,
"executionTimeMillisEstimate"=>0,
"works"=>1,
"advanced"=>0,
"needTime"=>0,
"needYield"=>0,
"saveState"=>0,
"restoreState"=>0,
"isEOF"=>1,
"invalidates"=>0,
"keyPattern"=>{"user_id"=>1, "hold_until"=>1, "_id"=>-1},
"indexName"=>"user_id_1_hold_until_1__id_-1",
"isMultiKey"=>false,
"multiKeyPaths"=>{"user_id"=>[], "hold_until"=>[], "_id"=>[]},
"isUnique"=>false,
"isSparse"=>false,
"isPartial"=>false,
"indexVersion"=>2,
"direction"=>"forward",
"indexBounds"=>{"user_id"=>["[1, 1]"], "hold_until"=>["[new Date(1519595604368), new Date(9223372036854775807)]"], "_id"=>["[MaxKey, MinKey]"]},
"keysExamined"=>0,
"seeks"=>1,
"dupsTested"=>0,
"dupsDropped"=>0,
"seenInvalidated"=>0}},
"allPlansExecution"=>
[{"nReturned"=>0,
"executionTimeMillisEstimate"=>0,
"totalKeysExamined"=>1,
"totalDocsExamined"=>1,
"executionStages"=>
{"stage"=>"FETCH",
"filter"=>{"user_id"=>{"$eq"=>1}},
"nReturned"=>0,
"executionTimeMillisEstimate"=>0,
"works"=>1,
"advanced"=>0,
"needTime"=>1,
"needYield"=>0,
"saveState"=>0,
"restoreState"=>0,
"isEOF"=>0,
"invalidates"=>0,
"docsExamined"=>1,
"alreadyHasObj"=>0,
"inputStage"=>
{"stage"=>"IXSCAN",
"nReturned"=>1,
"executionTimeMillisEstimate"=>0,
"works"=>1,
"advanced"=>1,
"needTime"=>0,
"needYield"=>0,
"saveState"=>0,
"restoreState"=>0,
"isEOF"=>0,
"invalidates"=>0,
"keyPattern"=>{"hold_until"=>1},
"indexName"=>"hold_until_1",
"isMultiKey"=>false,
"multiKeyPaths"=>{"hold_until"=>[]},
"isUnique"=>false,
"isSparse"=>false,
"isPartial"=>false,
"indexVersion"=>2,
"direction"=>"forward",
"indexBounds"=>{"hold_until"=>["[new Date(1519595604368), new Date(9223372036854775807)]"]},
"keysExamined"=>1,
"seeks"=>1,
"dupsTested"=>0,
"dupsDropped"=>0,
"seenInvalidated"=>0}}},
{"nReturned"=>0,
"executionTimeMillisEstimate"=>0,
"totalKeysExamined"=>0,
"totalDocsExamined"=>0,
"executionStages"=>
{"stage"=>"FETCH",
"nReturned"=>0,
"executionTimeMillisEstimate"=>0,
"works"=>1,
"advanced"=>0,
"needTime"=>0,
"needYield"=>0,
"saveState"=>0,
"restoreState"=>0,
"isEOF"=>1,
"invalidates"=>0,
"docsExamined"=>0,
"alreadyHasObj"=>0,
"inputStage"=>
{"stage"=>"IXSCAN",
"nReturned"=>0,
"executionTimeMillisEstimate"=>0,
"works"=>1,
"advanced"=>0,
"needTime"=>0,
"needYield"=>0,
"saveState"=>0,
"restoreState"=>0,
"isEOF"=>1,
"invalidates"=>0,
"keyPattern"=>{"user_id"=>1, "hold_until"=>1, "_id"=>-1},
"indexName"=>"user_id_1_hold_until_1__id_-1",
"isMultiKey"=>false,
"multiKeyPaths"=>{"user_id"=>[], "hold_until"=>[], "_id"=>[]},
"isUnique"=>false,
"isSparse"=>false,
"isPartial"=>false,
"indexVersion"=>2,
"direction"=>"forward",
"indexBounds"=>{"user_id"=>["[1, 1]"], "hold_until"=>["[new Date(1519595604368), new Date(9223372036854775807)]"], "_id"=>["[MaxKey, MinKey]"]},
"keysExamined"=>0,
"seeks"=>1,
"dupsTested"=>0,
"dupsDropped"=>0,
"seenInvalidated"=>0}}}]},
"serverInfo"=>{"host"=>"1cf25211760e", "port"=>27017, "version"=>"3.4.9", "gitVersion"=>"876ebee8c7dd0e2d992f36a848ff4dc50ee6603e"},
"ok"=>1.0}
结果表明使用了 { user_id: 1, hold_until: 1, _id: -1 }
索引。检查的文档数为 0,我认为这表明查询已被索引覆盖 (https://docs.mongodb.com/manual/reference/explain-results/)。
请注意,在上面的查询中 user_id 是 1,没有具有该 ID 的用户。
现在,如果我使用真实的 user_id(其类型是 BSON::ObjectId):
WithdrawalHold.where(:user_id => user.id, :hold_until.gte => Time.now).order_by(:hold_until => 'asc').explain
{"queryPlanner"=>
{"plannerVersion"=>1,
"namespace"=>"test.limits_withdrawal_holds",
"indexFilterSet"=>false,
"parsedQuery"=>{"$and"=>[{"user_id"=>{"$eq"=>BSON::ObjectId('5a932ba8476e0a3ee86a3b5e')}}, {"hold_until"=>{"$gte"=>2018-02-25 21:55:05 UTC}}]},
"winningPlan"=>
{"stage"=>"FETCH",
"filter"=>{"user_id"=>{"$eq"=>BSON::ObjectId('5a932ba8476e0a3ee86a3b5e')}},
"inputStage"=>
{"stage"=>"IXSCAN",
"keyPattern"=>{"hold_until"=>1},
"indexName"=>"hold_until_1",
"isMultiKey"=>false,
"multiKeyPaths"=>{"hold_until"=>[]},
"isUnique"=>false,
"isSparse"=>false,
"isPartial"=>false,
"indexVersion"=>2,
"direction"=>"forward",
"indexBounds"=>{"hold_until"=>["[new Date(1519595705482), new Date(9223372036854775807)]"]}}},
"rejectedPlans"=>
[{"stage"=>"FETCH",
"inputStage"=>
{"stage"=>"IXSCAN",
"keyPattern"=>{"user_id"=>1, "hold_until"=>1, "_id"=>-1},
"indexName"=>"user_id_1_hold_until_1__id_-1",
"isMultiKey"=>false,
"multiKeyPaths"=>{"user_id"=>[], "hold_until"=>[], "_id"=>[]},
"isUnique"=>false,
"isSparse"=>false,
"isPartial"=>false,
"indexVersion"=>2,
"direction"=>"forward",
"indexBounds"=>
{"user_id"=>["[ObjectId('5a932ba8476e0a3ee86a3b5e'), ObjectId('5a932ba8476e0a3ee86a3b5e')]"],
"hold_until"=>["[new Date(1519595705482), new Date(9223372036854775807)]"],
"_id"=>["[MaxKey, MinKey]"]}}}]},
"executionStats"=>
{"executionSuccess"=>true,
"nReturned"=>3,
"executionTimeMillis"=>0,
"totalKeysExamined"=>3,
"totalDocsExamined"=>3,
"executionStages"=>
{"stage"=>"FETCH",
"filter"=>{"user_id"=>{"$eq"=>BSON::ObjectId('5a932ba8476e0a3ee86a3b5e')}},
"nReturned"=>3,
"executionTimeMillisEstimate"=>0,
"works"=>5,
"advanced"=>3,
"needTime"=>0,
"needYield"=>0,
"saveState"=>0,
"restoreState"=>0,
"isEOF"=>1,
"invalidates"=>0,
"docsExamined"=>3,
"alreadyHasObj"=>0,
"inputStage"=>
{"stage"=>"IXSCAN",
"nReturned"=>3,
"executionTimeMillisEstimate"=>0,
"works"=>4,
"advanced"=>3,
"needTime"=>0,
"needYield"=>0,
"saveState"=>0,
"restoreState"=>0,
"isEOF"=>1,
"invalidates"=>0,
"keyPattern"=>{"hold_until"=>1},
"indexName"=>"hold_until_1",
"isMultiKey"=>false,
"multiKeyPaths"=>{"hold_until"=>[]},
"isUnique"=>false,
"isSparse"=>false,
"isPartial"=>false,
"indexVersion"=>2,
"direction"=>"forward",
"indexBounds"=>{"hold_until"=>["[new Date(1519595705482), new Date(9223372036854775807)]"]},
"keysExamined"=>3,
"seeks"=>1,
"dupsTested"=>0,
"dupsDropped"=>0,
"seenInvalidated"=>0}},
"allPlansExecution"=>
[{"nReturned"=>3,
"executionTimeMillisEstimate"=>0,
"totalKeysExamined"=>3,
"totalDocsExamined"=>3,
"executionStages"=>
{"stage"=>"FETCH",
"nReturned"=>3,
"executionTimeMillisEstimate"=>0,
"works"=>4,
"advanced"=>3,
"needTime"=>0,
"needYield"=>0,
"saveState"=>0,
"restoreState"=>0,
"isEOF"=>1,
"invalidates"=>0,
"docsExamined"=>3,
"alreadyHasObj"=>0,
"inputStage"=>
{"stage"=>"IXSCAN",
"nReturned"=>3,
"executionTimeMillisEstimate"=>0,
"works"=>4,
"advanced"=>3,
"needTime"=>0,
"needYield"=>0,
"saveState"=>0,
"restoreState"=>0,
"isEOF"=>1,
"invalidates"=>0,
"keyPattern"=>{"user_id"=>1, "hold_until"=>1, "_id"=>-1},
"indexName"=>"user_id_1_hold_until_1__id_-1",
"isMultiKey"=>false,
"multiKeyPaths"=>{"user_id"=>[], "hold_until"=>[], "_id"=>[]},
"isUnique"=>false,
"isSparse"=>false,
"isPartial"=>false,
"indexVersion"=>2,
"direction"=>"forward",
"indexBounds"=>
{"user_id"=>["[ObjectId('5a932ba8476e0a3ee86a3b5e'), ObjectId('5a932ba8476e0a3ee86a3b5e')]"],
"hold_until"=>["[new Date(1519595705482), new Date(9223372036854775807)]"],
"_id"=>["[MaxKey, MinKey]"]},
"keysExamined"=>3,
"seeks"=>1,
"dupsTested"=>0,
"dupsDropped"=>0,
"seenInvalidated"=>0}}},
{"nReturned"=>3,
"executionTimeMillisEstimate"=>0,
"totalKeysExamined"=>3,
"totalDocsExamined"=>3,
"executionStages"=>
{"stage"=>"FETCH",
"filter"=>{"user_id"=>{"$eq"=>BSON::ObjectId('5a932ba8476e0a3ee86a3b5e')}},
"nReturned"=>3,
"executionTimeMillisEstimate"=>0,
"works"=>4,
"advanced"=>3,
"needTime"=>0,
"needYield"=>0,
"saveState"=>0,
"restoreState"=>0,
"isEOF"=>1,
"invalidates"=>0,
"docsExamined"=>3,
"alreadyHasObj"=>0,
"inputStage"=>
{"stage"=>"IXSCAN",
"nReturned"=>3,
"executionTimeMillisEstimate"=>0,
"works"=>4,
"advanced"=>3,
"needTime"=>0,
"needYield"=>0,
"saveState"=>0,
"restoreState"=>0,
"isEOF"=>1,
"invalidates"=>0,
"keyPattern"=>{"hold_until"=>1},
"indexName"=>"hold_until_1",
"isMultiKey"=>false,
"multiKeyPaths"=>{"hold_until"=>[]},
"isUnique"=>false,
"isSparse"=>false,
"isPartial"=>false,
"indexVersion"=>2,
"direction"=>"forward",
"indexBounds"=>{"hold_until"=>["[new Date(1519595705482), new Date(9223372036854775807)]"]},
"keysExamined"=>3,
"seeks"=>1,
"dupsTested"=>0,
"dupsDropped"=>0,
"seenInvalidated"=>0}}}]},
"serverInfo"=>{"host"=>"1cf25211760e", "port"=>27017, "version"=>"3.4.9", "gitVersion"=>"876ebee8c7dd0e2d992f36a848ff4dc50ee6603e"},
"ok"=>1.0}
结果表明只使用了 { hold_until: 1 }
索引并且检查了一些文档。
我想了解为什么这两个查询的计划不同,以及是否可以编写此查询以使其完全被索引覆盖。
你问了快两年了
到目前为止还没有人回答这个问题,因为我猜这个答案会吸引潜在的反对票。
我想知道是否有任何答案仍然与您相关。
我既不知道你在collection中有什么,也不很了解MongoDB索引,但获胜的计划是"FETCH"是一个好兆头。这意味着您的索引正在运行。 相反,如果根本没有使用索引,则计划将是 "COLLSCAN".
现在解决剩下的问题:
- 为什么要审查一些文件?
- 至少,MongoDB 需要检查他们返回的内容。 "nReturned" 和 "docsExamined" 的相同值表示 MongoDB 除了返回的文档之外没有触及其他文档。
- 为什么它不选择另一个方案,而是拒绝了它?
- 我在这里无法确定确切原因,但数据库确定使用它选择的索引在统计上更有效。
它可能取决于 collection 上存储的数据,例如在 SQL 实现中,即使 table 包含 BTREE 索引,有时规划器拒绝使用 BTREE 并扫描 table 而不是因为 table 包含一行或仅几行数据。 BTREE 查找不值得。
MongoDB 也会发生类似的事情。
唯一能准确告诉你原因的是为这种情况优化数据库的人。
- 我在这里无法确定确切原因,但数据库确定使用它选择的索引在统计上更有效。
这里的主要take-home是:有时候你需要放手。如果你希望每一个细节都准确无误,你将无法像具有健康无知水平的人那样取得成就。如果数据库运行得足够好,那说明你做的足够好。