【发布时间】:2019-10-17 18:06:52
【问题描述】:
当我使用 Node.js 运行并发 MongoDb 查询时,第二个查询总是需要大约 2 秒才能返回。使用explain(),executionTimeMillis 总是返回 0ms,这是绝对正常的,因为我的测试集合只有 2 个条目。这是我的简化测试用例:
'use strict'
const { MongoClient } = require('mongodb')
const main = async () => {
const client = new MongoClient('mongodb://admin:123456@localhost:27017/', {
useNewUrlParser: true,
useUnifiedTopology: true,
})
await client.connect()
const db = client.db('test')
const numbers = db.collection('numbers')
const promises = []
console.time()
for (let i = 0; i < 3; i++) {
promises.push(numbers.find({ number: i }).explain())
}
for (const promise of promises) {
console.log(await promise)
console.timeLog()
}
console.timeEnd()
await client.close()
}
main()
输出:
{
queryPlanner: {
plannerVersion: 1,
namespace: 'test.numbers',
indexFilterSet: false,
parsedQuery: { number: [Object] },
winningPlan: { stage: 'FETCH', inputStage: [Object] },
rejectedPlans: []
},
executionStats: {
executionSuccess: true,
nReturned: 1,
executionTimeMillis: 0,
totalKeysExamined: 1,
totalDocsExamined: 1,
executionStages: {
stage: 'FETCH',
nReturned: 1,
executionTimeMillisEstimate: 0,
works: 2,
advanced: 1,
needTime: 0,
needYield: 0,
saveState: 0,
restoreState: 0,
isEOF: 1,
invalidates: 0,
docsExamined: 1,
alreadyHasObj: 0,
inputStage: [Object]
},
allPlansExecution: []
},
serverInfo: {
host: 'DESKTOP-C7CAL9N',
port: 27017,
version: '4.0.10',
gitVersion: 'c389e7f69f637f7a1ac3cc9fae843b635f20b766'
},
ok: 1
}
default: 32.252ms
{
queryPlanner: {
plannerVersion: 1,
namespace: 'test.numbers',
indexFilterSet: false,
parsedQuery: { number: [Object] },
winningPlan: { stage: 'FETCH', inputStage: [Object] },
rejectedPlans: []
},
executionStats: {
executionSuccess: true,
nReturned: 1,
executionTimeMillis: 0,
totalKeysExamined: 1,
totalDocsExamined: 1,
executionStages: {
stage: 'FETCH',
nReturned: 1,
executionTimeMillisEstimate: 0,
works: 2,
advanced: 1,
needTime: 0,
needYield: 0,
saveState: 0,
restoreState: 0,
isEOF: 1,
invalidates: 0,
docsExamined: 1,
alreadyHasObj: 0,
inputStage: [Object]
},
allPlansExecution: []
},
serverInfo: {
host: 'DESKTOP-C7CAL9N',
port: 27017,
version: '4.0.10',
gitVersion: 'c389e7f69f637f7a1ac3cc9fae843b635f20b766'
},
ok: 1
}
default: 2042.929ms
{
queryPlanner: {
plannerVersion: 1,
namespace: 'test.numbers',
indexFilterSet: false,
parsedQuery: { number: [Object] },
winningPlan: { stage: 'FETCH', inputStage: [Object] },
rejectedPlans: []
},
executionStats: {
executionSuccess: true,
nReturned: 0,
executionTimeMillis: 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: [Object]
},
allPlansExecution: []
},
serverInfo: {
host: 'DESKTOP-C7CAL9N',
port: 27017,
version: '4.0.10',
gitVersion: 'c389e7f69f637f7a1ac3cc9fae843b635f20b766'
},
ok: 1
}
default: 2062.851ms
default: 2063.513ms
如果我依次运行查询,则每个查询只需几毫秒即可返回。那为什么是2秒的响应时间呢?
编辑:
在第一个 for 循环中,我创建/运行了“并发”查询 promises.push(numbers.find({ number: i }).explain())。在第二个 for 循环中,我等待一个又一个的 Promise 解决,但这并不意味着一个 Promise 必须等到前一个解决后才能开始其工作。
为避免误解,我对我的代码做了一些改动,将两个 for 循环替换为:
for (let i = 0; i < 3; i++) {
promises.push(
numbers
.find({ number: i })
.explain()
.then(result => {
// console.log(result)
console.log('query index:', i)
console.timeLog()
})
)
}
await Promise.all(promises)
输出:
query index: 0
default: 22.040ms
query index: 2
default: 2032.921ms
query index: 1
default: 2034.682ms
default: 2035.260ms
编辑 2:
为了进一步说明,我使用标签来表示计时器。
for (let i = 0; i < 3; i++) {
console.time(`query index: ${ i }`)
promises.push(
numbers
.find({ number: i })
.explain()
.then(result => {
// console.log(result)
console.timeEnd(`query index: ${ i }`)
})
)
}
await Promise.all(promises)
输出:
query index: 0: 12.692ms
query index: 1: 2015.143ms
query index: 2: 2015.310ms
【问题讨论】:
-
您的查询以瀑布形式执行,第一个查询需要 1 秒,然后第二个查询需要 2 秒(从开始检查点开始)...
-
@hoangdv 请看我的编辑。第一个查询只需 20 毫秒即可完成。预计第二个和第三个在 ~20ms 或最多 ~50ms 内完成。
-
让我们使用
console.time和label参数来明确情况。标签是循环的索引,在将查询推送到promise数组之前需要调用console.time(i),console.endTime(i)需要在查询的.then函数中设置。 -
将 MongoClient 的
poolSize设置为1,然后重试。 -
真的 :ooo 谢谢。我想我现在明白了一些事情。如果我们在之前的查询处于活动状态时运行新查询,mongo 客户端会创建一个新连接(只要当前活动连接的数量
< poolSize),这将在我的机器上花费 2 秒。在此之前,我尝试将poolSize设置为更高的数字,甚至没有考虑poolSize=1! :d 谢谢。请添加上述评论作为答案,以便我接受。