成人午夜视频全免费观看高清-秋霞福利视频一区二区三区-国产精品久久久久电影小说-亚洲不卡区三一区三区一区

海量數(shù)據(jù)的分頁怎么破

一、背景

分頁應(yīng)該是極為常見的數(shù)據(jù)展現(xiàn)方式了,一般在數(shù)據(jù)集較大而無法在單個(gè)頁面中呈現(xiàn)時(shí)會(huì)采用分頁的方法。
各種前端UI組件在實(shí)現(xiàn)上也都會(huì)支持分頁的功能,而數(shù)據(jù)交互呈現(xiàn)所相應(yīng)的后端系統(tǒng)、數(shù)據(jù)庫都對(duì)數(shù)據(jù)查詢的分頁提供了良好的支持。
以幾個(gè)流行的數(shù)據(jù)庫為例:

創(chuàng)新互聯(lián)于2013年開始,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目成都網(wǎng)站設(shè)計(jì)、網(wǎng)站制作網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個(gè)夢(mèng)想脫穎而出為使命,1280元文縣做網(wǎng)站,已為上家服務(wù),為文縣各地企業(yè)和個(gè)人服務(wù),聯(lián)系電話:18980820575

查詢表 t_data 第 2 頁的數(shù)據(jù)(假定每頁 5 條)

  • MySQL 的做法:

    select * from t_data limit 5,5
  • PostGreSQL 的做法:

    select * from t_data limit 5 offset 5
  • MongoDB 的做法:
db.t_data.find().limit(5).skip(5);

盡管每種數(shù)據(jù)庫的語法不盡相同,通過一些開發(fā)框架封裝的接口,我們可以不需要熟悉這些差異。如 SpringData 提供的分頁接口:

public interface PagingAndSortingRepository<T, ID extends Serializable>
  extends CrudRepository<T, ID> {

  Page<T> findAll(Pageable pageable);
}

這樣看來,開發(fā)一個(gè)分頁的查詢功能是非常簡(jiǎn)單的。
然而萬事皆不可能盡全盡美,盡管上述的數(shù)據(jù)庫、開發(fā)框架提供了基礎(chǔ)的分頁能力,在面對(duì)日益增長(zhǎng)的海量數(shù)據(jù)時(shí)卻難以應(yīng)對(duì),一個(gè)明顯的問題就是查詢性能低下!
那么,面對(duì)千萬級(jí)、億級(jí)甚至更多的數(shù)據(jù)集時(shí),分頁功能該怎么實(shí)現(xiàn)?

下面,我以 MongoDB 作為背景來探討幾種不同的做法。

二、傳統(tǒng)方案

就是最常規(guī)的方案,假設(shè) 我們需要對(duì)文章 articles 這個(gè)表(集合) 進(jìn)行分頁展示,一般前端會(huì)需要傳遞兩個(gè)參數(shù):

  • 頁碼(當(dāng)前是第幾頁)
  • 頁大小(每頁展示的數(shù)據(jù)個(gè)數(shù))

按照這個(gè)做法的查詢方式,如下圖所示:

海量數(shù)據(jù)的分頁怎么破

因?yàn)槭窍M詈髣?chuàng)建的文章顯示在前面,這里使用了_id 做降序排序。
其中紅色部分語句的執(zhí)行計(jì)劃如下:

{
  "queryPlanner" : {
    "plannerVersion" : 1,
    "namespace" : "appdb.articles",
    "indexFilterSet" : false,
    "parsedQuery" : {
      "$and" : []
    },
    "winningPlan" : {
      "stage" : "SKIP",
      "skipAmount" : 19960,
      "inputStage" : {
        "stage" : "FETCH",
        "inputStage" : {
          "stage" : "IXSCAN",
          "keyPattern" : {
            "_id" : 1
          },
          "indexName" : "_id_",
          "isMultiKey" : false,
          "direction" : "backward",
          "indexBounds" : {
            "_id" : [ 
              "[MaxKey, MinKey]"
            ]
         ...
}

可以看到隨著頁碼的增大,skip 跳過的條目也會(huì)隨之變大,而這個(gè)操作是通過 cursor 的迭代器來實(shí)現(xiàn)的,對(duì)于cpu的消耗會(huì)比較明顯。
而當(dāng)需要查詢的數(shù)據(jù)達(dá)到千萬級(jí)及以上時(shí),會(huì)發(fā)現(xiàn)響應(yīng)時(shí)間非常的長(zhǎng),可能會(huì)讓你幾乎無法接受!

或許,假如你的機(jī)器性能很差,在數(shù)十萬、百萬數(shù)據(jù)量時(shí)已經(jīng)會(huì)出現(xiàn)瓶頸

三、改良做法

既然傳統(tǒng)的分頁方案會(huì)產(chǎn)生 skip 大量數(shù)據(jù)的問題,那么能否避免呢?答案是可以的。
改良的做法為:

  1. 選取一個(gè)唯一有序的關(guān)鍵字段,比如 _id,作為翻頁的排序字段;
  2. 每次翻頁時(shí)以當(dāng)前頁的最后一條數(shù)據(jù)_id值作為起點(diǎn),將此并入查詢條件中。

如下圖所示:

海量數(shù)據(jù)的分頁怎么破

修改后的語句執(zhí)行計(jì)劃如下:

{
  "queryPlanner" : {
    "plannerVersion" : 1,
    "namespace" : "appdb.articles",
    "indexFilterSet" : false,
    "parsedQuery" : {
      "_id" : {
        "$lt" : ObjectId("5c38291bd4c0c68658ba98c7")
      }
    },
    "winningPlan" : {
      "stage" : "FETCH",
      "inputStage" : {
        "stage" : "IXSCAN",
        "keyPattern" : {
          "_id" : 1
        },
        "indexName" : "_id_",
        "isMultiKey" : false,
        "direction" : "backward",
        "indexBounds" : {
          "_id" : [ 
            "(ObjectId('5c38291bd4c0c68658ba98c7'), ObjectId('000000000000000000000000')]"
          ]
      ...
}

可以看到,改良后的查詢操作直接避免了昂貴的 skip 階段,索引命中及掃描范圍也是非常合理的!

性能對(duì)比

為了對(duì)比這兩種方案的性能差異,下面準(zhǔn)備了一組測(cè)試數(shù)據(jù)。

測(cè)試方案

準(zhǔn)備10W條數(shù)據(jù),以每頁20條的參數(shù)從前往后翻頁,對(duì)比總體翻頁的時(shí)間消耗

db.articles.remove({});
var count = 100000;

var items = [];
for(var i=1; i<=count; i++){

  var item = {
    "title" : "論年輕人思想建設(shè)的重要性-" + i,
    "author" : "王小兵-" + Math.round(Math.random() * 50),
    "type" : "雜文-" + Math.round(Math.random() * 10) ,
    "publishDate" : new Date(),
  } ;
  items.push(item);

  if(i%1000==0){
    db.test.insertMany(items);
    print("insert", i);

    items = [];
  }
}

傳統(tǒng)翻頁腳本

function turnPages(pageSize, pageTotal){

  print("pageSize:", pageSize, "pageTotal", pageTotal)

  var t1 = new Date();
  var dl = [];

  var currentPage = 0;
  //輪詢翻頁
  while(currentPage < pageTotal){

     var list = db.articles.find({}, {_id:1}).sort({_id: -1}).skip(currentPage*pageSize).limit(pageSize);
     dl = list.toArray();

     //沒有更多記錄
     if(dl.length == 0){
         break;
     }
     currentPage ++;
     //printjson(dl)
  }

  var t2 = new Date();

  var spendSeconds = Number((t2-t1)/1000).toFixed(2)
  print("turn pages: ", currentPage, "spend ", spendSeconds, ".")  

}

改良翻頁腳本

function turnPageById(pageSize, pageTotal){

  print("pageSize:", pageSize, "pageTotal", pageTotal)

  var t1 = new Date();

  var dl = [];
  var currentId = 0;
  var currentPage = 0;

  while(currentPage ++ < pageTotal){

      //以上一頁的ID值作為起始值
     var condition = currentId? {_id: {$lt: currentId}}: {};
     var list = db.articles.find(condition, {_id:1}).sort({_id: -1}).limit(pageSize);
     dl = list.toArray();

     //沒有更多記錄
     if(dl.length == 0){
         break;
     }

     //記錄最后一條數(shù)據(jù)的ID
     currentId = dl[dl.length-1]._id;
  }

  var t2 = new Date();

  var spendSeconds = Number((t2-t1)/1000).toFixed(2)
  print("turn pages: ", currentPage, "spend ", spendSeconds, ".")    
}

以100、500、1000、3000頁數(shù)的樣本進(jìn)行實(shí)測(cè),結(jié)果如下

海量數(shù)據(jù)的分頁怎么破

可見,當(dāng)頁數(shù)越大(數(shù)據(jù)量越大)時(shí),改良的翻頁效果提升越明顯!
這種分頁方案其實(shí)采用的就是時(shí)間軸(TImeLine)的模式,實(shí)際應(yīng)用場(chǎng)景也非常的廣,比如Twitter、微博、朋友圈動(dòng)態(tài)都可采用這樣的方式。
而同時(shí)除了上述的數(shù)據(jù)庫之外,HBase、ElastiSearch 在Range Query的實(shí)現(xiàn)上也支持這種模式。

四、完美的分頁

時(shí)間軸(TimeLine)的模式通常是做成“加載更多”、上下翻頁這樣的形式,但無法自由的選擇某個(gè)頁碼。
那么為了實(shí)現(xiàn)頁碼分頁,同時(shí)也避免傳統(tǒng)方案帶來的 skip 性能問題,我們可以采取一種折中的方案。

這里參考Google搜索結(jié)果頁作為說明:

海量數(shù)據(jù)的分頁怎么破

通常在數(shù)據(jù)量非常大的情況下,頁碼也會(huì)有很多,于是可以采用頁碼分組的方式。
以一段頁碼作為一組,每一組內(nèi)數(shù)據(jù)的翻頁采用ID 偏移量 + 少量的 skip 操作實(shí)現(xiàn)

具體的操作如下圖所示:

海量數(shù)據(jù)的分頁怎么破

實(shí)現(xiàn)步驟

  1. 對(duì)頁碼進(jìn)行分組(groupSize=8, pageSize=20),每組為8個(gè)頁碼;

  2. 提前查詢 end_offset,同時(shí)獲得本組頁碼數(shù)量:

    db.articles.find({ _id: { $lt: start_offset } }).sort({_id: -1}).skip(20*8).limit(1)
  3. 分頁數(shù)據(jù)查詢以本頁組 start_offset 作為起點(diǎn),在有限的頁碼上翻頁(skip)
    由于一個(gè)分組的數(shù)據(jù)量通常很小(8*20=160),在分組內(nèi)進(jìn)行skip產(chǎn)生的代價(jià)會(huì)非常小,因此性能上可以得到保證。

小結(jié)

隨著物聯(lián)網(wǎng),大數(shù)據(jù)業(yè)務(wù)的白熱化,一般企業(yè)級(jí)系統(tǒng)的數(shù)據(jù)量也會(huì)呈現(xiàn)出快速的增長(zhǎng)。而傳統(tǒng)的數(shù)據(jù)庫分頁方案在海量數(shù)據(jù)場(chǎng)景下很難滿足性能的要求。
在本文的探討中,主要為海量數(shù)據(jù)的分頁提供了幾種常見的優(yōu)化方案(以MongoDB作為實(shí)例),并在性能上做了一些對(duì)比,旨在提供一些參考。

當(dāng)前名稱:海量數(shù)據(jù)的分頁怎么破
網(wǎng)站路徑:http://jinyejixie.com/article12/ppiedc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供Google網(wǎng)站設(shè)計(jì)、網(wǎng)頁設(shè)計(jì)公司移動(dòng)網(wǎng)站建設(shè)、網(wǎng)站導(dǎo)航域名注冊(cè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都app開發(fā)公司
盐池县| 政和县| 四川省| 盐山县| 广饶县| 桃江县| 文安县| 久治县| 正定县| 临西县| 正定县| 凤山县| 东源县| 南京市| 静海县| 砚山县| 濮阳市| 新源县| 道孚县| 齐齐哈尔市| 抚顺县| 山阳县| 闸北区| 左贡县| 馆陶县| 高唐县| 夹江县| 盐池县| 安国市| 红桥区| 侯马市| 紫云| 抚顺市| 明水县| 南昌县| 博罗县| 横山县| 靖远县| 呼图壁县| 班玛县| 南宁市|