MongoDB 计算空值(或 {$exists: false})时非常慢
MongoDB extremely slow at counting null values (or {$exists: false})
我在 VPS 上有一个 Mongo 服务器 运行,内存为 16GB(尽管使用磁盘的 IO 可能很慢)。
我收集了大约 3500 万条无法放入主内存的记录(db.stats()
报告了 35GB 的 size
和 14GB 的 storageSize
,但是totalIndexSize
报告的 1.7GB 应该很适合。
有一个特定的字段 bg
我正在查询哪些字段可以具有值 true
或完全不存在(请不要讨论这是否是最好的数据表示 – 我仍然认为 Mongo 表现得很奇怪)。此字段使用报告大小为 146MB 的非稀疏索引编制索引。
我正在使用具有默认缓存大小的 WiredTiger 存储引擎(因此它应该在 8GB 左右)。
我正在计算缺少 bg
字段的记录数。
计算 true
个值的速度相当快(几秒钟):
> db.entities.find({bg: true}).count()
8300677
但是查询缺失值非常慢(大约 5 分钟):
> db.entities.find({bg: null}).count()
27497706
在我看来,explain()
看起来不错:
> db.entities.find({bg: null}).explain()
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "testdb.entities",
"indexFilterSet" : false,
"parsedQuery" : {
"bg" : {
"$eq" : null
}
},
"winningPlan" : {
"stage" : "FETCH",
"filter" : {
"bg" : {
"$eq" : null
}
},
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"bg" : 1
},
"indexName" : "bg_1",
"isMultiKey" : false,
"direction" : "forward",
"indexBounds" : {
"bg" : [
"[null, null]"
]
}
}
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "mongo01",
"port" : 27017,
"version" : "3.0.3",
"gitVersion" : "b40106b36eecd1b4407eb1ad1af6bc60593c6105"
},
"ok" : 1
}
然而,即使在多次调用之后,查询仍然非常缓慢。其他不同值的计数查询很快:
> db.entities.find({bg: "foo"}).count()
0
> db.entities.find({}).count()
35798383
我觉得这有点奇怪,因为我的理解是非稀疏索引中缺失的字段简单地存储为null
,所以使用null
的计数查询应该类似于计数一个实际值(或者可能是正值的三倍,如果它必须计算更多的索引条目或其他东西)。事实上,this answer 报告称,与涉及 null
值和 .count()
的类似查询相比,速度有了巨大的改进。我能想到的唯一区别点是 WiredTiger。
任何人都可以解释为什么我的查询计算空值如此缓慢或者我可以做些什么来解决它(除了从总数中明显减去 true
计数,这会工作正常但是不能满足我的好奇心)?
这是预期的行为,请参阅:https://jira.mongodb.org/browse/SERVER-18653。对我来说似乎是一个奇怪的电话,但是你去吧,我敢肯定有程序员比我更了解 MongoDB 是负责任的。
您需要使用不同的值来表示空值。我想这将取决于您使用该字段的目的。在我的例子中,它是一个外部引用,所以我将开始使用 false 来表示 null。如果您使用它来存储布尔值,那么您可能需要使用 "null"、-1、0 等
我在 VPS 上有一个 Mongo 服务器 运行,内存为 16GB(尽管使用磁盘的 IO 可能很慢)。
我收集了大约 3500 万条无法放入主内存的记录(db.stats()
报告了 35GB 的 size
和 14GB 的 storageSize
,但是totalIndexSize
报告的 1.7GB 应该很适合。
有一个特定的字段 bg
我正在查询哪些字段可以具有值 true
或完全不存在(请不要讨论这是否是最好的数据表示 – 我仍然认为 Mongo 表现得很奇怪)。此字段使用报告大小为 146MB 的非稀疏索引编制索引。
我正在使用具有默认缓存大小的 WiredTiger 存储引擎(因此它应该在 8GB 左右)。
我正在计算缺少 bg
字段的记录数。
计算 true
个值的速度相当快(几秒钟):
> db.entities.find({bg: true}).count()
8300677
但是查询缺失值非常慢(大约 5 分钟):
> db.entities.find({bg: null}).count()
27497706
在我看来,explain()
看起来不错:
> db.entities.find({bg: null}).explain()
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "testdb.entities",
"indexFilterSet" : false,
"parsedQuery" : {
"bg" : {
"$eq" : null
}
},
"winningPlan" : {
"stage" : "FETCH",
"filter" : {
"bg" : {
"$eq" : null
}
},
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"bg" : 1
},
"indexName" : "bg_1",
"isMultiKey" : false,
"direction" : "forward",
"indexBounds" : {
"bg" : [
"[null, null]"
]
}
}
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "mongo01",
"port" : 27017,
"version" : "3.0.3",
"gitVersion" : "b40106b36eecd1b4407eb1ad1af6bc60593c6105"
},
"ok" : 1
}
然而,即使在多次调用之后,查询仍然非常缓慢。其他不同值的计数查询很快:
> db.entities.find({bg: "foo"}).count()
0
> db.entities.find({}).count()
35798383
我觉得这有点奇怪,因为我的理解是非稀疏索引中缺失的字段简单地存储为null
,所以使用null
的计数查询应该类似于计数一个实际值(或者可能是正值的三倍,如果它必须计算更多的索引条目或其他东西)。事实上,this answer 报告称,与涉及 null
值和 .count()
的类似查询相比,速度有了巨大的改进。我能想到的唯一区别点是 WiredTiger。
任何人都可以解释为什么我的查询计算空值如此缓慢或者我可以做些什么来解决它(除了从总数中明显减去 true
计数,这会工作正常但是不能满足我的好奇心)?
这是预期的行为,请参阅:https://jira.mongodb.org/browse/SERVER-18653。对我来说似乎是一个奇怪的电话,但是你去吧,我敢肯定有程序员比我更了解 MongoDB 是负责任的。
您需要使用不同的值来表示空值。我想这将取决于您使用该字段的目的。在我的例子中,它是一个外部引用,所以我将开始使用 false 来表示 null。如果您使用它来存储布尔值,那么您可能需要使用 "null"、-1、0 等