【问题标题】:mongodb -- count() much slower than find()?mongodb -- count() 比 find() 慢得多?
【发布时间】:2016-06-29 10:40:20
【问题描述】:

我正在使用 mongoose 来计算与某个查询匹配的文档数。我对这个查询的索引是:{createdAt: -1, status: -1, oId: -1}

Mongo 版本为 3.2,集合中的文档数量约为 175 万。

如果我这样做:

model.find({
                        createdAt: {'$gte': threeMonths, '$lt': today},
                        status: {'$in': model.STATUS_SET}
                    }).select({_id: 0, status: 1}).count().then((c) => result[alias] = c)

需要 2 分钟以上。但如果我这样做:

model.find({
                createdAt: {'$gte': threeMonths, '$lt': today},
                status: {'$in': model.STATUS_SET}
            }).select({_id: 0, status: 1}).lean().then((c) => result[alias] = c.length)

然后大约需要 2.5 秒。

我做错了什么?我可以做些什么来帮助加快速度?

编辑:解释日志。

计数:

"executionStats" : {
		"executionSuccess" : true,
		"nReturned" : 0,
		"executionTimeMillis" : 82671,
		"totalKeysExamined" : 1749689,
		"totalDocsExamined" : 1643722,
		"executionStages" : {
			"stage" : "COUNT",
			"nReturned" : 0,
			"executionTimeMillisEstimate" : 80960,
			"works" : 1750066,
			"advanced" : 0,
			"needTime" : 1749689,
			"needFetch" : 376,
			"saveState" : 14662,
			"restoreState" : 14662,
			"isEOF" : 1,
			"invalidates" : 0,
			"nCounted" : 1643722,
			"nSkipped" : 0,
			"inputStage" : {
				"stage" : "FETCH",
				"nReturned" : 1643722,
				"executionTimeMillisEstimate" : 80890,
				"works" : 1750065,
				"advanced" : 1643722,
				"needTime" : 105967,
				"needFetch" : 376,
				"saveState" : 14662,
				"restoreState" : 14662,
				"isEOF" : 1,
				"invalidates" : 0,
				"docsExamined" : 1643722,
				"alreadyHasObj" : 0,
				"inputStage" : {
					"stage" : "IXSCAN",
					"nReturned" : 1643722,
					"executionTimeMillisEstimate" : 3800,
					"works" : 1749689,
					"advanced" : 1643722,
					"needTime" : 105967,
					"needFetch" : 0,
					"saveState" : 14662,
					"restoreState" : 14662,
					"isEOF" : 1,
					"invalidates" : 0,
					"keyPattern" : {
						"createdAt" : -1,
						"status" : -1,
						"oId" : -1
					},
					"indexName" : "moderatedContent",
					"isMultiKey" : false,
					"direction" : "forward",
					"indexBounds" : {
						"createdAt" : [
							"(new Date(1467195213000), new Date(1459246413000)]"
						],
						"status" : [
							"[\"UNDECIDED\", \"UNDECIDED\"]",
							"[\"APPROVED\", \"APPROVED\"]"
						],
						"oId" : [
							"[MaxKey, MinKey]"
						]
					},
					"keysExamined" : 1749689,
					"dupsTested" : 0,
					"dupsDropped" : 0,
					"seenInvalidated" : 0,
					"matchTested" : 0
				}
			}
		},
		"allPlansExecution" : [ ]
	}

用于查找。

"executionStats" : {
		"executionSuccess" : true,
		"nReturned" : 1643722,
		"executionTimeMillis" : 1216,
		"totalKeysExamined" : 1749689,
		"totalDocsExamined" : 0,
		"executionStages" : {
			"stage" : "PROJECTION",
			"nReturned" : 1643722,
			"executionTimeMillisEstimate" : 1080,
			"works" : 1749690,
			"advanced" : 1643722,
			"needTime" : 105967,
			"needFetch" : 0,
			"saveState" : 13669,
			"restoreState" : 13669,
			"isEOF" : 1,
			"invalidates" : 0,
			"transformBy" : {
				"_id" : 0,
				"status" : 1
			},
			"inputStage" : {
				"stage" : "IXSCAN",
				"nReturned" : 1643722,
				"executionTimeMillisEstimate" : 920,
				"works" : 1749690,
				"advanced" : 1643722,
				"needTime" : 105967,
				"needFetch" : 0,
				"saveState" : 13669,
				"restoreState" : 13669,
				"isEOF" : 1,
				"invalidates" : 0,
				"keyPattern" : {
					"createdAt" : -1,
					"status" : -1,
					"oId" : -1
				},
				"indexName" : "moderatedContent",
				"isMultiKey" : false,
				"direction" : "forward",
				"indexBounds" : {
					"createdAt" : [
						"(new Date(1467195213000), new Date(1459246413000)]"
					],
					"status" : [
						"[\"UNDECIDED\", \"UNDECIDED\"]",
						"[\"APPROVED\", \"APPROVED\"]"
					],
					"oId" : [
						"[MaxKey, MinKey]"
					]
				},
				"keysExamined" : 1749689,
				"dupsTested" : 0,
				"dupsDropped" : 0,
				"seenInvalidated" : 0,
				"matchTested" : 0
			}
		}
	}

对于光标中第一个带有 .explain() 的帖子:

"executionStats" : {
		"executionSuccess" : true,
		"nReturned" : 0,
		"executionTimeMillis" : 89191,
		"totalKeysExamined" : 1749689,
		"totalDocsExamined" : 1643722,
		"executionStages" : {
			"stage" : "COUNT",
			"nReturned" : 0,
			"executionTimeMillisEstimate" : 83400,
			"works" : 1751709,
			"advanced" : 0,
			"needTime" : 1749689,
			"needFetch" : 2019,
			"saveState" : 15648,
			"restoreState" : 15648,
			"isEOF" : 1,
			"invalidates" : 0,
			"nCounted" : 1643722,
			"nSkipped" : 0,
			"inputStage" : {
				"stage" : "FETCH",
				"nReturned" : 1643722,
				"executionTimeMillisEstimate" : 83260,
				"works" : 1751708,
				"advanced" : 1643722,
				"needTime" : 105967,
				"needFetch" : 2019,
				"saveState" : 15648,
				"restoreState" : 15648,
				"isEOF" : 1,
				"invalidates" : 0,
				"docsExamined" : 1643722,
				"alreadyHasObj" : 0,
				"inputStage" : {
					"stage" : "IXSCAN",
					"nReturned" : 1643722,
					"executionTimeMillisEstimate" : 8290,
					"works" : 1749689,
					"advanced" : 1643722,
					"needTime" : 105967,
					"needFetch" : 0,
					"saveState" : 15648,
					"restoreState" : 15648,
					"isEOF" : 1,
					"invalidates" : 0,
					"keyPattern" : {
						"createdAt" : -1,
						"status" : -1,
						"oId" : -1
					},
					"indexName" : "moderatedContent",
					"isMultiKey" : false,
					"direction" : "forward",
					"indexBounds" : {
						"createdAt" : [
							"(new Date(1467195213000), new Date(1459246413000)]"
						],
						"status" : [
							"[\"UNDECIDED\", \"UNDECIDED\"]",
							"[\"APPROVED\", \"APPROVED\"]"
						],
						"oId" : [
							"[MaxKey, MinKey]"
						]
					},
					"keysExamined" : 1749689,
					"dupsTested" : 0,
					"dupsDropped" : 0,
					"seenInvalidated" : 0,
					"matchTested" : 0
				}
			}
		}
	}

【问题讨论】:

  • 使用 explain 方法检查发生了什么。你能同时发布两个解释日志吗?
  • @TiagoBértolo 已发布。
  • 您的解释有问题。他不应该从计数开始。
  • 你能发布这个命令的解释吗? model.explain().find({createdAt: {'$gte': threeMonths, '$lt': today},status: {'$in': model.STATUS_SET}}).count()
  • 这个不提供执行统计数据。

标签: javascript node.js mongodb mongoose


【解决方案1】:

答案的关键是

//count
"totalDocsExamined" : 1643722,

//find
"totalDocsExamined" : 0,

find 查询完全在索引上操作,不读取任何单个文档,而 count 查询实际上从 db 中读取每个文档。

原因是,您的查找查询使用了lean() 选项。据猫鼬doc

启用了精简选项的查询返回的文档是纯 JavaScript 对象,而不是 MongooseDocuments。他们没有应用保存方法、getter/setter 或其他 Mongoose 魔法。

最重要的是,在您的精益查找查询中,您只有 select()ing idstatus,这似乎是预测的 ("transformBy" ...),因此整个查询变成了 @987654322 @ 并且不需要阅读任何文件来满足请求。

【讨论】:

  • 我明白这一点。但我不禁想知道,一旦完成 find 查询,mongo 是否有任何方法只返回游标的大小?此外,迭代结果集真的需要 90 秒吗?
  • 在 mongoshell 中它只是 db.yourCollection.find(...).count(),在 mongoose 中它似乎是 model.find({query}).select({projection}).lean().count() - 但我还没有测试过这个......此外,估计是以毫秒为单位的,所以它是 90 秒
  • 我的意思是,如果我可以在 3 秒内检索所有带有覆盖查询的文档,然后只获取结果数组的长度,那么为什么 mongo 不能呢?我的意思是,我什至会这样,但我没有足够的内存。
  • 在检索文档时,覆盖查询的性能非常好,但您不需要它来计算。我对猫鼬不是很熟悉,但我认为在你的情况下find({query}) 获取文件,count 在猫鼬而不是 mongodb 中运行结果 - 因此从lean() 和覆盖查询中获利。我假设只有model.count({query}) 会在 mongodb 上执行计数,应该没问题。
  • 我认为我们有误会。我正在做的是一个简单的 find(query) 导致 mongodb 执行覆盖查询,然后我所要做的就是检索结果数组的 .length 属性。约 3 秒,但由于要获取 160 万条记录,因此非常费力。如果我正在做find(query).count(),无论是在猫鼬还是在 mongodb 中,都需要大约 90 秒。我无法理解的是,为什么 mongo 不能简单地在服务器端返回“.length”?很明显我遗漏了一些关键点,但我不确定那点是什么。
【解决方案2】:

Find - 在集合中查找文档并将光标返回到选定的文档。游标是指向查询结果集的指针。客户端可以遍历游标来检索结果。

Count - count() 等价于db.collection.find(query).count()

Count 实际上是一个游标方法,shell 只是提供了一个快捷方式。在 Shell 中,我们有 count 的快捷方式,如下所示db.collection.count(query)

原因:在 find 操作中,我们没有对结果集进行迭代,而在 count 操作中,我们正在对结果集进行操作。这会导致时间延迟。

https://docs.mongodb.com/manual/reference/method/db.collection.find/ https://docs.mongodb.com/manual/reference/method/db.collection.count/

【讨论】:

  • 所以没有办法避免这种迭代?
  • @nainy - 添加了更多关于答案的信息。希望它能澄清您的疑问。
猜你喜欢
  • 2016-03-06
  • 2015-07-22
  • 2012-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-16
  • 2012-05-12
相关资源
最近更新 更多