分頁應(yīng)該是極為常見的數(shù)據(jù)展現(xiàn)方式了,一般在數(shù)據(jù)集較大而無法在單個頁面中呈現(xiàn)時會采用分頁的方法。
各種前端UI組件在實現(xiàn)上也都會支持分頁的功能,而數(shù)據(jù)交互呈現(xiàn)所相應(yīng)的后端系統(tǒng)、數(shù)據(jù)庫都對數(shù)據(jù)查詢的分頁提供了良好的支持。
以幾個流行的數(shù)據(jù)庫為例:
創(chuàng)新互聯(lián)于2013年開始,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項目成都網(wǎng)站設(shè)計、網(wǎng)站制作網(wǎng)站策劃,項目實施與項目整合能力。我們以讓每一個夢想脫穎而出為使命,1280元文縣做網(wǎng)站,已為上家服務(wù),為文縣各地企業(yè)和個人服務(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
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ā)一個分頁的查詢功能是非常簡單的。
然而萬事皆不可能盡全盡美,盡管上述的數(shù)據(jù)庫、開發(fā)框架提供了基礎(chǔ)的分頁能力,在面對日益增長的海量數(shù)據(jù)時卻難以應(yīng)對,一個明顯的問題就是查詢性能低下!
那么,面對千萬級、億級甚至更多的數(shù)據(jù)集時,分頁功能該怎么實現(xiàn)?
下面,我以 MongoDB 作為背景來探討幾種不同的做法。
就是最常規(guī)的方案,假設(shè) 我們需要對文章 articles 這個表(集合) 進行分頁展示,一般前端會需要傳遞兩個參數(shù):
按照這個做法的查詢方式,如下圖所示:
因為是希望最后創(chuàng)建的文章顯示在前面,這里使用了_id 做降序排序。
其中紅色部分語句的執(zhí)行計劃如下:
{
"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 跳過的條目也會隨之變大,而這個操作是通過 cursor 的迭代器來實現(xiàn)的,對于cpu的消耗會比較明顯。
而當需要查詢的數(shù)據(jù)達到千萬級及以上時,會發(fā)現(xiàn)響應(yīng)時間非常的長,可能會讓你幾乎無法接受!
或許,假如你的機器性能很差,在數(shù)十萬、百萬數(shù)據(jù)量時已經(jīng)會出現(xiàn)瓶頸
既然傳統(tǒng)的分頁方案會產(chǎn)生 skip 大量數(shù)據(jù)的問題,那么能否避免呢?答案是可以的。
改良的做法為:
如下圖所示:
修改后的語句執(zhí)行計劃如下:
{
"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 階段,索引命中及掃描范圍也是非常合理的!
為了對比這兩種方案的性能差異,下面準備了一組測試數(shù)據(jù)。
測試方案
準備10W條數(shù)據(jù),以每頁20條的參數(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ù)的樣本進行實測,結(jié)果如下:
可見,當頁數(shù)越大(數(shù)據(jù)量越大)時,改良的翻頁效果提升越明顯!
這種分頁方案其實采用的就是時間軸(TImeLine)的模式,實際應(yīng)用場景也非常的廣,比如Twitter、微博、朋友圈動態(tài)都可采用這樣的方式。
而同時除了上述的數(shù)據(jù)庫之外,HBase、ElastiSearch 在Range Query的實現(xiàn)上也支持這種模式。
時間軸(TimeLine)的模式通常是做成“加載更多”、上下翻頁這樣的形式,但無法自由的選擇某個頁碼。
那么為了實現(xiàn)頁碼分頁,同時也避免傳統(tǒng)方案帶來的 skip 性能問題,我們可以采取一種折中的方案。
這里參考Google搜索結(jié)果頁作為說明:
通常在數(shù)據(jù)量非常大的情況下,頁碼也會有很多,于是可以采用頁碼分組的方式。
以一段頁碼作為一組,每一組內(nèi)數(shù)據(jù)的翻頁采用ID 偏移量 + 少量的 skip 操作實現(xiàn)
具體的操作如下圖所示:
實現(xiàn)步驟
對頁碼進行分組(groupSize=8, pageSize=20),每組為8個頁碼;
提前查詢 end_offset,同時獲得本組頁碼數(shù)量:
db.articles.find({ _id: { $lt: start_offset } }).sort({_id: -1}).skip(20*8).limit(1)
隨著物聯(lián)網(wǎng),大數(shù)據(jù)業(yè)務(wù)的白熱化,一般企業(yè)級系統(tǒng)的數(shù)據(jù)量也會呈現(xiàn)出快速的增長。而傳統(tǒng)的數(shù)據(jù)庫分頁方案在海量數(shù)據(jù)場景下很難滿足性能的要求。
在本文的探討中,主要為海量數(shù)據(jù)的分頁提供了幾種常見的優(yōu)化方案(以MongoDB作為實例),并在性能上做了一些對比,旨在提供一些參考。
當前名稱:海量數(shù)據(jù)的分頁怎么破
網(wǎng)站路徑:http://m.newbst.com/article12/ppiedc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供Google、網(wǎng)站設(shè)計、網(wǎng)頁設(shè)計公司、移動網(wǎng)站建設(shè)、網(wǎng)站導(dǎo)航、域名注冊
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)