Offset Pagination — sidnumrering med offset — metod för sidvis laddning av data via HTTP API. Klienten skickar parametrarna offset (förskjutning från början) och limit (sidstorlek), och servern returnerar poster från offset-positionen. Enligt REST API Tutorial används detta tillvägagångssätt i stor utsträckning i RESTful-tjänster på grund av enkel implementering. Vid stora datamängder förlorar dock offset-sidnumrering prestanda på grund av fullständig genomsökning av tabellen till önskad position.
Huvudpunkter
Offset Pagination — är en metod för sidvis uppdelning av data, där klientförfrågan innehåller två parametrar: offset (hur många poster som ska hoppas över) och limit (hur många poster som ska returneras). Servern utför en SQL-fråga med OFFSET och LIMIT, hoppar över det angivna antalet rader och returnerar resultatuppsättningen med fast storlek.
Metoden uppstod i relationsdatabaser som det enklaste sättet att organisera navigering mellan sidor och överfördes till HTTP API i takt med utvecklingen av REST-arkitektur. Offset Pagination kräver inte lagring av tillstånd på servern — varje förfrågan är oberoende och innehåller all information som behövs för att hämta data.
Enligt forskning om API-design från Postman (2025) används offset-sidnumrering i 72% av offentliga REST API:er, vilket gör det till den dominerande standarden trots kända prestandabegränsningar vid stora datamängder.
En typisk REST-förfrågan med Offset Pagination innehåller frågeparametrarna offset och limit. Svaret innehåller en lista med poster från den begärda sidan och metadata för att bygga navigeringsgränssnittet.
Parametern limit begränsar antalet returnerade poster och skyddar servern och klienten från överdriven belastning. Typiska limit-värden — från 10 till 50 poster per sida beroende på datakomplexitet.
data class PageRequest(
val offset: Int,
val limit: Int
)
data class PageResponse<T>(
val items: List<T>,
val total: Int,
val hasMore: Boolean
)
fun RetrofitApi.fetchPage(request: PageRequest): Call<PageResponse<Item>>
Offset Pagination omvandlas till en SQL-fråga med konstruktionerna OFFSET och FETCH NEXT (eller LIMIT i MySQL/SQLite). Databasservern skannar tabellen, hoppar över antalet rader som är lika med offset och returnerar nästa limit rader. Ju större offset, desto längre tid tar frågan.
Prestandaproblemet beror på att databasen inte direkt kan gå till offset-positionen — den måste läsa och kassera alla föregående rader. Vid offset = 100000 och limit = 20 läser DBMS 100020 rader och returnerar endast 20.
SQL — språket som servern använder för att utföra offset-sidnumrering. I PostgreSQL och MySQL används LIMIT, i SQL Server och Oracle — OFFSET...FETCH. Olika DBMS optimerar denna fråga på sina egna sätt, men det grundläggande skanningsproblemet förblir detsamma.
SELECT id, title, created_at
FROM articles
ORDER BY created_at DESC
OFFSET 100000 ROWS
FETCH NEXT 20 ROWS ONLY;
Datakonsistens — den största nackdelen med Offset Pagination vid arbete med dynamiska uppsättningar. Om en ny post läggs till i början av tabellen mellan två användarförfrågningar, flyttas alla befintliga poster. Användaren ser dubbletter eller hopp.
Föreställ dig en tabell med 100 poster med limit = 20. På sida 1 ser användaren poster 1-20. Administratören lägger till 5 nya poster. På sida 2 ser användaren poster 26-45 istället för förväntade 21-40 — poster 21-25 hoppas över och poster 21-25 från föregående uppsättning dupliceras på sida 1.
Cursor-based pagination — ett alternativ till Offset Pagination, som använder en pekare till den sista posten på den aktuella sidan. Istället för numerisk förskjutning skickar klienten identifieraren för den senast mottagna posten, och servern returnerar nästa N poster efter den.
Cursor-based-metoden löser konsistensproblemet: markörens position ändras inte vid insättningar eller borttagningar, eftersom markören refererar till en specifik post, inte till en position. Den är dock mer komplex att implementera — kräver ett unikt sorterbart fält (vanligtvis ID eller timestamp).
| Parameter | Offset Pagination | Cursor-based Pagination |
|---|---|---|
| Enkelhet | Hög — två numeriska parametrar | Medel — kodning av markör krävs |
| Konsistens | Låg — dubbletter vid insättning | Hög — markör oberoende av ändringar |
| Prestanda | Minskar med växande offset | Stabil vid alla volymer |
| Hopp till sida | Ja — kan gå till valfri sida | Nej — endast sekventiell navigering |
| Lämplig för | Tabeller <10K poster, UI med sidnummer | Flöden, oändlig scroll, stora uppsättningar |
Valet mellan metoder beror på användarens gränssnittskrav. Om navigering med sidnummer och direkt hopp behövs — är Offset Pagination enklare. För oändlig scroll eller nyhetsflöden är markörer att föredra.
Keyset pagination — en variant av cursor-based-metoden, där filtrering sker med en unik nyckel med WHERE istället för OFFSET. SQL-frågan använder villkoret WHERE id > lastId, vilket gör att databasen kan använda index utan att skanna kasserade rader.
Enligt PostgreSQL Wiki körs keyset pagination 100-1000 gånger snabbare än en offset-fråga vid stora förskjutningar, eftersom indexskanning ersätter fullständig tabellgenomgång. Nackdel — omöjlighet att hoppa till en godtycklig sida utan sekventiell genomgång.
Offset Pagination är optimal för små och medelstora datamängder (upp till 10000 poster), där användaren behöver ett gränssnitt med sidnummer. Typiska scenarier — adminpaneler, orderlistor, kataloger med filtrering och sidnumrering.
För mobila applikationer är offset-sidnumrering lämplig vid laddning av historisk data, där insättning av nya poster är sällsynt eller omöjlig — till exempel användarens orderhistorik, lista över slutförda uppgifter, transaktionsarkiv. I dessa scenarier uppstår inte konsistensproblemet.
Rekommenderas inte att använda Offset Pagination för sociala media-flöden, kommentarlistor, chattar och andra dynamiska uppsättningar med frekventa insättningar. I dessa fall försämrar hopp och dubbletter användarupplevelsen och kräver ytterligare dedupliceringslogik på klienten.
Hybridsidnumrering kombinerar offset och markör: den första förfrågan använder offset för att visa startsidan, och efterföljande använder markör för att ladda oändlig scroll. Denna metod är implementerad i Instagram och Twitter, där den första sidan laddas via markör, men offset används för att beräkna positionen vid återgång till föregående vy.
Implementering av hybridmetoden kräver lagring av användarens virtuella position på klienten och koordination av två sidnumreringsmekanismer på servern. Enligt Instagram Engineering-bloggen använder deras team cursor-based-sidnumrering med ett extra fält startCursor, som ersätter offset för initial laddning.
Mobila applikationer använder Offset Pagination tillsammans med Retrofit/OkHttp på Android och URLSession/Combine på iOS. Det typiska mönstret — laddning av nästa sida vid scrollning till slutet av listan via RecyclerView.OnScrollListener eller UICollectionView prefetching.
Implementering av offset-sidnumrering på en mobil klient omfattar tre komponenter: sidnumreringshanterare (lagrar aktuell offset och hasMore), listadapter (visar element och laddningsindikator) och repository (utför förfrågningar och hanterar fel). Android Jetpack erbjuder Paging 3-biblioteket, som stöder både offset- och cursor-based-sidnumrering direkt från lådan.
Paging 3 — Android Jetpack-bibliotek för sidvis laddning av data. Det kapslar in sidnumreringslogik, inklusive offset-spårning, laddningsstatushantering och automatisk laddning vid scrollning. PagingSource definierar nycklar för nästa och föregående sida.
class OffsetPagingSource(
private val api: ApiService,
private val limit: Int = 20
) : PagingSource<Int, Item>() {
override suspend fun load(
params: LoadParams<Int>
): LoadResult<Int, Item> {
val offset = params.key ?: 0
return try {
val response = api.getItems(offset, limit)
LoadResult.Page(
data = response.items,
prevKey = null,
nextKey = if (response.hasMore) offset + limit else null
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
}
PagingSource definierar nycklarna prevKey och nextKey för sidnavigering. Vid offset-sidnumrering är prevKey alltid null (kan inte gå till föregående sida utan att spara historik), och nextKey ökar med limit vid varje laddning, tills servern returnerar hasMore = false. Detta är en enkel och förutsägbar modell för mobila listor.
Första misstaget — att förlita sig på posternas ordning utan sortering. Offset Pagination kräver stabil ORDER BY-sortering på ett unikt fält. Utan detta kan DBMS returnera poster i godtycklig ordning, vilket leder till slumpmässiga dubbletter och hopp mellan sidor.
Andra misstaget — att använda offset för att beräkna sidnummer i gränssnittet. Formeln page = offset / limit + 1 fungerar endast under förutsättning att ingen post har tagits bort eller lagts till mellan laddningar. Vid dynamisk data blir sidnumret felaktigt och användaren ser felaktig information.
Tredje misstaget — att ignorera timeouts för frågor med stor offset. Vid offset över 100000 kan frågan ta tiotals sekunder, blockera gränssnittet och förbruka serverresurser. Det rekommenderas att ställa in ett maximalt offsetvärde på API-nivå (till exempel 10000) och använda cursor-based-sidnumrering för stora volymer.
Fjärde misstaget — att inte lägga till total count i svaret. Utan det totala antalet poster kan klienten inte visa antalet sidor och implementera sidnumrering med nummer. Att beräkna COUNT(*) på stora tabeller är dock också kostsamt — för uppsättningar över 100000 poster, använd uppskattningar eller begränsa det maximala totalvärdet.
Vanliga frågor
Offset använder numerisk förskjutning (offset) för att hoppa över poster, medan markören använder en pekare till den sista posten på föregående sida. Offset är enklare att implementera, men lider av dubbletter vid insättning och prestandaförlust vid stora förskjutningar. Markören är stabil vid alla dataförändringar.
Offset-sidnumrering är ineffektiv vid offset över 10000 poster på grund av fullständig tabellskanning. Den är också olämplig för dynamiska uppsättningar (flöden, chattar) där nya poster dyker upp mellan förfrågningar — användaren ser hopp och dubbletter vid navigering.
Optimal limit beror på poststorlek och nätverkshastighet — från 10 till 50 element per sida. För listor med stora bilder, använd limit = 10-15, för textdata — 20-50. Tillåt alltid klienten att specificera sin egen limit med en maximal begränsning på servern (vanligtvis 100).
För att hantera dubbletter, använd deduplicering på klienten baserat på unikt ID, tillämpa stabil sortering på ett unikt fält eller byt till cursor-based-sidnumrering. Android Paging 3 stöder nyckel för automatisk deduplicering av listelement.
Ja, GraphQL stöder offset-sidnumrering genom argumenten offset och limit i frågan, även om Relay-specifikationen rekommenderar cursor-based-metoden. Biblioteken Apollo GraphQL och Relay erbjuder inbyggt stöd för offset-sidnumrering med automatisk sidstatushantering.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också