本篇分享 MongoDB 在批次查訊大量數據時的小技巧
在查詢小筆數據時,往往我們就是用 find().toArray()
直接回傳結果,需要簡單的分頁就搭配 limit() / skip()
即可完成
但如果需要分析數據跑過整個 collection,不可能用 find() 一次拉回所有的資料,透過 skip() 批次處理,如果 collection 資料不多還好,如果幾十萬筆、幾百萬筆資料效能回非常的低落,因為 skip 的話每次查詢 DB 都必須要重頭開始跳過指定筆數資料,才能回傳,所以查詢時間會隨著筆數增加而趨近於指數倍增長
目前已知有兩種做法,可以有效輪詢整個 collection 而不拖垮 DB 效能
一種是 Cursor
,但是在實務上我個人並沒有採用,原因是 Cursor 每次只能透過 .next()
取得一筆資料,如果希望一次拉個上千筆資料處理就無法做到;
另一種是本次要分享的方式,透過遞迴查詢,保持性能情況下跑完整個 Collection,實務上每天應用在有千萬筆資料的 Collection 而沒有太大的問題
遞回查詢
說穿了其實很簡單,透過有建立 index 的索引,按照遞增或遞減排序,每批次取出部分數據後,下次查詢的條件改為取出數據的最後一位,一路到 Collection 結束
以下是程式碼部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
| const MongoClient = require('mongodb').MongoClient;
const dbUrl = "mongodb://127.0.0.1:27017";
MongoClient.connect(dbUrl, async function (err, client) {
console.log(err)
const testDB = client.db("test");
const userCollection = testDB.collection("users");
const result = await iterateCollection({
sourceCollection: userCollection,
query: {
age: 20
},
batchSize: 10,
order: "asc"
});
console.log(result);
client.close();
});
async function iterateCollection({
sourceCollection,
query,
batchSize,
order
}) {
let result = [];
let sort = {
_id: -1
}
let _query = {
...query
};
if (order === "asc") {
sort = {
_id: 1
}
}
while (true) {
const queryResults = await sourceCollection.find(_query).limit(batchSize).sort(sort).toArray();
if(queryResults.length === 0){
break;
}
_query._id = {
$lt: queryResults[queryResults.length - 1]._id
}
if (order === "asc") {
_query._id = {
$gt: queryResults[queryResults.length - 1]._id
}
}
// process data and push to result
// result.push(queryResults.map(result => result._id));
}
return result;
}
|