兩種分頁(yè)方式
傳統(tǒng)的分頁(yè)方式頁(yè)最典型的特點(diǎn)是頁(yè)面上有一連串的頁(yè)碼,和電梯按鈕相似,因此頁(yè)常被稱之為電梯式分頁(yè)。
電梯式特點(diǎn):
- 通過(guò)頁(yè)碼進(jìn)行分頁(yè)
- 通過(guò)點(diǎn)擊上/下頁(yè)按鈕可實(shí)現(xiàn)頁(yè)面切換
- 通過(guò)點(diǎn)擊頁(yè)碼可實(shí)現(xiàn)頁(yè)面切換
- 可直接跳轉(zhuǎn)至指定頁(yè)面
- 多用于 PC 端,適合需要查找特定內(nèi)容的頁(yè)面
- 需要計(jì)算總數(shù)or總頁(yè)數(shù)(搜索引擎等場(chǎng)景也可以無(wú)需計(jì)算渴语,相應(yīng)的跳轉(zhuǎn)按鈕會(huì)有所限制)
電梯式分頁(yè)適用于傳統(tǒng)的頁(yè)面布局丁逝,而在移動(dòng)端頁(yè)面上更流行的是瀑布流布局方式汁胆,相應(yīng)的分頁(yè)方式稱為流式分頁(yè),有時(shí)也稱為無(wú)限下拉式分頁(yè)霜幼。
流式分頁(yè)特點(diǎn):
- 通過(guò)滾動(dòng)/上拉/點(diǎn)擊等方式加載新一頁(yè)
- 無(wú)頁(yè)碼
- 無(wú)上/下頁(yè)按鈕
- 不可跳轉(zhuǎn)至指定頁(yè)面
- pc端和移動(dòng)端均有使用嫩码,適合UGC、視覺(jué)內(nèi)容以及推薦系統(tǒng)等“瀏覽型”頁(yè)面
- 無(wú)需計(jì)算總數(shù)or總頁(yè)數(shù)
對(duì)于用電梯式分頁(yè)方式的接口罪既,有頁(yè)碼铸题、頁(yè)大小以及結(jié)果總數(shù)等參數(shù),頁(yè)碼需要明確是從1開(kāi)始還是0開(kāi)始琢感,一般使用pageNumber表示從1開(kāi)始丢间,使用pageIndex表示頁(yè)索引從0開(kāi)始,pageSize和limint均可表示單頁(yè)數(shù)據(jù)量驹针。totalCount和totalPage可以分別表示總數(shù)據(jù)條數(shù)和總頁(yè)數(shù)烘挫。
- page(pageNumber、pageIndex)
- pageSize/limit
- total(totalCount柬甥、totalPage)
當(dāng)然饮六,對(duì)于流式分頁(yè),上述的接口設(shè)計(jì)也滿足要求苛蒲,但在大多數(shù)場(chǎng)景下卤橄,可以使用更適合的設(shè)計(jì),比如游標(biāo)式(下文會(huì)介紹)臂外。
常見(jiàn)問(wèn)題
數(shù)據(jù)缺失:獲取后頁(yè)時(shí)窟扑,前頁(yè)數(shù)據(jù)有刪除。此時(shí)漏健,本應(yīng)出現(xiàn)在后頁(yè)的內(nèi)容被“頂”到前頁(yè)嚎货,而前頁(yè)已經(jīng)加載過(guò)了不會(huì)重新加載,后頁(yè)又無(wú)此內(nèi)容蔫浆,從而無(wú)法被用戶看到殖属。
數(shù)據(jù)重復(fù):獲取后頁(yè)時(shí),前頁(yè)數(shù)據(jù)有插入克懊。此時(shí),原本在前頁(yè)的內(nèi)容被“壓”到后頁(yè)七蜘,導(dǎo)致前后也都有此數(shù)據(jù)谭溉,在用戶端就是重復(fù)數(shù)據(jù)。
性能問(wèn)題:較大頁(yè)碼的數(shù)據(jù)獲取時(shí)性能會(huì)下降橡卤,計(jì)算總數(shù)也會(huì)帶來(lái)額外的開(kāi)銷扮念。
解決方案:
- 游標(biāo)式分頁(yè)參數(shù)設(shè)計(jì):
- 客戶端記錄當(dāng)前分頁(yè)的最后一條數(shù)據(jù)的 ID(curcor)
- 請(qǐng)求下一頁(yè)的時(shí)候,從這個(gè) ID 開(kāi)始獲取一頁(yè)大斜炭狻(pageSize)的內(nèi)容
優(yōu)點(diǎn):
- 能夠避免數(shù)據(jù)重復(fù)/遺漏
- 無(wú)需計(jì)算offset柜与,性能更穩(wěn)定
缺點(diǎn):
- 只適用于按照時(shí)間追加的方式等簡(jiǎn)單排序
- 無(wú)法跳到指定頁(yè)巧勤,適合流式分頁(yè)
- 一次性下發(fā)或緩存所有ID
- 請(qǐng)求第 1 頁(yè)數(shù)據(jù)之前/時(shí)先緩存所有 ID 列表
- 請(qǐng)求第 2,3弄匕,…n 頁(yè)數(shù)據(jù)時(shí)颅悉,只需傳入單頁(yè)相關(guān)的 ID 列表參數(shù)
優(yōu)點(diǎn):
- 可將排序由數(shù)據(jù)庫(kù)移到應(yīng)用容器,同時(shí)僅取ID一列迁匠,降低DB壓力剩瓶。
- 無(wú)需重復(fù)計(jì)算總數(shù),性能更優(yōu)更穩(wěn)定城丧。
缺點(diǎn):
- 僅適用于 id 列表不會(huì)很大(數(shù)百條數(shù)據(jù))的業(yè)務(wù)場(chǎng)景
- 限定數(shù)據(jù)生成時(shí)間
分頁(yè)參數(shù)中再額外多一個(gè)timestamp參數(shù)延曙,第一頁(yè)請(qǐng)求時(shí)timestamp由后端生成并傳給前端,前端在后頁(yè)查詢時(shí)將此值再次傳回給后端亡哄,后端在查詢條件中只用此值限定數(shù)據(jù)插入時(shí)間枝缔。
此方法可以解決數(shù)據(jù)重復(fù),但無(wú)法解決數(shù)據(jù)缺失蚊惯,因此適用于只增不刪或極少刪的場(chǎng)景愿卸。
常見(jiàn)性能問(wèn)題優(yōu)化方法
- SQL查兩次,先查出所需頁(yè)的ID拣挪,再用IN查詢單頁(yè)數(shù)據(jù)擦酌。
- 對(duì)熱門(mén)數(shù)據(jù)緩存,如前n頁(yè)菠劝。
- 在頁(yè)數(shù)很靠后時(shí)赊舶,MySQL的limit會(huì)有比較大的性能問(wèn)題,可以按倒數(shù)第n頁(yè)的思路將排序方式反轉(zhuǎn)查詢赶诊。
另一種分頁(yè)參數(shù)設(shè)計(jì)
在使用數(shù)據(jù)庫(kù)做分頁(yè)查詢時(shí)笼平,常見(jiàn)的方式通過(guò)行號(hào)rownum(SQL Server 、Oracle)或偏移offset(MySQL舔痪、SQL Server)來(lái)實(shí)現(xiàn)寓调,因此有時(shí)候會(huì)將接口的參數(shù)設(shè)計(jì)成rowStart和rowCount。
rowStart锄码,起始行索引夺英,從0開(kāi)始,rowStart = pageIndex * pageSize
rowCount滋捶,單頁(yè)行數(shù)痛悯,即pageSize
但這種行方式設(shè)計(jì)要是需要轉(zhuǎn)為page參數(shù),卻容易出現(xiàn)不兼容重窟,比如rowStart=1载萌,rowCount=5,此時(shí)上述的轉(zhuǎn)換關(guān)系不成立。之前在做接口切換時(shí)遇到過(guò)一次扭仁,為做到兼容垮衷,使用了如下的轉(zhuǎn)換算法,本質(zhì)是找出可以覆蓋到rowStart到rowEnd(rowStart+rowCount)的最小pageIndex和pageSize乖坠,然后從結(jié)果中取出最終需要的subList即可搀突。
/// 分頁(yè)參數(shù)換算
/// </summary>
/// <param name="rowIndexStart">起始行索引,從0開(kāi)始瓤帚,包含</param>
/// <param name="rowCount">單頁(yè)行數(shù)</param>
/// <param name="pageIndex">頁(yè)碼索引描姚,從0開(kāi)始</param>
/// <param name="pageSize">單頁(yè)行數(shù)</param>
/// <param name="resultIndexStart">最終結(jié)果起始索引,從0開(kāi)始戈次,包含</param>
private static void Row2Page(int rowIndexStart, int rowCount, out int pageIndex, out int pageSize, out int resultIndexStart)
{
if (rowIndexStart < 0 || rowCount <= 0)
{
pageIndex = 0;
pageSize = 10;
resultIndexStart = 0;
return;
}
pageSize = rowCount;
while (rowIndexStart % pageSize + rowCount > pageSize)
{
pageSize++;
}
pageIndex = rowIndexStart / pageSize;
resultIndexStart = rowIndexStart - pageIndex * pageSize;
}