欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

MongoDB中優(yōu)雅刪除大量數據的三種方式

 更新時間:2021年10月26日 09:28:14   作者:iVictor  
最近接到一個任務,線上的mongodb積累了大量的無用數據,導致宕機,現(xiàn)在對里面的數據進行批量刪除,所以這篇文章主要給大家介紹了關于MongoDB中優(yōu)雅刪除大量數據的三種方式,需要的朋友可以參考下

刪除大量數據,無論是在哪種數據庫中,都是一個普遍性的需求。除了正常的業(yè)務需求,我們需要通過這種方式來為數據庫“瘦身”。

為什么要“瘦身”呢?

1、表的數據量到達一定量級后,數據量越大,表的查詢性能會越差。

畢竟數據量越大,B+樹的層級會越高,需要的IO也會越多。

2、表的數據有冷熱之分,將很多無用或很少用到的數據存儲在數據庫中會消耗數據庫的資源。

譬如會占用緩存;會增加備份集的大小,進而影響備份的恢復時間等。

所以,對于那些無用的數據,我們會定期刪除。

對于那些很少用到的數據,則會定期歸檔。歸檔,一般是將數據寫入到歸檔實例或抽取到大數據組件中。歸檔完畢后,會將對應的數據從原實例中刪除。

一般來說,這種刪除操作涉及的數據量都比較大。

對于這類刪除操作,很多開發(fā)童鞋的實現(xiàn)就是一個簡單的DELETE操作。看上去,簡單明了,干凈利落。

但是,這種方式,危害性卻極大。

以 MySQL 為例:

  • 會造成大事務
    大事務會導致主從延遲,而主從延遲又會影響數據庫的高可用切換。
  • 回滾表空間會不斷膨脹
    在MySQL 8.0之前,回滾表空間默認是放到系統(tǒng)表空間中,而系統(tǒng)表空間一旦”膨脹“,就不會收縮。
  • 鎖定的記錄多
    相對而言,更容易導致鎖等待。

即使是分布式數據庫,如TiDB,如果一次刪除了大量數據,這批數據在進行Compaction時有可能會觸發(fā)流控。

所以,對于線上的大規(guī)模刪除操作,建議分而治之。具體來說,就是批量刪除,每次只刪除一部分數據,分多次執(zhí)行。

就如何刪除大量數據,接下來我們看看MongoDB中的落地方案。

本文主要包括以下四部分內容。

  1. MongoDB中刪除數據的三種方式。
  2. 三種方式的執(zhí)行效率對比。
  3. 通過Write Concern規(guī)避主從延遲。
  4. 刪除過程中碰到的Bug。

MongoDB中刪除數據的三種方式

在MongoDB中刪除數據,可通過以下三種方式:

db.collection.remove()

刪除單個文檔或滿足條件的所有文檔。

db.collection.deleteMany()

刪除滿足條件的所有文檔。

db.collection.bulkWrite()

批量操作接口,可執(zhí)行批量插入、更新、刪除操作。

接下來,對比下這三種方式的執(zhí)行效率。

三種方式的執(zhí)行效率對比

環(huán)境:MongoDB 3.4.4,副本集。

測試思路:分別使用 remove、deleteMany、bulkWrite 刪除 10w 條記錄(每批刪除 5000 條),交叉執(zhí)行 5 次。

1. remove

// delete_date是刪除條件
var delete_date = new Date("2021-01-01T00:00:00.000Z");
// 獲取程序開始時間
var start_time = new Date();
// 獲取滿足刪除條件的記錄數
rows = db.test_collection.find({"createtime": {$lt: delete_date}}).count()
print("total rows:", rows);
// 定義每批需要刪除的記錄數
var batch_num = 5000;
while (rows > 0) {
    // rows也可理解為剩余記錄數
    // 如果剩余記錄數小于batch_num,則將剩余記錄數賦值給batch_num
    // 為什么要怎么做,后面會提到。
    if (rows < batch_num) {
        batch_num = rows;
    }
    // 獲取滿足刪除條件的最小的5000個_id(ObjectID)
    var cursor = db.test_collection.find({"createtime": {$lt: delete_date}}, {"_id": 1}).sort({"_id": 1}).limit(batch_num);
    rows = rows - batch_num;
    cursor.forEach(function (each_row) {
        // 通過remove刪除記錄,這里指定了"justOne": true,每次只能刪除一條記錄。
        // 為了避免誤刪除,這里同時指定了主鍵和刪除條件。
        db.test_collection.remove({'_id': each_row["_id"], "createtime": {'$lt': delete_date}}, {
            "justOne": true,
            w: "majority"
        })
    });
}
// 獲取程序結束時間
var end_time = new Date();
// 兩者的差值,即為程序執(zhí)行時長
print((end_time - start_time) / 1000);

2. deleteMany

實例思路同remove類似,只不過會將待刪除的_id放到一個數組中,最后再通過deleteMany一次性刪除。

具體代碼如下:

var delete_date = new Date("2021-01-01T00:00:00.000Z");
var start_time = new Date();
rows = db.test_collection.find({"createtime": {$lt: delete_date}}).count()
print("total rows:", rows);
var batch_num = 5000;
while (rows > 0) {
    if (rows < batch_num) {
        batch_num = rows;
    }
    var cursor = db.test_collection.find({"createtime": {$lt: delete_date}}, {"_id": 1}).sort({"_id": 1}).limit(batch_num);
    rows = rows - batch_num;
    var delete_ids = [];
    // 將滿足條件的主鍵值放入到數組中。
    cursor.forEach(function (each_row) {
        delete_ids.push(each_row["_id"]);
    });
    // 通過deleteMany一次刪除5000條記錄。
    db.test_collection.deleteMany({
        '_id': {"$in": delete_ids},
        "createTime": {'$lt': delete_date}
    },{w: "majority"})
}
var end_time = new Date();
print((end_time - start_time) / 1000);

3. bulkWrite

實現(xiàn)思路同deleteMany類似,也是將待刪除的_id放到一個數組中,最后再調用bulkWrite進行刪除。

具體代碼如下:

var delete_date = new Date("2021-01-01T00:00:00.000Z");
var start_time = new Date();
rows = db.test_collection.find({"createtime": {$lt: delete_date}}).count()
print("total rows:", rows);
var batch_num = 5000;
while (rows > 0) {
    if (rows < batch_num) {
        batch_num = rows;
    }
    var cursor = db.test_collection.find({"createtime": {$lt: delete_date}}, {"_id": 1}).sort({"_id": 1}).limit(batch_num);
    rows = rows - batch_num;
    var delete_ids = [];
    cursor.forEach(function (each_row) {
        delete_ids.push(each_row["_id"]);
    });
    db.test_collection.bulkWrite(
        [
            {
                deleteMany: {
                    "filter": {
                        '_id': {"$in": delete_ids},
                        "createTime": {'$lt': delete_date}
                    }
                }
            }
        ],
        {ordered: false},
        {writeConcern: {w: "majority", wtimeout: 100}}
    )
}
var end_time = new Date();
print((end_time - start_time) / 1000);

接下來,看看三者的執(zhí)行效率。

刪除方式 平均執(zhí)行時間(s) 第一次 第二次 第三次 第四次 第五次
remove 47.341 49.606 48.487 49.314 47.572 41.727
deleteMany 16.951 16.566 18.669 17.932 18.66 12.928
bulkWrite 16.476 17.247 14.181 16.151 18.403 16.397

結合表中的數據,可以看出,

  1. 執(zhí)行最慢的是remove,執(zhí)行最快的是bulkWrite,前者差不多是后者的 2.79 倍。
  2. deleteMany 和 bulkWrite 的執(zhí)行效率差不多,但就語法而言,前者比后者簡潔。

所以線上如果要刪除大量數據,推薦使用 deleteMany + ObjectID 進行批量刪除。

通過 Write Concern 規(guī)避主從延遲

雖然是批量刪除,但在MySQL中,如果沒控制好節(jié)奏,還是很容易導致主從延遲。在MongoDB中,其實也有類似的擔憂,不過我們可以通過 Write Concern 進行規(guī)避。

Write Concern,可理解為寫安全策略,簡單來說,它定義了一個寫操作,需要在幾個節(jié)點上應用(Apply)完,才會給客戶端反饋。

看下面這個原理圖。

 

圖中是一個一主兩從的副本集,設置了w: "majority",代表一個寫操作,需要等待副本集中絕大多數節(jié)點(本例中是兩個)應用完,才能給客戶端反饋。

在前面的代碼中,無論是remove,deleteMany還是bulkWrite方法,都設置了w: "majority"。

之所以這樣設置,一方面是為了保證數據的安全性,畢竟刪除操作能在多個節(jié)點落盤,另一方面,還能有效降低批量操作可能導致的主從延遲風險。

Write Concern的完整語法如下,

{ w: <value>, j: <boolean>, wtimeout: <number> }

其中,

w:指定節(jié)點數或tags。其有如下取值:

  • <number>:顯式指定節(jié)點數量。

設置為0,無需Server端反饋。

設置為1,只需Primary節(jié)點反饋。

設置為2,在副本集中,需要一個Primary節(jié)點(Primary節(jié)點必需)和一個Secondary節(jié)點反饋。

需要注意的是,這里的Secondary節(jié)點必須是數據節(jié)點,可以是隱藏節(jié)點、延遲節(jié)點或Priority為 0 的節(jié)點,但仲裁節(jié)點(Arbiter)絕對不行。

一般來說,設置的節(jié)點數越多,數據越安全,寫入的效率也會越低。

  • majority:副本集大多數節(jié)點。

與上面不一樣的是,這里的Secondary節(jié)點不僅要求是數據節(jié)點,它的votes(members[n].votes)還必須大于0。

  • <custom write concern name>:指定tags。

tag,顧名思義,是給節(jié)點打標簽。常用于多數據中心部署場景。

如一個集群,有5個節(jié)點,跨機房部署。其中3個節(jié)點在A機房,另外2個節(jié)點在B機房,因為對數據的安全性、一致性要求很高,我們希望寫操作至少能在A機房的2個節(jié)點落盤,B機房的1個節(jié)點落盤。

對于這種個性化的需求,只有通過tags才能實現(xiàn)。

具體使用,可參考:https://docs.mongodb.com/manual/tutorial/configure-replica-set-tag-sets/#configure-custom-write-concern。

j:是否需要等待對應操作的日志持久化到磁盤中。

在MongoDB中,一個寫操作會涉及到三個動作:更新數據,更新索引,寫入oplog,這三個動作要么全部成功,要么全部失敗,這也是MongoDB單行事務的由來。

對于每個寫操作,WiredTiger都會記錄一條日志到 journal 中。

日志在寫入journal之前,會首先寫入到 journal buffer(最大128KB)中。

Journal buffer會在以下場景持久化到 journal 文件中:

  • 副本集中,當有操作等待oplog時。

這類操作包括:針對oplog最新位置點的掃描查詢;Causally consistent session中的讀操作;對于Secondary節(jié)點,每次批量應用oplog后。

  • Write Concern 設置了 j: true。
  • 每100ms。

由 storage.journal.commitIntervalMs 參數指定。

  • 創(chuàng)建新的 journal 文件時。

當 journal 文件的大小達到100MB時會自動創(chuàng)建一個新的journal 文件。

wtimeout:超時時長,單位ms。

不設置或設置為0,命令在執(zhí)行的過程中,如果遇到了鎖等待或節(jié)點數不滿足要求,會一直阻塞。

如果設置了時間,命令在這個時間內沒有執(zhí)行成功,則會超時報錯,具體報錯信息如下:

rs:PRIMARY> db.test.insert({"a": 1}, {writeConcern: {w: "majority", wtimeout: 100}})
WriteResult({
    "nInserted": 1,
    "writeConcernError": {
        "code": 64,
        "codeName": "WriteConcernFailed",
        "errInfo": {
            "wtimeout": true
        },
        "errmsg": "waiting for replication timed out"
    }
})

刪除過程中遇到的Bug

其實,最開始的刪除程序是下面這個版本。

var delete_date = new Date("2021-01-01T00:00:00.000Z");
var start_time = new Date();
var batch_num = 5000;
while (1 == 1) {
    var cursor = db.test_collection.find({"createtime": {$lt: delete_date}}, {"_id": 1}).sort({"_id": 1}).limit(batch_num);
    delete_ids = []
    cursor.forEach(function (each_row) {
        delete_ids.push(each_row["_id"])
    });

    if (delete_ids.length == 0) {
        break;
    }
    db.test_collection.deleteMany({
        '_id': {"$in": delete_ids},
        "createtime": {'$lt': delete_date}
    }, {w: "majority"})
}
var end_time = new Date();
print((end_time - start_time) / 1000);

相對于效率對比章節(jié)的版本,這個版本的代碼簡潔不少。

  1. 不用額外獲取需要刪除的記錄數。
  2. batch_num在整個執(zhí)行過程中也是不變的。

但用這個版本在線上刪除數據時,發(fā)現(xiàn)了一個問題。

在刪除到最后一批時,程序會hang在那里。重試了多次依然如此。分析如下:

  • 最后一批的文檔數小于batch_num時,會出現(xiàn)這個問題。

刪除同實例下另外一個集合,也出現(xiàn)了類似的問題。

但在測試環(huán)境,刪除一個簡單的集合卻沒有復現(xiàn)出來,懷疑這個Bug與線上集合的記錄過長有關。

  • cursor只是一個迭代對象,并不是查詢結果?;赾ursor可以分批返回記錄,類似于Python中的迭代器。

最后一批也不是完全沒有返回,而是在返回100條之后才hang在那里。

  • 不使用sort沒有這個問題。

為什么要使用sort呢?這樣可保證得到的id是有序且在物理上的存儲是相鄰的。這樣,在執(zhí)行批量刪除操作時,效率也會相對較高。

經過實際測試,當要刪除的數據量較大時,使用sort的效率確實比不使用的要高。

如果刪除的數據量較小,使不使用sort則沒多大區(qū)別。

總結

從最佳實踐的角度出發(fā),無論是在哪種數據庫中,如果都刪除(更新)大量數據,都建議分而治之,分批執(zhí)行。

在MongoDB中,如果要刪除大量數據,推薦使用deleteMany + ObjectID進行批量刪除。

為了保證操作的安全性及規(guī)避批量操作帶來的主從延遲風險,建議在執(zhí)行刪除操作時,將Write Concern設置為w: "majority"。

到此這篇關于MongoDB中優(yōu)雅刪除大量數據的文章就介紹到這了,更多相關MongoDB刪除大量數據內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

參考

[1] Journaling

[2] Write Concern

相關文章

  • Mongodb的oplog詳解

    Mongodb的oplog詳解

    這篇文章主要介紹了Mongodb的oplog詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-07-07
  • MongoDB教程之索引介紹

    MongoDB教程之索引介紹

    這篇文章主要介紹了MongoDB教程之索引介紹,本文講解了索引基礎、唯一索引、使用explain、索引管理等內容,需要的朋友可以參考下
    2015-05-05
  • MongoDB入門教程之分片技術詳解

    MongoDB入門教程之分片技術詳解

    這篇文章主要介紹了MongoDB入門教程之分片技術詳解,分片是mongodb中的另一種集群技術,需要的朋友可以參考下
    2014-08-08
  • window平臺安裝MongoDB數據庫圖文詳解

    window平臺安裝MongoDB數據庫圖文詳解

    本篇文章主要介紹了window平臺安裝MongoDB數據庫圖文詳解,主要介紹window下面安裝mogod的步驟和使用細節(jié)。感興趣的小伙伴們可以參考一下。
    2016-11-11
  • MongoDB最大連接數設置失效的異常分析過程與解決方法

    MongoDB最大連接數設置失效的異常分析過程與解決方法

    mongodb最大連接數是20000。所以業(yè)界流傳一段話,千萬級以下的用mysql、千萬級以上的用mongodb,億級以上的用hadoop。下面這篇文章主要給大家介紹了關于MongoDB最大連接數設置失效的異常分析過程,需要的朋友可以參考下
    2018-09-09
  • mongodb+php實現(xiàn)簡單的增刪改查

    mongodb+php實現(xiàn)簡單的增刪改查

    這篇文章主要介紹了mongodb+php實現(xiàn)簡單的增刪改查的相關資料,需要的朋友可以參考下
    2016-07-07
  • CentOS7.2 安裝 MongoDB 3.4的教程

    CentOS7.2 安裝 MongoDB 3.4的教程

    這篇文章主要介紹了CentOS7.2 安裝 MongoDB 3.4的方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-05-05
  • SpringBoot?集成MongoDB實現(xiàn)文件上傳功能

    SpringBoot?集成MongoDB實現(xiàn)文件上傳功能

    這篇文章主要介紹了SpringBoot?集成MongoDB實現(xiàn)文件上傳,主要通過示例代碼記錄文件上傳的步驟,代碼簡單易懂,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-04-04
  • mongodb數據庫入門之CURD簡單操作示例

    mongodb數據庫入門之CURD簡單操作示例

    這篇文章主要介紹了mongodb數據庫入門之CURD簡單操作,結合簡單示例形式分析了MongoDB數據庫基本的CURD增刪改查相關操作技巧與注意事項,需要的朋友可以參考下
    2019-10-10
  • MongoDB查詢之高級操作詳解(多條件查詢、正則匹配查詢等)

    MongoDB查詢之高級操作詳解(多條件查詢、正則匹配查詢等)

    這篇文章主要給大家介紹了關于MongoDB查詢之高級操作(多條件查詢、正則匹配查詢等)的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-10-10

最新評論