日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

MongoDB批量快速插入100万数据

發(fā)布時間:2023/12/1 综合教程 26 生活家
生活随笔 收集整理的這篇文章主要介紹了 MongoDB批量快速插入100万数据 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

文章目錄

      • 三、小試牛刀露鋒芒
      • 一、黑云壓城城欲摧
      • 二、山重水復(fù)疑無路
      • 四、回首向來蕭瑟處,歸去,也無風(fēng)雨也無晴
      • 五、按下葫蘆浮起瓢
      • 六、不計較一城一池之得失

三、小試牛刀露鋒芒

現(xiàn)網(wǎng)GhsHis表有幾百萬數(shù)據(jù),但是測試環(huán)境只有幾萬數(shù)據(jù),想要模擬現(xiàn)網(wǎng)數(shù)據(jù)量進行測試。

叮囑測試用js腳本往數(shù)據(jù)庫插入,結(jié)果她還是調(diào)了接口進行插入。雖然測試環(huán)境MongoDB部署的還是分片集群,但是,還是把測試環(huán)境搞掛了。

關(guān)鍵時刻,還得開發(fā)上場。

有了測試同事的教訓(xùn),為了研發(fā)環(huán)境的安全,我用js腳本先插入了一萬條數(shù)據(jù)小試牛刀。結(jié)果呢,執(zhí)行得很慢。考慮到還有主從復(fù)制,從
庫查詢壓力也很大。

肯定不能一條一條插。那就批量插入。剛好MongoDB有支持批量插入的命令insertMany,于是試了一下,果真批量插入,速度不同凡
響,快的不是一星半點。

理論上可行,但實踐起來還有很多細節(jié)要考慮。

要插入一百萬數(shù)據(jù),肯定不能一次性插入,我們一次插入一萬,分一百次插入。寫兩層for循環(huán)輕松搞定。

插入的數(shù)據(jù)不能一模一樣,比如創(chuàng)建時間和進入歷史表的時間不能都一樣,所以需要動態(tài)設(shè)置。

嘗試插入了二十條數(shù)據(jù),雖然動態(tài)設(shè)置了時間,但是發(fā)現(xiàn)最終所有數(shù)據(jù)都跟最后一條數(shù)據(jù)時間一毛一樣。

我一個寫Java的,為啥要讓我寫Js腳本?我感覺你這是在為難我胖虎。

錢難掙,屎難吃,工資也不是那么好拿的。于是,我又開始了面向百度編程。

很明顯是由于對Js語法不了解導(dǎo)致的。百度了兩行代碼,試了一下可以。

var b = {};
Object.assign(b, a);
b.createDate = NumberLong(new Date().getTime() - Math.round(Math.random() * 10000) + 10000);

之前寫的是

var b;
b.createDate = NumberLong(new Date().getTime() - Math.round(Math.random() * 10000) + 10000);

然后就是往數(shù)組里面插入數(shù)據(jù),使用push方法即可。

統(tǒng)計一下執(zhí)行腳本的耗時,java的sout使用的是加號拼接,Js使用的是逗號拼接。

var time = new Date().getTime();
print("執(zhí)行耗時:",new Date().getTime()-time);

讓時間具有隨機性,調(diào)用Js數(shù)學(xué)類庫函數(shù)。

NumberLong(new Date().getTime() - Math.round(Math.random() * 10000) + 10000);

最終腳本

var a = {"customerId": "123456789","username": "ghs@qq.com","pkgId": "66666666","state": "USE_END","price": 1.0,"createDate": NumberLong(1666341382443),"orgId": "963852741","deptId": "147258369","remark": "666","inHisTime": NumberLong(1666346588556)
};var time = new Date().getTime();
for (j = 1; j <= 100; j++) {var arr = [];for (i = 1; i <= 10000; i++) {var b = {};Object.assign(b, a);if (i % 3 == 0) {b.state = "USE_END";} else if (i % 3 == 1) {b.state = "EXPIRE";} else {b.state = "TRANSFER";}b.createDate = NumberLong(new Date().getTime() - Math.round(Math.random() * 10000) + 10000);b.inHisTime = NumberLong(b.createDate + Math.round(Math.random() * 10000) + 100);arr.push(b);}db.GhsHis.insertMany(arr);
}
print("執(zhí)行耗時:",new Date().getTime()-time);

今天是程序員節(jié),祝大家節(jié)日快樂!!!

大功告成???

不,這才是萬里長征第一步。革命尚未成功,同志仍需努力!!!

數(shù)據(jù)是構(gòu)造好了,有了跟現(xiàn)網(wǎng)差不多的數(shù)據(jù)量級,但是現(xiàn)網(wǎng)問題的復(fù)現(xiàn)、測試還沒開始呢!!!

一、黑云壓城城欲摧

故事背景:現(xiàn)網(wǎng)導(dǎo)出接口導(dǎo)出Excel數(shù)據(jù)出現(xiàn)了Id重復(fù),幾乎是必現(xiàn)。

測試環(huán)境不能復(fù)現(xiàn),距離升級只有三天時間了。時間緊,任務(wù)重。

找業(yè)務(wù)人員要了當(dāng)時導(dǎo)出的那份Excel,將Id列復(fù)制到D:\delete1.txt文件,準備用java代碼分析一下。

代碼思路,使用高速緩沖字符流一次讀取一行,將讀取到的Id放入List集合,然后遍歷List集合,使用Set集合去重,拿到重復(fù)的Id以及下標(biāo)。

public class IOReader {public static void main(String[] args) throws IOException {BufferedReader bis = null;List<String> list = new ArrayList<>();try {bis = new BufferedReader(new InputStreamReader(new FileInputStream("D:\\delete1.txt")));String str = null;while ((str = bis.readLine()) != null){list.add(str);}} catch (FileNotFoundException e) {e.printStackTrace();} finally {bis.close();}System.out.println(list.size());Set<String> set = new HashSet<>();for(int i=0;i<list.size();i++){if(set.contains(list.get(i))){System.out.println("重復(fù)數(shù)據(jù):"+list.get(i)+",下標(biāo)"+i);}else{set.add(list.get(i));}}}
}

通過代碼分析發(fā)現(xiàn),確實存在重復(fù)Id。因為Id是唯一的,出現(xiàn)重復(fù)有悖常理。因為有現(xiàn)表和歷史表,表結(jié)構(gòu)大差不差。導(dǎo)出又可以同時導(dǎo)出歷史表和現(xiàn)表。查看代碼發(fā)現(xiàn),在將現(xiàn)表數(shù)據(jù)移入歷史表的時候,先插入的歷史表,然后刪除的現(xiàn)表,這樣在極端情況下,是可能出現(xiàn)導(dǎo)出重復(fù)Id的。

事情會這么簡單嗎?很明顯不會。

二、山重水復(fù)疑無路

隨著代碼的繼續(xù)深入,我發(fā)現(xiàn)對于導(dǎo)出所有的情況,代碼已經(jīng)做了去重處理,并無明顯Bug。

帶著內(nèi)心的疑問,我又找到了業(yè)務(wù)人員。仔細詢問了他操作的細節(jié)。

操作流水號?操作Id?很明顯,業(yè)務(wù)人員并不關(guān)注這些,只是丟給了我一個用戶名。

我進入用戶表一查,好家伙,一個用戶名對應(yīng)十幾個用戶。

我又問了他操作時所屬的部門,這才將將初步定位了嫌疑人。

案發(fā)時間在昨天,還好時間不久,一切證據(jù)還未被抹除。

現(xiàn)網(wǎng)Kabana日志只展示七天,超過七天的就只能去磁盤看。需要申請一堆的權(quán)限,耗時又費力,大家一般都不愿意申請。

將時間定格在昨天,將嫌疑人鎖死在剛才找到的Id。果真尋到了案發(fā)現(xiàn)場。

本著不放過一切蛛絲馬跡的原則,我仔仔細細地查看了案發(fā)時的證據(jù),但并未發(fā)現(xiàn)什么特別的有價值的證據(jù)。

導(dǎo)出Excel的過程是這樣的,前臺請求中臺,中臺請求后臺。每次最多導(dǎo)出1000條,中臺分次請求后臺。

我看了一下每次導(dǎo)出的時間間隔竟然相差14秒,確實有點大。這算是案發(fā)現(xiàn)場唯一的收獲了。

是中臺設(shè)置的請求時間還是后臺接口竟然如此慢?

我去測試環(huán)境試了一下,很快。那就是后臺接口慢嘍。

案件一下陷入了僵局,撲朔迷離的案情屬實讓人焦頭亂額。

大腦飛速地轉(zhuǎn)動著,思索著還有哪些未考慮到的場景。

我又去看了一下導(dǎo)出Excel,這算是直接證據(jù)了,要好好分析一下。

憑借著精湛的業(yè)務(wù)能力和三年多的工作經(jīng)驗,靈光一現(xiàn),我機智地發(fā)現(xiàn)了出現(xiàn)重復(fù)的數(shù)據(jù)位于每一頁開頭的位置。

一個Idea浮現(xiàn)在了我的腦海。

我激動地翻找代碼,想要佐證自己的想法。果真如我所想,初步定位到了問題所在。

知錯,改錯,驗錯。第一步總算是完成了。

改錯也很簡單,幾秒鐘搞定。知錯幾小時,改錯幾秒鐘。

接力棒交接到了測試同事的手中,我總算可以松一口氣了。

四、回首向來蕭瑟處,歸去,也無風(fēng)雨也無晴

測試按我所說,未能成功復(fù)現(xiàn)問題。球呀,又到了我的手里。

作為全場最靚的仔,這點事肯定難不倒英明神武的我。

現(xiàn)網(wǎng)每一次操作時間間隔有14秒,有充足的時間來進行我們想要的操作。測試環(huán)境只有三秒,時間不等人,拼的是手速。

交代一下我定位出來的問題:我懷疑是排序字段選擇不當(dāng)造成導(dǎo)出Id重復(fù)。

啥?排序字段還能引起導(dǎo)出Id重復(fù)?我只是工作了三年的實習(xí)生,你別蒙我!

別急,聽我細細道來。因為導(dǎo)出是既可以選擇現(xiàn)表,也可以選擇歷史表,它們的排序規(guī)則都是一樣的:創(chuàng)建時間逆序。問題就出在這里,現(xiàn)表使用創(chuàng)建時間沒有問題,但是歷史表就有問題了,創(chuàng)建時間早的不一定進入歷史表的時間就早。

舉個例子,導(dǎo)出的第二頁,第1001條到第2000條數(shù)據(jù)。在正導(dǎo)出第二頁的時候,有一個創(chuàng)建時間恰好位于第1001條到第2000條之間的數(shù)據(jù)被插入,那么根據(jù)創(chuàng)建時間逆序排序,原來第2000條數(shù)據(jù)就會被排到第2001條數(shù)據(jù)的位置,表現(xiàn)出來就是第2001條數(shù)據(jù)Id重復(fù)。

找出數(shù)據(jù)量在10000條以上的部門,然后選一個一萬條左右的部門導(dǎo)出。

db.GhsHis.aggregate([{$group:{_id:"$deptId",total:{$sum:1}}},{$match:{total:{$gt:10000}}}],{allowDiskUse: true})

管道有100M內(nèi)存限制。設(shè)置allowDiskUse:true,允許使用磁盤存儲數(shù)據(jù)。

由于測試環(huán)境數(shù)據(jù)量用戶量不足,一瞬間沒有那么多數(shù)據(jù)失效然后進入歷史表。如此苛刻的復(fù)現(xiàn)條件只能是手動來提供。

三秒的操作時間,理論上是來得及操作的。但是,可能不具有普適性。測試又復(fù)現(xiàn)失敗了。

不知道測試同事心中此時作何感想。(叼毛,按你說的方法復(fù)現(xiàn)不了問題?)

為了維護我在同事心中的靚仔形象。啪,很快呀,我又寫了一個腳本。

var a = {"customerId": "123456789","username": "ghs@qq.com","pkgId": "66666666","state": "USE_END","price": 1.0,"createDate": NumberLong(1666341382443),"orgId": "963852741","deptId": "147258369","remark": "666","inHisTime": NumberLong(1666346588556)
};
function sleep(number){var now = new Date();var exitTime = now.getTime() + number;while (true) {now = new Date();if(now.getTime() > exitTime){return;}}
}
var timeArr=[1641864542173,1641864263779,1637305936014,1637293032528,1636098374840,1624608384592,1621040814675,1617868030123,1617866906466];
timeArr.forEach(function(t){var b = {};Object.assign(b, a);b.createDate = NumberLong(t);sleep(200);db.GhsHis.insert(b);
});

開發(fā)呀,你不講武德,你跟我說用手操作,你竟然偷偷寫腳本。

還不是為了操作的普適性,為了問題在測試環(huán)境必現(xiàn),你以為我想寫腳本呀?

說起來云淡風(fēng)輕,實際上斬棘披荊。

老規(guī)矩:先解釋一下代碼思路。找出了九個時間點,這九個時間點都是位于第二頁的。然后遍歷,每隔200毫秒,以該時間點為創(chuàng)建時間,插入一條數(shù)據(jù),代表的是此時有一條創(chuàng)建時間位于第二頁的數(shù)據(jù)被插入歷史表,以此來模擬現(xiàn)網(wǎng)操作。

Java線程睡眠一行代碼就夠了,Js竟然還得自己寫,當(dāng)然都是百度的了。

那這九個時間點是怎么找出來的?

db.GhsHis.find({"deptId" : "147258369","state":{$in:["EXPIRE","USE_END","TRANSFER"]}}).sort({"createDate":-1}).skip(1100)

skip那里改成1200,一直到1900,找出9個創(chuàng)建時間。

我將腳本給了測試,讓他在第一頁導(dǎo)出之后,第二頁正在導(dǎo)出的時候,立刻執(zhí)行該腳本。

結(jié)果呢,翻車了,還是沒有復(fù)現(xiàn)。

大意呀,我沒有改進入歷史表的時間。因為我改了排序規(guī)則,新規(guī)則使用進入歷史表的時間和創(chuàng)建時間兩個字段來逆向排序。

b.inHisTime= NumberLong(new Date().getTime());

又把腳本給了測試,問題成功復(fù)現(xiàn)!靚仔的形象得到了強有力的維護!自己的形象要靠自己來維護。

五、按下葫蘆浮起瓢

然后,測試升級版本,開始驗錯,到了校驗我改錯的時候了。

結(jié)果又翻車了!!!每次都能復(fù)現(xiàn)問題,改了跟沒改一樣,我怕不是改了個毛線?

這下好了,靚仔徹底變叼毛了。

細細端詳代碼,思忖著究竟是哪里出了問題?原來是排序字段的排序方式有問題。

之前是按照創(chuàng)建時間逆向排序,我想都沒想,就沿用了之前的方式,根據(jù)進入歷史表的時間和創(chuàng)建時間兩個字段來逆向排序。(當(dāng)進入歷史表時間相同,按照創(chuàng)建時間來逆向排序)

我怎能重蹈前任的覆轍呢?我跟他們又有什么區(qū)別?我改錯又有什么意義呢?只是從一個坑爬到了另一個坑。

大意,還是大意呀。不僅丟了燕云十八州,連荊襄九郡都丟了。

痛定思痛,痛改前非。

還是時間太緊急了,搞得我很急躁,都不能冷靜思考了,犯了如此低級的錯誤。總得給自己找個借口安慰一下英明神武明察秋毫的自己。

改成正向排序以后,問題果然解決了。問題不會復(fù)現(xiàn)了。Nice !

現(xiàn)網(wǎng)問題到這里已經(jīng)解決了,不會再導(dǎo)出重復(fù)的Id了。

六、不計較一城一池之得失

bug已經(jīng)解決,但是優(yōu)化永無止境。查詢導(dǎo)出1000條耗費14秒,太慢了。

查詢慢,第一反應(yīng)肯定是沒加索引。查看現(xiàn)網(wǎng)GhsHis表的索引。

db.GhsHis.getIndexes()

發(fā)現(xiàn)表里索引挺多的,但是,排序字段createDate竟然沒加索引!

分析一下查詢導(dǎo)出語句的執(zhí)行計劃,“stage” : “SORT”,證明排序沒有使用索引,在內(nèi)存中做了排序,且做了全表掃描。

db.GhsHis.find({"deptId" : "147258369","state":{$in:["EXPIRE","USE_END","TRANSFER"]}})
.sort({"createDate":-1}).skip(1000).limit(1000).explain("executionStats")

鑒于已經(jīng)修改了排序規(guī)則,所以我給進入歷史表時間和創(chuàng)建時間建了聯(lián)合索引。{background:true}這句一定要加,否則會鎖表。

db.GhsHis.ensureIndex({inHisTime:1,createDate:1},{background:true})

state字段與deptId字段數(shù)據(jù)區(qū)分度不高,暫時不加索引。

db.GhsHis.find({"deptId" : "147258369","state":{$in:["EXPIRE","USE_END","TRANSFER"]}}).sort({"inHisTime":1,"createDate":1}).skip(1000).limit(1000).explain("executionStats")

再次查看執(zhí)行計劃,“stage” : “IXSCAN”,說明使用了索引,只掃描了幾千條數(shù)據(jù),查詢時間也只有幾十毫秒了。

MongoDB的執(zhí)行計劃比起Mysql而言更加復(fù)雜難懂,后續(xù)有時間再做深入研究學(xué)習(xí),今日尚且淺嘗輒止!

總結(jié)

以上是生活随笔為你收集整理的MongoDB批量快速插入100万数据的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。