<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      MongoDB中如何優雅地刪除大量數據

      刪除大量數據,無論是在哪種數據庫中,都是一個普遍性的需求。

      除了正常的業務需求,我們也需要通過這種方式來為數據庫“瘦身”。

      為什么要“瘦身”呢?

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

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

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

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

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

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

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

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

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

      以 MySQL 為例:

      • 會造成大事務

        大事務會導致主從延遲,而主從延遲又會影響數據庫的高可用切換。

      • 回滾表空間會不斷膨脹

        在MySQL 8.0之前,回滾表空間默認是放到系統表空間中,而系統表空間一旦”膨脹“,就不會收縮。

      • 鎖定的記錄多

        相對而言,更容易導致鎖等待。

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

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

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

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

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

      MongoDB中刪除數據的三種方式

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

      • db.collection.remove()

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

      • db.collection.deleteMany()

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

      • db.collection.bulkWrite()

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

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

      三種方式的執行效率對比

      環境:MongoDB 3.4.4,副本集。

      測試思路:分別使用 remove、deleteMany、bulkWrite 刪除 10w 條記錄(每批刪除 5000 條),交叉執行 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();
      // 兩者的差值,即為程序執行時長
      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

      實現思路同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);

      接下來,看看三者的執行效率。

      刪除方式平均執行時間(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. 執行最慢的是remove,執行最快的是bulkWrite,前者差不多是后者的 2.79 倍。
      2. deleteMany 和 bulkWrite 的執行效率差不多,但就語法而言,前者比后者簡潔。

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

      通過 Write Concern 規避主從延遲

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

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

      看下面這個原理圖:

       


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

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

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

      Write Concern的完整語法如下,

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

       

      下面看看各個選項的具體含義。

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

      • <number>:顯式指定節點數量。

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

        設置為1,只需Primary節點反饋。

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

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

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

      • majority:副本集大多數節點。

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

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

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

        如一個集群,有5個節點,跨機房部署,其中3個節點在A機房,另外2個節點在B機房。

        因為對數據的安全性、一致性要求很高,我們希望寫操作至少能在A機房的2個節點落盤,B機房的1個節點落盤。

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

        具體使用,可參考:              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 節點,每次批量應用 oplog 后。

      • Write Concern 設置了 j: true。

      • 每100ms。

        由 storage.journal.commitIntervalMs 參數指定。

      • 創建新的 journal 文件時。

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

      wtimeout:超時時長,單位ms。

      不設置或設置為0,如果命令在執行的過程中,遇到了鎖等待或節點數達不到要求,會一直阻塞。

      刪除過程中遇到的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"])
          });
          // 如果數組的大小為0,則代表結果集為空,這個時候,可退出循環。
          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)

      相對于后來的版本,這個版本的代碼簡潔不少。

      它沒有獲取需要刪除的記錄數,也沒有改變batch_num的大小。它是通過結果集的大小,來判斷記錄是否刪除完。如果結果集為空,則意味著滿足刪除條件的記錄已經刪除完,這個時候,可退出循環。

      但用這個版本的程序在線上刪除數據時,發現了一個問題。

      在刪除最后一批數據時,程序會hang在那里,重試了多次依然如此。

      分析如下:

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

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

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

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

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

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

        為什么要使用sort呢?

        這樣可保證得到的id是有序且在物理上的存儲是相鄰的。這樣,在執行批量刪除操作時,效率也會相對較高。

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

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

      總結

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

      基于主鍵進行批量刪除(更新),是一個通用的解決方案,適用于所有數據庫。

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

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

      原文鏈接:https://mp.weixin.qq.com/s/JFw-XFks8D7BteZ9vijTHQ

      參考

      [1] Journaling
      https://docs.mongodb.com/manual/core/journaling/

      [2] Write Concern
      https://docs.mongodb.com/manual/reference/write-concern/

      posted @ 2023-12-20 11:20  VicLW  閱讀(810)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 2022最新国产在线不卡a| 男女18禁啪啪无遮挡激烈网站| 麻豆国产成人AV在线播放| 亚洲国产日韩欧美一区二区三区| 精品国产亚洲午夜精品av| 国产精品综合色区av| 国产成人精品永久免费视频| 久久精品国产精品亚洲综合| 蜜桃视频在线免费观看一区二区| 欧洲中文字幕一区二区| 水蜜桃av无码| 久久午夜无码免费| 久久久一本精品99久久精品36| 二区中文字幕在线观看| 无码h片在线观看网站| 五月丁香综合缴情六月小说| 亚洲欧洲∨国产一区二区三区| 四虎库影成人在线播放| 日韩人妻无码精品久久| 99久久婷婷国产综合精品青草漫画 | 中文字幕无线码在线观看| 国产11一12周岁女毛片| 成人啪精品视频网站午夜| 樱桃视频影院在线播放| 久久月本道色综合久久| 最新国产精品精品视频| 久久99精品久久久久久9| 亚洲精品亚洲人成人网| 国产一区二区三区色视频| 久久天天躁狠狠躁夜夜婷| 炉霍县| 麻豆精产国品一二三区区| 成人又黄又爽又色的视频| 国产中文字幕在线一区| 亚洲一区二区三区影院| 国产亚洲精品AA片在线爽| 国产精品自产拍在线播放| 日本中文字幕乱码免费| 好硬好湿好爽再深一点动态图视频| 国产日产欧产美韩系列麻豆| 国内精品久久久久久无码不卡 |