Mongodb遍歷數(shù)據(jù)鹿寨,游標(biāo)VS排序(附實(shí)現(xiàn)代碼)

前言

在使用mongodb的時(shí)候,經(jīng)常會(huì)有這樣的業(yè)務(wù)場(chǎng)景薪夕,比如搜索某個(gè)條件脚草,然后這個(gè)條件的結(jié)果有幾十萬甚至幾百萬,然后一時(shí)半會(huì)處理不過來原献,就需要使用遍歷循環(huán)來處理馏慨。一般來說遍歷大量的數(shù)據(jù)有三種方法:

  • 第一種就是用mongodb自帶的游標(biāo)去遍歷
  • 第二種是用排序然后取最后一個(gè)id去遍歷
  • 第三種是使用limit和skip去遍歷

當(dāng)數(shù)據(jù)量很少的時(shí)候可以使用第三種方法遍歷,其他時(shí)候均不適合使用第三種方法遍歷姑隅。本文主要對(duì)比第一種和第二種方法的優(yōu)劣

使用游標(biāo)遍歷

一般來說直接使用mongodb的find查詢写隶,會(huì)返回一個(gè)游標(biāo),默認(rèn)是返回20條讲仰,使用游標(biāo)的next()方法可以繼續(xù)訪問下一頁腥放,類似一個(gè)翻頁器赊时。但是要注意,不要輕易的去調(diào)用游標(biāo)的toArray()方法,除非你在確定返回結(jié)果數(shù)量的情況下荠雕,否則游標(biāo)會(huì)把所有數(shù)據(jù)加載到內(nèi)存。游標(biāo)可以通過batchSize來設(shè)置每頁的數(shù)量

游標(biāo)需要注意的地方

首先瓮具,游標(biāo)是一個(gè)內(nèi)存的狀態(tài)槽袄,在默認(rèn)配置下,一個(gè)游標(biāo)在兩次getmore間隔超過10分鐘毫捣,那么這個(gè)游標(biāo)就會(huì)被回收详拙,也就是說在批量處理數(shù)據(jù)的時(shí)候,如果發(fā)生卡頓或者執(zhí)行時(shí)間超過預(yù)期蔓同,就有可能導(dǎo)致當(dāng)前游標(biāo)被回收饶辙,然后無法繼續(xù)遍歷,報(bào)錯(cuò)找不到游標(biāo)牌柄。當(dāng)然可以調(diào)整這個(gè)延遲時(shí)間或者縮小批量的數(shù)量來避免這個(gè)問題
其次畸悬,游標(biāo)的本質(zhì)是數(shù)據(jù)庫的一個(gè)指針,指向了數(shù)據(jù)的地址珊佣,所以當(dāng)數(shù)據(jù)發(fā)生變化的時(shí)候蹋宦,可能會(huì)出現(xiàn)混亂的情況。
游標(biāo)的返回是不保證順序的咒锻,如果使用排序冷冗,會(huì)占用大量的資源。同時(shí)因?yàn)椴槐WC順序的情況惑艇,遍歷是無法暫停后繼續(xù)的蒿辙。

示例代碼

首先導(dǎo)入maven依賴

 <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-mongodb</artifactId>
    </dependency>

然后java示例

 public void loopCollection() {
      String collectionName = "test_table";
        // 獲取集合
        MongoCollection<Document> collection = mongoTemplate.getCollection(collectionName);
        // 執(zhí)行查詢拇泛,獲取游標(biāo)
        MongoCursor<Document> cursor = collection.find().iterator();
        // 遍歷游標(biāo)
        while (cursor.hasNext()) {
            Document document = cursor.next();
            // 將 Document 轉(zhuǎn)換為 JSONObject
            JSONObject jsonObject = new JSONObject(document.toJson());
            // 處理每個(gè) JSONObject
        }
        // 關(guān)閉游標(biāo)
        cursor.close();
    }

使用排序遍歷

一般來說,排序遍歷是使用某個(gè)唯一字段作為排序來遍歷思灌,每次都取結(jié)果的最后一個(gè)數(shù)據(jù)的這個(gè)字段來作為下一次查詢的條件俺叭,使用limit來控制性能。比如:通過_id來遍歷一個(gè)數(shù)據(jù)集合泰偿,先使用limit(100)拿到100條數(shù)據(jù)熄守,然后取最后一個(gè)數(shù)據(jù)的_id假設(shè)為idA,然后在下一次遍歷的時(shí)候加入條件 {"_id":{$gt:idA}然后繼續(xù)limit(100),以此類推,來達(dá)到遍歷的效果

排序遍歷需要注意的地方

排序遍歷每次都會(huì)使用排序耗跛,當(dāng)條件很簡(jiǎn)單或者是遍歷所有數(shù)據(jù)的時(shí)候裕照,這種方法是性能和準(zhǔn)確性的最佳方法,同時(shí)每次遍歷數(shù)據(jù)消耗的性能都是比較平均的调塌,不容易造成數(shù)據(jù)庫性能擁堵晋南。
排序遍歷在條件比較復(fù)雜的情況下,性能可能受索引的影響羔砾,在條件很多的情況下负间,排序遍歷挺難所有的查詢都使用索引,特別是_id排序姜凄,往往后面的遍歷只會(huì)使用的到_id的索引唉擂。所以條件復(fù)雜的時(shí)候需要測(cè)試性能來避免遍歷引起過多數(shù)據(jù)庫開銷。

排序遍歷的java實(shí)現(xiàn)

以springboot來說檀葛,以下是排序遍歷的一個(gè)java工具玩祟,大家可以直接復(fù)制使用

@Slf4j
public class MongoLoopUtil<T> {
    private Object loopValue = null;
    private String sortKey;
    private MongoTemplate mongoTemplate;
    private Class<T> returnObj;
    private int batchSize;
    private String collection;
    private String[] excludes;
    private int count;

    public void setExcludes(String[] excludes) {
        this.excludes = excludes;
    }

    public MongoLoopUtil(
            MongoTemplate mongoTemplate,
            String sortKey,
            Class<T> type,
            int batchSize,
            String collection) {
        this.mongoTemplate = mongoTemplate;
        this.sortKey = sortKey;
        this.returnObj = type;
        this.batchSize = batchSize;
        this.collection = collection;
    }

    public List<T> get(Criteria criteria) {
        return get(collection, criteria, null);
    }

    public List<T> get(Criteria criteria, String[] includeField) {
        return get(collection, criteria, includeField);
    }

    public List<T> get(String collection, Criteria criteria, String[] includeField) {
        Query query = new Query();
        query.addCriteria(criteria);
        query.with(Sort.by(Sort.Order.asc(sortKey)));
        if (loopValue != null) {
            query.addCriteria(Criteria.where(sortKey).gt(loopValue));
        }

        if (includeField != null) {
            query.fields().include(includeField);
        }
        if (excludes != null) {
            query.fields().exclude(excludes);
        }
        query.limit(batchSize);
        List<T> list = null;
        if (collection != null) {
            list = mongoTemplate.find(query, returnObj, collection);
        } else {
            list = mongoTemplate.find(query, returnObj);
        }
        if (list.size() == 0) {
            loopValue = null;
        } else {
            T objLast = list.get(list.size() - 1);
            JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(objLast));
            loopValue = jsonObject.get(sortKey);
            count += list.size();
        }
        log.info("MongoLoopUtil already get count:{},collection", count, collection);
        return list;
    }
}

使用方法:

MongoLoopUtil<JSONObject> mongoLoopUtil =
                new MongoLoopUtil<>(
                        mongoTemplate,
                        "_id",
                        JSONObject.class,
                        100,
                        "test_table");
while (true) {
            List<JSONObject> datas = mongoLoopUtil.get(criteria);
            if (null == datas || datas.size() == 0) {
                break;
            }
            //doSomeThing
        }

可以通過使用的示例看到,需要遍歷的時(shí)候創(chuàng)建一個(gè)MongoLoopUtil對(duì)象屿聋,其中的泛型就是返回的數(shù)據(jù)類型空扎,然后構(gòu)建方法里面?zhèn)魅雖ongoTemplate和排序的字段,這里排序的字段是_id润讥,然后傳入泛型的class转锈,然后傳入每次遍歷的數(shù)量,這里數(shù)量是100楚殿,然后傳入需要遍歷的表名撮慨,然后這個(gè)對(duì)象就創(chuàng)建完成了,然后通過get方法就可以遍歷數(shù)據(jù)了脆粥,其中criteria是查詢條件砌溺,一般來說這個(gè)條件是不變的。

游標(biāo)VS排序遍歷對(duì)比

使用游標(biāo)優(yōu)點(diǎn):

  • 游標(biāo)逐個(gè)返回結(jié)果变隔,適用于按需加載數(shù)據(jù)规伐,減少內(nèi)存占用。
  • 可以在查詢過程中即時(shí)獲取到最新的數(shù)據(jù)匣缘,不受排序影響猖闪。

使用游標(biāo)缺點(diǎn):

  • 如果沒有合適的索引支持鲜棠,可能需要對(duì)整個(gè)集合進(jìn)行全表掃描,性能較差培慌。
  • 在數(shù)據(jù)變更較多的情況下豁陆,游標(biāo)可能不穩(wěn)定,有可能會(huì)漏掉或重復(fù)某些文檔吵护。

使用排序優(yōu)點(diǎn):

  • 如果可以使用索引進(jìn)行排序献联,可以提高查詢性能。
  • 每次查詢的性能消耗是穩(wěn)定且可預(yù)測(cè)的何址。
  • 遍歷中途可以暫停后重新開始
  • 對(duì)每次遍歷處理數(shù)據(jù)的時(shí)間沒有要求

使用排序缺點(diǎn):

  • 需要事先知道排序的字段,并且需要有適當(dāng)?shù)乃饕С帧?/li>
  • 在數(shù)據(jù)變更較多的情況下进胯,可能需要考慮新數(shù)據(jù)的插入和舊數(shù)據(jù)的刪除用爪,以確保數(shù)據(jù)的準(zhǔn)確性。
  • 復(fù)雜查詢可能性能不好

總結(jié)

總體來說游標(biāo)遍歷和排序遍歷各有優(yōu)缺點(diǎn)胁镐,各位還是要根據(jù)實(shí)際的業(yè)務(wù)情況去分析選擇最合適的遍歷方法偎血。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市盯漂,隨后出現(xiàn)的幾起案子颇玷,更是在濱河造成了極大的恐慌,老刑警劉巖就缆,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帖渠,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡竭宰,警方通過查閱死者的電腦和手機(jī)空郊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來切揭,“玉大人狞甚,你說我怎么就攤上這事±” “怎么了哼审?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)孕豹。 經(jīng)常有香客問我涩盾,道長(zhǎng),這世上最難降的妖魔是什么励背? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任旁赊,我火速辦了婚禮,結(jié)果婚禮上椅野,老公的妹妹穿的比我還像新娘终畅。我一直安慰自己籍胯,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布离福。 她就那樣靜靜地躺著杖狼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪妖爷。 梳的紋絲不亂的頭發(fā)上蝶涩,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音絮识,去河邊找鬼绿聘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛次舌,可吹牛的內(nèi)容都是我干的熄攘。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼彼念,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼挪圾!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起逐沙,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤哲思,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后吩案,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棚赔,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年徘郭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了忆嗜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡崎岂,死狀恐怖捆毫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情冲甘,我是刑警寧澤绩卤,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站江醇,受9級(jí)特大地震影響濒憋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜陶夜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一凛驮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧条辟,春花似錦黔夭、人聲如沸宏胯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肩袍。三九已至,卻和暖如春婚惫,著一層夾襖步出監(jiān)牢的瞬間氛赐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工先舷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留艰管,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓蒋川,卻偏偏與公主長(zhǎng)得像牲芋,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子尔破,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容