Offset Pagination ในการพัฒนาแอปมือถือ: คืออะไรและนำไปใช้อย่างไร

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-03-11 เวลาอ่าน: 10 นาที

Offset Pagination — การแบ่งหน้าแบบออฟเซ็ต — วิธีการโหลดข้อมูลแบบแบ่งหน้าผ่าน HTTP API โดยไคลเอนต์ส่งพารามิเตอร์ offset (ออฟเซ็ตจากจุดเริ่มต้น) และ limit (ขนาดหน้า) และเซิร์ฟเวอร์ส่งคืนเรกคอร์ดจากตำแหน่ง offset ตามข้อมูลจาก REST API Tutorial แนวทางนี้ถูกใช้อย่างแพร่หลายในบริการ RESTful เนื่องจากความเรียบง่ายในการนำไปใช้ อย่างไรก็ตาม ในปริมาณข้อมูลขนาดใหญ่ การแบ่งหน้าแบบออฟเซ็ตจะสูญเสียประสิทธิภาพเนื่องจากการสแกนทั้งตารางจนถึงตำแหน่งที่ต้องการ

ประเด็นสำคัญ

  • Offset Pagination — วิธีการแบ่งหน้าที่เซิร์ฟเวอร์ข้าม N เรกคอร์ดและส่งคืน M เรกคอร์ดถัดไป
  • ความเรียบง่าย ในการนำไปใช้ทำให้เป็นมาตรฐานสำหรับ REST API และไคลเอนต์มือถือ
  • ปัญหาการข้าม — เมื่อมีการแทรกเรกคอร์ดระหว่างคำขอ ผู้ใช้จะเห็นรายการซ้ำ
  • การกระโดดของข้อมูล — การลบเรกคอร์ดทำให้หน้าเลื่อนและสูญเสียเนื้อหา
  • การแบ่งหน้าแบบ Cursor-based แก้ปัญหาเหล่านี้ด้วยการใช้ตัวชี้ไปยังเรกคอร์ดสุดท้ายแทนออฟเซ็ต

Offset Pagination คืออะไร?

Offset Pagination เป็นวิธีการแบ่งหน้าข้อมูลที่คำขอของไคลเอนต์ประกอบด้วยสองพารามิเตอร์: offset (จำนวนเรกคอร์ดที่จะข้าม) และ limit (จำนวนเรกคอร์ดที่จะส่งคืน) เซิร์ฟเวอร์ดำเนินการ查询 SQL ด้วย OFFSET และ LIMIT ข้ามจำนวนแถวที่ระบุ และส่งคืนชุดผลลัพธ์ขนาดคงที่

วิธีการนี้เกิดขึ้นในฐานข้อมูลเชิงสัมพันธ์เพื่อเป็นวิธีที่ง่ายที่สุดในการจัดระเบียบการนำทางระหว่างหน้า และถูกถ่ายโอนไปยัง HTTP API พร้อมกับการพัฒนาสถาปัตยกรรม REST Offset Pagination ไม่ต้องการการจัดเก็บสถานะบนเซิร์ฟเวอร์ — แต่ละคำขอเป็นอิสระและมีข้อมูลทั้งหมดที่จำเป็นสำหรับการ查询

ตามรายงานการออกแบบ API ของ Postman (2025) การแบ่งหน้าแบบออฟเซ็ตถูกใช้ใน 72% ของ REST API สาธารณะ ทำให้เป็นมาตรฐานที่โดดเด่นแม้จะมีข้อจำกัดด้านประสิทธิภาพที่ทราบในชุดข้อมูลขนาดใหญ่

โครงสร้างคำขอและการตอบสนอง

คำขอ REST ทั่วไปที่มี Offset Pagination ประกอบด้วยพารามิเตอร์查询 offset และ limit การตอบสนองประกอบด้วยรายการเรกคอร์ดของหน้าที่ขอและข้อมูลเมตาสำหรับสร้างอินเทอร์เฟซการนำทาง

พารามิเตอร์ limit จำกัดจำนวนเรกคอร์ดที่ส่งคืนและปกป้องเซิร์ฟเวอร์และไคลเอนต์จากการโหลดมากเกินไป ค่า limit ทั่วไปอยู่ระหว่าง 10 ถึง 50 เรกคอร์ดต่อหน้าขึ้นอยู่กับความซับซ้อนของข้อมูล

kotlin
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 ทำงานอย่างไร

Offset Pagination แปลงเป็นคำสั่ง SQL ด้วยโครงสร้าง OFFSET และ FETCH NEXT (หรือ LIMIT ใน MySQL/SQLite) เซิร์ฟเวอร์ฐานข้อมูลสแกนตาราง ข้ามจำนวนแถวเท่ากับออฟเซ็ต และส่งคืน limit แถวถัดไป ยิ่งออฟเซ็ตมากเท่าไร คำสั่งก็ยิ่งใช้เวลานานขึ้นเท่านั้น

ปัญหาด้านประสิทธิภาพเกี่ยวข้องกับข้อเท็จจริงที่ว่าฐานข้อมูลไม่สามารถข้ามไปยังตำแหน่งออฟเซ็ตได้โดยตรง — ต้องอ่านและละทิ้งแถวก่อนหน้าทั้งหมด ที่ offset = 100000 และ limit = 20 ระบบจัดการฐานข้อมูลอ่าน 100,020 แถวและส่งคืนเพียง 20 แถว

คำสั่ง SQL ภายใต้ฝาครอบ

SQL — ภาษาที่เซิร์ฟเวอร์ดำเนินการแบ่งหน้าแบบออฟเซ็ต PostgreSQL และ MySQL ใช้ LIMIT ในขณะที่ SQL Server และ Oracle ใช้ OFFSET...FETCH ระบบจัดการฐานข้อมูลต่างๆ ปรับคำสั่งนี้ให้เหมาะสมแตกต่างกัน แต่ปัญหาการสแกนพื้นฐานยังคงเหมือนเดิม

sql
SELECT id, title, created_at
FROM articles
ORDER BY created_at DESC
OFFSET 100000 ROWS
FETCH NEXT 20 ROWS ONLY;

ปัญหาความสอดคล้อง

ความสอดคล้อง ของข้อมูลเป็นข้อเสียหลักของ Offset Pagination เมื่อทำงานกับชุดข้อมูลแบบไดนามิก หากมีการเพิ่มเรกคอร์ดใหม่ที่จุดเริ่มต้นของตารางระหว่างคำขอของผู้ใช้สองครั้ง เรกคอร์ดที่มีอยู่ทั้งหมดจะเลื่อนตำแหน่ง ผู้ใช้จะเห็นรายการซ้ำหรือช่องว่าง

พิจารณาตารางที่มี 100 เรกคอร์ดโดยมี limit = 20 ในหน้า 1 ผู้ใช้เห็นเรกคอร์ด 1-20 ผู้ดูแลระบบเพิ่ม 5 เรกคอร์ดใหม่ ในหน้า 2 ผู้ใช้เห็นเรกคอร์ด 26-45 แทนที่ 21-40 ที่คาดไว้ — เรกคอร์ด 21-25 ถูกข้าม และเรกคอร์ด 21-25 จากชุดก่อนหน้า ถูกทำซ้ำ ในหน้า 1

Offset vs Cursor-based: เปรียบเทียบแนวทาง

การแบ่งหน้าแบบ Cursor-based เป็นทางเลือกแทน Offset Pagination ที่ใช้ตัวชี้ไปยังเรกคอร์ดสุดท้ายของหน้าปัจจุบัน แทนที่จะใช้ออฟเซ็ตเชิงตัวเลข ไคลเอนต์ส่งตัวระบุของเรกคอร์ดสุดท้ายที่ได้รับ และเซิร์ฟเวอร์ส่งคืน N เรกคอร์ดถัดไปหลังจากนั้น

แนวทางแบบ cursor-based แก้ปัญหาความสอดคล้อง: ตำแหน่งเคอร์เซอร์ไม่เปลี่ยนแปลงเมื่อมีการแทรกหรือลบเนื่องจากเคอร์เซอร์อ้างอิงถึงเรกคอร์ดเฉพาะ ไม่ใช่ตำแหน่ง อย่างไรก็ตาม มันซับซ้อนกว่าในการนำไปใช้ — ต้องมีฟิลด์ที่ไม่ซ้ำกันที่เรียงลำดับได้ (โดยปกติคือ ID หรือ timestamp)

พารามิเตอร์Offset PaginationCursor-based Pagination
ความเรียบง่ายสูง — สองพารามิเตอร์ตัวเลขปานกลาง — ต้องเข้ารหัสเคอร์เซอร์
ความสอดคล้องต่ำ — รายการซ้ำเมื่อแทรกสูง — เคอร์เซอร์ไม่ได้รับผลกระทบจากการเปลี่ยนแปลง
ประสิทธิภาพลดลงเมื่อออฟเซ็ตใหญ่คงที่ในทุกปริมาณ
กระโดดไปหน้าได้ — สามารถนำทางไปยังหน้าใดก็ได้ไม่ได้ — นำทางตามลำดับเท่านั้น
เหมาะสำหรับตาราง <10K เรกคอร์ด, UI ที่มีเลขหน้าฟีด, เลื่อนไม่สิ้นสุด, ชุดข้อมูลขนาดใหญ่

การเลือกระหว่างแนวทางขึ้นอยู่กับความต้องการ อินเทอร์เฟซ ของผู้ใช้ หากต้องการการนำทางด้วยเลขหน้าและการกระโดดโดยตรง — Offset Pagination ง่ายกว่า สำหรับการเลื่อนไม่สิ้นสุดหรือฟีดข่าว เคอร์เซอร์จะดีกว่า

Keyset pagination

Keyset pagination เป็นรูปแบบหนึ่งของแนวทาง cursor-based ที่การกรองดำเนินการบนคีย์ที่ไม่ซ้ำกันโดยใช้ WHERE แทน OFFSET คำสั่ง SQL ใช้เงื่อนไขเช่น WHERE id > lastId ซึ่งช่วยให้ฐานข้อมูลใช้ดัชนีโดยไม่ต้องสแกนแถวที่ถูกทิ้ง

ตาม PostgreSQL Wiki keyset pagination ทำงานเร็วกว่า 100-1000 เท่าเมื่อเทียบกับคำสั่งแบบออฟเซ็ตที่ออฟเซ็ตขนาดใหญ่ เนื่องจากการสแกนดัชนีแทนที่การสแกนทั้งตาราง ข้อเสียคือไม่สามารถกระโดดไปยังหน้าที่ต้องการได้โดยไม่ต้องเดินตามลำดับ

เมื่อใดควรใช้ Offset Pagination

Offset Pagination เหมาะสมที่สุดสำหรับชุดข้อมูลขนาดเล็กถึงกลาง (สูงถึง 10,000 เรกคอร์ด) ที่ผู้ใช้ต้องการอินเทอร์เฟซแบบเลขหน้า สถานการณ์ทั่วไปรวมถึงแผงผู้ดูแลระบบ รายการคำสั่งซื้อ และแคตตาล็อกที่กรองแล้วพร้อมการแบ่งหน้าตามหน้า

สำหรับแอปพลิเคชันมือถือ การแบ่งหน้าแบบออฟเซ็ตเหมาะเมื่อโหลดข้อมูลประวัติที่การแทรกใหม่เกิดขึ้นน้อยหรือเป็นไปไม่ได้ — ตัวอย่างเช่น ประวัติคำสั่งซื้อของผู้ใช้ รายการงานที่เสร็จสมบูรณ์ คลังธุรกรรม ในสถานการณ์เหล่านี้ ปัญหาความสอดคล้องจะไม่เกิดขึ้น

ไม่แนะนำ สำหรับฟีดโซเชียลมีเดีย รายการความคิดเห็น แชท และชุดข้อมูลแบบไดนามิกอื่นๆ ที่มีการแทรกบ่อยครั้ง ในกรณีเหล่านี้ ช่องว่างและเรกคอร์ดซ้ำจะลดคุณภาพประสบการณ์ผู้ใช้และต้องการตรรกะการลบรายการซ้ำเพิ่มเติมบนไคลเอนต์

แนวทางแบบผสม

การแบ่งหน้าแบบผสม รวม offset และ cursor: คำขอแรกใช้ offset เพื่อแสดงหน้าเริ่มต้น ในขณะที่คำขอถัดไปใช้ cursor สำหรับการโหลดแบบเลื่อนไม่สิ้นสุด แนวทางนี้ใช้ใน Instagram และ Twitter ซึ่งหน้าแรกโหลดผ่าน cursor แต่ offset ใช้ในการคำนวณตำแหน่งเมื่อกลับไปยังมุมมองก่อนหน้า

การนำแนวทางแบบผสมไปใช้ต้องการการจัดเก็บตำแหน่งเสมือนของผู้ใช้บนไคลเอนต์และการประสานงานกลไกการแบ่งหน้าสองแบบบนเซิร์ฟเวอร์ ตามบล็อก Instagram Engineering ทีมของพวกเขาใช้การแบ่งหน้าแบบ cursor-based พร้อมฟิลด์เพิ่มเติม startCursor ที่แทนที่ offset สำหรับการโหลดเริ่มต้น

Offset Pagination ในแอปพลิเคชันมือถือ

แอปพลิเคชันมือถือ ใช้ Offset Pagination ร่วมกับ Retrofit/OkHttp บน Android และ URLSession/Combine บน iOS รูปแบบทั่วไปคือการโหลดหน้าถัดไปเมื่อเลื่อนไปจนสุดรายการผ่าน RecyclerView.OnScrollListener หรือ UICollectionView prefetching

การนำ Offset Pagination ไปใช้บนไคลเอนต์มือถือประกอบด้วยสามองค์ประกอบ: ตัวจัดการการแบ่งหน้า (จัดเก็บ offset ปัจจุบันและ hasMore), ตัวปรับรายการ (แสดงรายการและตัวบ่งชี้การโหลด), และพื้นที่เก็บข้อมูล (ดำเนินการคำขอและจัดการข้อผิดพลาด) Android Jetpack มีไลบรารี Paging 3 ซึ่งรองรับทั้งการแบ่งหน้าแบบ offset และ cursor-based พร้อมใช้งานทันที

การนำไปใช้ใน Kotlin ด้วย Paging 3

Paging 3 เป็นไลบรารี Android Jetpack สำหรับการโหลดข้อมูลแบบแบ่งหน้า มันห่อหุ้มตรรกะการแบ่งหน้า รวมถึงการติดตามออฟเซ็ต การจัดการสถานะการโหลด และการโหลดล่วงหน้าอัตโนมัติเมื่อเลื่อน PagingSource กำหนดคีย์สำหรับหน้าถัดไปและหน้าก่อนหน้า

kotlin
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 กำหนดคีย์ prevKey และ nextKey สำหรับการนำทางระหว่างหน้า ด้วยการแบ่งหน้าแบบออฟเซ็ต prevKey เป็น null เสมอ (ไม่สามารถไปหน้าก่อนหน้าได้โดยไม่บันทึกประวัติ) ในขณะที่ nextKey เพิ่มขึ้นทีละ limit ในการโหลดแต่ละครั้งจนกว่าเซิร์ฟเวอร์จะส่งคืน hasMore = false นี่เป็นโมเดลที่เรียบง่ายและคาดการณ์ได้สำหรับรายการมือถือ

ข้อผิดพลาดทั่วไปกับ Offset Pagination

ข้อผิดพลาดแรก — การพึ่งพาลำดับเรกคอร์ดโดยไม่มีการเรียงลำดับ Offset Pagination ต้องการ การเรียงลำดับ ORDER BY ที่เสถียรบนฟิลด์ที่ไม่ซ้ำกัน หากไม่มี ระบบจัดการฐานข้อมูลอาจส่งคืนเรกคอร์ดในลำดับใดก็ได้ ทำให้เกิดรายการซ้ำและช่องว่างแบบสุ่มระหว่างหน้า

ข้อผิดพลาดที่สอง — การใช้ offset เพื่อคำนวณ เลขหน้า ในอินเทอร์เฟซ สูตร page = offset / limit + 1 ทำงานเฉพาะเมื่อไม่มีเรกคอร์ดถูกลบหรือเพิ่มระหว่างการโหลด ด้วยข้อมูลแบบไดนามิก เลขหน้าจะไม่แม่นยำและผู้ใช้เห็นข้อมูลที่ไม่ถูกต้อง

ข้อผิดพลาดที่สาม — การเพิกเฉยต่อ การหมดเวลา ของคำสั่งที่มี offset ขนาดใหญ่ ที่ offset เกิน 100,000 คำสั่งอาจใช้เวลาหลายสิบวินาที ปิดกั้นอินเทอร์เฟซและใช้ทรัพยากรเซิร์ฟเวอร์ ควรตั้งค่าออฟเซ็ตสูงสุดที่ระดับ API (เช่น 10,000) และใช้การแบ่งหน้าแบบ cursor-based สำหรับปริมาณมาก

ข้อผิดพลาดที่สี่ — การไม่รวม total count ในการตอบสนอง หากไม่มีจำนวนเรกคอร์ดทั้งหมด ไคลเอนต์ไม่สามารถแสดงจำนวนหน้าและใช้การแบ่งหน้าแบบมีเลขได้ อย่างไรก็ตาม COUNT(*) บนตารางขนาดใหญ่มีค่าใช้จ่ายสูง — สำหรับชุดข้อมูลที่เกิน 100,000 เรกคอร์ด ให้ใช้การประมาณหรือจำกัดค่าสูงสุดของ total

คำถามที่พบบ่อย

Offset Pagination แตกต่างจาก Cursor-based อย่างไร?

Offset ใช้ออฟเซ็ตตัวเลขเพื่อข้ามเรกคอร์ด ในขณะที่ cursor ใช้ตัวชี้ไปยังเรกคอร์ดสุดท้ายของหน้าก่อนหน้า Offset ใช้งานได้ง่ายกว่าแต่ประสบปัญหารายการซ้ำเมื่อแทรกและประสิทธิภาพลดลงที่ออฟเซ็ตขนาดใหญ่ Cursor มีความเสถียรไม่ว่าข้อมูลจะเปลี่ยนแปลงอย่างไร

เมื่อใดที่ Offset Pagination มีประสิทธิภาพไม่ดี?

การแบ่งหน้าแบบออฟเซ็ต ไม่มีประสิทธิภาพ ที่ออฟเซ็ตเกิน 10,000 เรกคอร์ดเนื่องจากการสแกนทั้งตาราง นอกจากนี้ยังไม่เหมาะสำหรับชุดข้อมูลแบบไดนามิก (ฟีด แชท) ที่เรกคอร์ดใหม่ปรากฏระหว่างคำขอ — ผู้ใช้เห็นช่องว่างและเรกคอร์ดซ้ำระหว่างการนำทาง

limit ใดเหมาะสมที่สุดสำหรับ Offset Pagination?

limit ที่เหมาะสมขึ้นอยู่กับขนาดเรกคอร์ดและความเร็วเครือข่าย — 10 ถึง 50 รายการต่อหน้า สำหรับรายการที่มีรูปภาพขนาดใหญ่ ใช้ limit = 10-15 สำหรับข้อมูลข้อความ 20-50 อนุญาตให้ไคลเอนต์ระบุ limit ของตนเองเสมอโดยมีค่าสูงสุดบนเซิร์ฟเวอร์ (โดยปกติ 100)

วิธีจัดการกับรายการซ้ำใน Offset Pagination?

เพื่อจัดการกับ รายการซ้ำ ใช้การลบรายการซ้ำฝั่งไคลเอนต์โดย ID ที่ไม่ซ้ำกัน ใช้การเรียงลำดับที่เสถียรบนฟิลด์ที่ไม่ซ้ำกัน หรือเปลี่ยนไปใช้การแบ่งหน้าแบบ cursor-based Android Paging 3 รองรับ key สำหรับการลบรายการซ้ำอัตโนมัติของรายการในรายการ

สามารถใช้ Offset Pagination กับ GraphQL ได้หรือไม่?

ได้ GraphQL รองรับการแบ่งหน้าแบบออฟเซ็ตผ่านอาร์กิวเมนต์ offset และ limit ในคำสั่ง แม้ว่าข้อกำหนด Relay จะแนะนำแนวทางแบบ cursor-based ไลบรารี Apollo GraphQL และ Relay ให้การสนับสนุนในตัวสำหรับการแบ่งหน้าแบบออฟเซ็ตพร้อมการจัดการสถานะหน้าอัตโนมัติ

สรุป

  • Offset Pagination — วิธีการแบ่งหน้าด้วยพารามิเตอร์ offset และ limit สำหรับข้ามและจำกัดเรกคอร์ดเมื่อโหลดข้อมูลแบบแบ่งหน้าจาก API
  • ความเรียบง่าย ในการนำไปใช้และความเป็นอิสระของคำขอทำให้ Offset Pagination เป็นแนวทางมาตรฐานสำหรับ 72% ของ REST API (ข้อมูล Postman, 2025)
  • ประสิทธิภาพ ลดลงที่ออฟเซ็ตเกิน 10,000 เนื่องจากการสแกนตารางถึงตำแหน่งเป้าหมาย — ฐานข้อมูลอ่านแถวที่ถูกทิ้งทั้งหมด
  • ปัญหาความสอดคล้อง — การแทรกและลบเรกคอร์ดระหว่างคำขอทำให้เกิดรายการซ้ำและช่องว่างในผลลัพธ์ของหน้า
  • การแบ่งหน้าแบบ Cursor-based แก้ปัญหาของ Offset Pagination โดยใช้ตัวชี้ไปยังเรกคอร์ดสุดท้ายแทนออฟเซ็ตตัวเลข
  • แนวทางแบบผสม รวม offset ของหน้าแรกกับการโหลดแบบ cursor สำหรับการเลื่อนไม่สิ้นสุดในแอปพลิเคชันมือถือ
  • คำแนะนำ — ใช้ Offset Pagination สำหรับชุดข้อมูลคงที่สูงถึง 10,000 เรกคอร์ดและเปลี่ยนไปใช้เคอร์เซอร์สำหรับปริมาณมากและข้อมูลแบบไดนามิก

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม