MongoDB CRUD操作:快照查詢
文章目錄
- MongoDB CRUD操作:快照查詢
- 對比本地和快照的讀關注
- 舉例
- 從相同的時間點運行查詢
- 從過去某個時刻讀取數據的一致狀態
- 配置快照保留時間
- 磁盤空間和歷史記錄
使用快照查詢可以讀取最近某個時間點的數據,而且從MongoDB 5.0開始,可以使用讀關注"snapshot"來查詢二級節點上的數據,這顯著提高了應用程序讀取的通用性和彈性,而且無需像以前那樣,先創建數據的靜態副本,將其轉移到一個單獨的系統中,然后手動隔離這些長期運行的查詢,以免干擾正常運行工作負載。相反,可以在從一致的數據狀態讀取數據的同時,針對實時事務數據庫執行長期運行查詢。
在輔助節點上使用讀關注"snapshot"不會影響應用程序的寫工作量,只有應用程序讀取才會從隔離到輔助節點的長期運行查詢中受益。
下面的情況建議使用快照查詢:
- 執行多個相關查詢,并確保每個查詢從同一時間點讀取數據。
- 確保從過去某個時間點的一致數據狀態讀取數據。
對比本地和快照的讀關注
當使用MongoDB默認的本地讀取關注執行長時間運行的查詢時,查詢結果可能包含與查詢同時發生的寫入數據,因此,查詢可能會返回意外或不一致的結果。
為了避免這種情況,可以創建一個會話并指定讀關注"snapshot",通過讀取關注"snapshot",MongoDB可以通過快照隔離運行查詢,這意味著查詢只讀取最近單個時間點出現的數據。
舉例
從相同的時間點運行查詢
讀關注"snapshot"允許在會話中運行多個相關查詢,并確保每個查詢從相同的時間點讀取數據。
例如,動物收容所有一個寵物數據庫,其中包含每種寵物的集合。寵物數據庫有cats
和dogs
兩個集合,它們都包含一個adoptable
字段,表示寵物是否可以領養。其中,cats
集合中的文檔如下所示:
{"name": "Whiskers","color": "white","age": 10,"adoptable": true
}
如果要查詢所有集合中可供收養的寵物總數,為了提供一致的數據視圖,需要返回兩個集合同一時間點的數據。這時,就可以在會話中使用讀關注快照:
mongoc_client_session_t *cs = NULL;
mongoc_collection_t *cats_collection = NULL;
mongoc_collection_t *dogs_collection = NULL;
int64_t adoptable_pets_count = 0;
bson_error_t error;
mongoc_session_opt_t *session_opts;cats_collection = mongoc_client_get_collection (client, "pets", "cats");
dogs_collection = mongoc_client_get_collection (client, "pets", "dogs");/* 使用 pets.cats 和 pets.dogs 數據作為示例 */
if (!pet_setup (cats_collection, dogs_collection)) {goto cleanup;
}/* 啟動會話快照 */
session_opts = mongoc_session_opts_new ();
mongoc_session_opts_set_snapshot (session_opts, true);
cs = mongoc_client_start_session (client, session_opts, &error);
mongoc_session_opts_destroy (session_opts);
if (!cs) {MONGOC_ERROR ("Could not start session: %s", error.message);goto cleanup;
}/** 執行下面的聚合管道,將合計值累加到adoptable_pets_count。** adoptablePetsCount = db.cats.aggregate(* [ { "$match": { "adoptable": true } },* { "$count": "adoptableCatsCount" } ], session=s* ).next()["adoptableCatsCount"]** adoptablePetsCount += db.dogs.aggregate(* [ { "$match": { "adoptable": True} },* { "$count": "adoptableDogsCount" } ], session=s* ).next()["adoptableDogsCount"]** 注意,要實現這個操作必須要通過mongoc_collection_aggregate把客戶端會話傳遞給選項,即:** mongoc_client_session_append (cs, &opts, &error);* cursor = mongoc_collection_aggregate (* collection, MONGOC_QUERY_NONE, pipeline, &opts, NULL);*/
accumulate_adoptable_count (cs, cats_collection, &adoptable_pets_count);
accumulate_adoptable_count (cs, dogs_collection, &adoptable_pets_count);printf ("there are %" PRId64 " adoptable pets\n", adoptable_pets_count);
在上面的一系列操作中:
- 使用MongoClient()與MongoDB實例建立連接。
- 切換到
pets
數據庫。 - 建立會話,該命令指定了
snapshot=True
,因此會話使用讀關注快照。 - 對
pets
數據庫中的每個集合執行下列操作:- 使用
$match
過濾adoptable
字段為True
的文件。 - 使用
$count
返回已篩選文檔的計數。 - 用數據庫中的計數遞增
adoptablePetsCount
變量。
- 使用
- 打印
adoptablePetsCount
變量。
會話中的所有查詢都會讀取同一時間點出現的數據,因此,最終計數反映的是一致的數據快照。
注意:如果會話持續時間超過WiredTiger
歷史記錄保留期(默認為 300 秒),查詢就會出現快照過舊(SnapshotTooOld)錯誤。
從過去某個時刻讀取數據的一致狀態
讀關注快照可確保查詢能讀取最近某個時間點出現的數據。例如:一家在線鞋店有一個sales
集合,其中包含該店售出的每一件商品的數據。其中,sales
集合中的一個文檔如下所示:
{"shoeType": "boot","price": 30,"saleDate": ISODate("2022-02-02T06:01:17.171Z")
}
每天晚上都會運行一個查詢來查看當天售出了多少雙鞋。每日銷售查詢如下所示:
mongoc_client_session_t *cs = NULL;
mongoc_collection_t *sales_collection = NULL;
bson_error_t error;
mongoc_session_opt_t *session_opts;
bson_t *pipeline = NULL;
bson_t opts = BSON_INITIALIZER;
mongoc_cursor_t *cursor = NULL;
const bson_t *doc = NULL;
bool ok = true;
bson_iter_t iter;
int64_t total_sales = 0;sales_collection = mongoc_client_get_collection (client, "retail", "sales");/* 使用'retail.sales'示例數據 */
if (!retail_setup (sales_collection)) {goto cleanup;
}/* 開始快照會話 */
session_opts = mongoc_session_opts_new ();
mongoc_session_opts_set_snapshot (session_opts, true);
cs = mongoc_client_start_session (client, session_opts, &error);
mongoc_session_opts_destroy (session_opts);
if (!cs) {MONGOC_ERROR ("Could not start session: %s", error.message);goto cleanup;
}if (!mongoc_client_session_append (cs, &opts, &error)) {MONGOC_ERROR ("could not apply session options: %s", error.message);goto cleanup;
}pipeline = BCON_NEW ("pipeline","[","{","$match","{","$expr","{","$gt","[","$saleDate","{","$dateSubtract","{","startDate","$$NOW","unit",BCON_UTF8 ("day"),"amount",BCON_INT64 (1),"}","}","]","}","}","}","{","$count",BCON_UTF8 ("totalDailySales"),"}","]");cursor = mongoc_collection_aggregate (sales_collection, MONGOC_QUERY_NONE, pipeline, &opts, NULL);
bson_destroy (&opts);ok = mongoc_cursor_next (cursor, &doc);if (mongoc_cursor_error (cursor, &error)) {MONGOC_ERROR ("could not get totalDailySales: %s", error.message);goto cleanup;
}if (!ok) {MONGOC_ERROR ("%s", "cursor has no results");goto cleanup;
}ok = bson_iter_init_find (&iter, doc, "totalDailySales");
if (ok) {total_sales = bson_iter_as_int64 (&iter);
} else {MONGOC_ERROR ("%s", "missing key: 'totalDailySales'");goto cleanup;
}
在上面的一系列操作中:
- 使用
$match
和$expr
對saleDate
字段進行過濾。$expr
允許在$match
階段使用聚合表達式(如NOW
)。
- 使用
$gt
運算符和$dateSubtract
表達式,返回saleDate
大于執行查詢前一天的文檔。 - 使用
$count
返回匹配文檔的計數。計數存儲在totalDailySales
變量中。 - 指定讀關注快照,確保查詢從單一時間點讀取。
因為sales
集合比較大,所以運行查詢可能需要幾分鐘時間。由于是在線商店,銷售可能在一天中的任何時間發生。
例如下面的情況:
- 查詢在上午 12:00 開始執行。
- 一位顧客在上午 12:02 時購買了三雙鞋。
- 查詢在凌晨 12:04 執行完畢。
如果查詢不使用讀關注快照,那么在查詢開始和查詢結束之間發生的銷售額就會包含在查詢計數中,盡管這些銷售額并不是在報告當天發生的。這可能導致報告不準確,某些銷售額被計算兩次。
指定讀取 "快照 "后,查詢只返回查詢開始執行前不久數據庫中的數據。
如果查詢時間超過WiredTiger歷史保留期(默認情況下為 300 秒),查詢就會出錯,并顯示 SnapshotTooOld 錯誤。
配置快照保留時間
默認情況下,WiredTiger 存儲引擎會保留 300 秒的歷史記錄。使用 snapshot=true
的會話,從會話中的第一個操作開始到最后一個操作結束,總共可使用 300 秒。如果使用會話的時間更長,會話就會因 SnapshotTooOld 錯誤而失敗。同樣,如果使用讀關注快照來查詢數據,且查詢持續時間超過 300 秒,查詢就會失敗。
如果查詢或會話運行時間超過 300 秒,可以考慮延長快照保留時間,可以通過修改minSnapshotHistoryWindowInSeconds
參數增加快照保留期,例如,把minSnapshotHistoryWindowInSeconds
的值設置為 600 秒:
db.adminCommand( { setParameter: 1, minSnapshotHistoryWindowInSeconds: 600 } )
磁盤空間和歷史記錄
需要注意,增加 minSnapshotHistoryWindowInSeconds
的值會增加磁盤使用量,因為服務器必須在指定的時間窗口內保持較早修改值的歷史記錄。磁盤空間的使用量取決于工作負載,工作量越大,需要的磁盤空間就越大。