ทุกๆ ปลายทางรายการจะต้องเจอกับคำถามเดียวกัน: จะแบ่งคำสั่งซื้อ 2 ล้านรายการออกเป็นหน้าๆ ให้ไคลเอนต์เรียกดูได้อย่างไร? เลือกการแบ่งหน้าแบบ Offset แล้วคุณจะได้ SQL ที่เรียบง่ายพร้อมหมายเลขหน้าที่ผู้ใช้เข้าใจ เลือกการแบ่งหน้าแบบ Cursor-based แล้วคุณจะได้ผลลัพธ์ที่เสถียรพร้อมความหน่วงที่คงที่ในทุกระดับความลึก แต่คุณจะเสียความสามารถในการ "ข้ามไปยังหน้า 47" ไป
ทีมส่วนใหญ่เลือก Offset เพราะเป็นค่าเริ่มต้นในทุกบทเรียน จากนั้นเมื่อตารางคำสั่งซื้อมีข้อมูลหลายล้านแถว หน้าที่ 4,000 ก็เริ่มหมดเวลา และผู้ใช้รายงานว่าเห็นข้อมูลซ้ำกันสองครั้งขณะเลื่อนดู คู่มือนี้จะครอบคลุมวิธีการทำงานของทั้งสองสไตล์ ข้อจำกัดของ Offset เหตุใด Stripe และ Slack จึงใช้ Cursor และวิธีการทดสอบทั้งสองสไตล์ด้วยคำขอแบบลูกโซ่ใน Apidog เมื่ออ่านจบ คุณจะทราบได้อย่างชัดเจนว่ารูปแบบใดเหมาะสมกับปลายทางของคุณ
หากคุณต้องการภาพรวมที่กว้างขึ้นก่อน คู่มือการแบ่งหน้า API ของเราครอบคลุมทุกกลยุทธ์แบบเปรียบเทียบ บทความนี้จะเจาะลึกสองกลยุทธ์ที่สำคัญที่สุด
การแบ่งหน้าแบบ Offset ทำงานอย่างไร
การแบ่งหน้าแบบ Offset จะจับคู่โดยตรงกับ SQL ไคลเอนต์ส่งหมายเลขหน้าและขนาดหน้า; เซิร์ฟเวอร์จะแปลงเป็น LIMIT และ OFFSET
SELECT id, customer_id, total_cents, created_at
FROM orders
ORDER BY created_at DESC
LIMIT 25 OFFSET 50;
การคิวรีนั้นจะส่งคืนหน้า 3 ของรายการคำสั่งซื้อของคุณ โดยมี 25 แถวต่อหน้า คำขอจะมีลักษณะดังนี้:
GET /v1/orders?page=3&per_page=25
และนี่คือการตอบกลับทั่วไป:
{
"data": [
{
"id": "ord_8821",
"customer_id": "cus_1932",
"total_cents": 4599,
"created_at": "2026-08-30T14:22:07Z"
}
],
"page": 3,
"per_page": 25,
"total": 1848203,
"total_pages": 73929
}
ข้อดีนั้นชัดเจน ไคลเอนต์สามารถข้ามไปยังหน้าใดก็ได้ เซิร์ฟเวอร์สามารถส่งคืนจำนวนรวมได้ นักพัฒนาคนใดก็สามารถสร้างมันขึ้นมาได้ภายในบ่ายวันเดียว สำหรับตารางผู้ดูแลระบบขนาดเล็ก นี่คือตัวเลือกที่เหมาะสม และ คู่มือการแบ่งหน้าใน REST API แบบทีละขั้นตอนของเราจะอธิบายการสร้าง Offset แบบเต็มรูปแบบ
แต่ Offset มีปัญหาโครงสร้างสองประการ ซึ่งทั้งสองประการไม่ปรากฏในการพัฒนา แต่จะปรากฏในการใช้งานจริง
ปัญหาที่ 1: การเลื่อนของหน้า (Page drift)
Offset นับแถวจากด้านบนของผลลัพธ์ที่จัดเรียงไว้ ไม่ทราบว่าไคลเอนต์เห็นแถวใดไปแล้วบ้าง ดังนั้นเมื่อมีการแทรกหรือลบแถวระหว่างคำขอ หน้าต่างๆ ก็จะเลื่อนไปจากที่ไคลเอนต์เคยเห็น
สมมติว่าผู้ใช้โหลดหน้า 1 ของคำสั่งซื้อที่จัดเรียงจากใหม่สุดไปเก่าสุด แถวที่ 1 ถึง 25 ขณะที่พวกเขากำลังอ่าน มีคำสั่งซื้อใหม่ 3 รายการเข้ามา พวกเขาขอหน้า 2 ซึ่งคือ OFFSET 25 แถวที่ 23, 24 และ 25 จากการตอบกลับครั้งแรกถูกดันลงไปอยู่ที่ตำแหน่ง 26 ถึง 28 ตอนนี้ผู้ใช้เห็นแถวเหล่านั้นอีกครั้ง เกิดรายการซ้ำซ้อน
การลบจะทำให้เกิดผลตรงกันข้าม ลบ 3 แถวออกจากหน้า 1 ขณะที่ผู้ใช้กำลังอ่าน และ OFFSET 25 จะข้าม 3 แถวที่ผู้ใช้ไม่เคยเห็นไป เกิดการสูญหายของข้อมูลโดยไม่มีข้อผิดพลาด และไม่มีใครได้รับแจ้ง
สำหรับรายงานรายเดือนที่ไม่มีใครเลื่อนดูแบบเรียลไทม์ การเลื่อนของหน้า (drift) ไม่เป็นอันตราย สำหรับฟีดกิจกรรม ปลายทางสำหรับการซิงค์ หรือสิ่งใดก็ตามที่สคริปต์เรียกดูหน้าทีละหน้าในขณะที่การเขียนข้อมูลยังคงดำเนินอยู่ การเลื่อนของหน้าหมายถึงข้อมูลที่ซ้ำซ้อนหรือขาดหายไป ซึ่งผู้ใช้งานจะสังเกตเห็นได้
ปัญหาที่ 2: การทำ Offset ลึกๆ จะสแกนข้อมูลทั้งหมดที่ข้ามไป
OFFSET 500000 ไม่ได้เป็นการเคลื่อนย้ายไปยังแถวที่ 500,001 โดยตรง ฐานข้อมูลจะไล่ดัชนีผ่านข้อมูลครึ่งล้านรายการ ทิ้งข้อมูลเหล่านั้นไป แล้วจึงส่งคืน 25 แถวของคุณ ค่าใช้จ่ายจะเพิ่มขึ้นตามความลึกแบบเชิงเส้น: O(n) โดยที่ n คือค่า offset
ตัวเลขจริงจะทำให้เรื่องนี้ชัดเจนขึ้น ในตารางคำสั่งซื้อของ Postgres ที่มี 2 ล้านแถวและมีดัชนีบน created_at:
LIMIT 25 OFFSET 0อ่านรายการดัชนี 25 รายการ ใช้เวลาเพียงไม่กี่มิลลิวินาทีLIMIT 25 OFFSET 100000อ่านรายการ 100,025 รายการและทิ้ง 100,000 รายการ ใช้เวลาหลายสิบมิลลิวินาทีLIMIT 25 OFFSET 1500000อ่านรายการ 1.5 ล้านรายการ ตอนนี้คุณกำลังใช้เวลาหลายร้อยมิลลิวินาที ถือบัฟเฟอร์และสิ้นเปลือง CPU เพียงเพื่อหน้าเดียว
บทความเกี่ยวกับการไม่ใช้ Offset ของ Markus Winand ใน Use The Index, Luke สาธิตค่าใช้จ่ายนี้ด้วยแผนการคิวรีและคุ้มค่าที่จะอ่านทั้งหมด รูปแบบในการผลิตคือบันทึกการคิวรีที่ช้าซึ่งมีคำขอ offset สูงเป็นส่วนใหญ่ ซึ่งมักจะมาจาก crawler ตัวเดียวที่กำลังเดินสำรวจทุกหน้าของ API สาธารณะของคุณอย่างขยันขันแข็ง ลูกค้ารายเดียว และค่า p99 ของคุณก็เพิ่มขึ้นเป็นสองเท่า
การแบ่งหน้าแบบ Cursor-based ทำงานอย่างไร
การแบ่งหน้าแบบ Cursor-based หรือที่เรียกว่า Keyset Pagination จะละทิ้งตัวนับแถว แทนที่จะบอกว่า “ข้าม 50 แถว” ไคลเอนต์จะบอกว่า “ให้ฉันดูแถวหลังจากบันทึกเฉพาะนี้” Cursor จะระบุแถวสุดท้ายที่ไคลเอนต์เห็น เพื่อให้เซิร์ฟเวอร์สามารถค้นหาชุดข้อมูลถัดไปได้โดยตรง
SQL ใช้การเปรียบเทียบแถวบนคีย์การจัดเรียงแทน OFFSET:
SELECT id, customer_id, total_cents, created_at
FROM orders
WHERE (created_at, id) < ('2026-08-30T14:22:07Z', 'ord_8821')
ORDER BY created_at DESC, id DESC
LIMIT 25;
สังเกตการเปรียบเทียบสองคอลัมน์ created_at เพียงอย่างเดียวไม่ซ้ำกัน; คำสั่งซื้อสองรายการอาจเกิดขึ้นในมิลลิวินาทีเดียวกัน และคีย์การจัดเรียงที่ไม่ซ้ำกันหมายความว่าแถวอาจถูกข้ามหรือซ้ำซ้อนกันที่ขอบหน้า การเพิ่ม id เป็นตัวตัดสินเมื่อค่าเท่ากันจะทำให้การจัดเรียงสมบูรณ์และการแบ่งหน้าแม่นยำ ด้วยดัชนีรวมบน (created_at, id) ฐานข้อมูลจะค้นหาตรงไปยังขอบเขตและอ่าน 25 รายการ หน้าที่ 1 และหน้าที่ 60,000 มีค่าใช้จ่ายเท่ากัน
อย่างไรก็ตาม API ไม่ควรเปิดเผยค่าดิบเหล่านั้น การใช้งานจริงจะเข้ารหัสคีย์การจัดเรียงเป็นโทเค็นที่ไม่โปร่งใส ซึ่งมักจะเป็น base64:
GET /v1/orders?limit=25&cursor=eyJjcmVhdGVkX2F0IjoiMjAyNi0wOC0zMFQxNDoyMjowN1oiLCJpZCI6Im9yZF84ODIxIn0
ความทึบแสงเป็นเรื่องของการตัดสินใจออกแบบ ไม่ใช่การทำให้เข้าใจยากเพื่อตัวมันเอง ไคลเอนต์ที่ไม่สามารถแยกวิเคราะห์ cursor จะไม่สามารถสร้าง URL ได้ด้วยตนเอง ซึ่งทำให้คุณมีอิสระในการเปลี่ยนคีย์การจัดเรียง เพิ่มคำแนะนำในการแบ่งส่วน หรือเปลี่ยนกลไกการจัดเก็บข้อมูลได้โดยไม่ทำให้ระบบของใครพัง ข้อตกลงจึงกลายเป็น “ส่งคืนสิ่งที่เราให้คุณไป” ไม่มีอะไรมากไปกว่านั้น
ข้อแลกเปลี่ยนคือ: ไม่มีหน้า 47 Cursor รู้แค่ “หลังจากแถวนี้” เท่านั้น ดังนั้นไคลเอนต์จะเดินหน้าไป (และถอยหลังได้ หากคุณมี previous-cursor) ทีละหน้าเท่านั้น จำนวนรวมทั้งหมดก็ไม่ได้มาฟรีเช่นกัน; การนับเป็นคิวรีแยกต่างหาก สำหรับการออกแบบที่ชุดข้อมูลมีขนาดใหญ่มาก คู่มือการออกแบบการแบ่งหน้า API สำหรับข้อมูลหลายล้านรายการ ของเราครอบคลุมด้านการขยายขนาดได้ลึกซึ้งยิ่งขึ้น
ข้อดีข้อเสียโดยสรุป
| มิติ | การแบ่งหน้าแบบ Offset | การแบ่งหน้าแบบ Cursor-based |
|---|---|---|
| ข้ามไปยังหน้าใดก็ได้ | ได้, หมายเลขหน้าใดก็ได้ | ไม่ได้, ต้องเดินหน้าไปทีละหน้าเท่านั้น |
| จำนวนรวม / จำนวนหน้า | ง่ายต่อการรวม | ต้องคิวรีเพื่อนับจำนวนแยกต่างหาก |
| ประสิทธิภาพในการเรียกหน้าลึก | O(n), ประสิทธิภาพลดลงตามความลึก | O(1) ต่อหน้าในทุกระดับความลึก |
| ความเสถียรภายใต้การเขียนข้อมูล | เลื่อนไปมา: ข้อมูลซ้ำและช่องว่าง | เสถียร, ยึดติดกับแถว |
| ค่าใช้จ่ายในการพัฒนา | ง่ายมาก | ปานกลาง: การเข้ารหัส, ตัวตัดสินเมื่อค่าเท่ากัน, การออกแบบดัชนี |
| ข้อกำหนดในการจัดเรียง | ORDER BY ใดก็ได้ | ต้องใช้คีย์การจัดเรียงที่ไม่ซ้ำกันและมีดัชนี |
| การแคช URL หน้า | ง่าย, URL สามารถคาดเดาได้ | ยากกว่า, cursors เปลี่ยนแปลงไปตามการเรียกแต่ละครั้ง |
| ความซับซ้อนของไคลเอนต์ | ต่ำ | ต่ำ, หากโครงสร้างตอบกลับชัดเจน |
มีจุดที่ละเอียดอ่อนในตารางนั้นที่ควรเน้นย้ำ: การแบ่งหน้าแบบ Cursor ต้องการการจัดเรียงที่กำหนดได้ หากปลายทางของคุณอนุญาตให้ไคลเอนต์จัดเรียงตามคอลัมน์ที่เปลี่ยนแปลงได้และไม่ซ้ำกัน เช่น status ตรรกะของ Keyset จะกลายเป็นเรื่องที่ยุ่งยากอย่างรวดเร็ว Offset ทนทานต่อการจัดเรียงที่หละหลวม แต่ Cursor จะลงโทษมัน
คุณควรเลือกแบบไหน?
จับคู่รูปแบบให้เข้ากับวิธีการใช้ข้อมูล
ตารางผู้ดูแลระบบและแดชบอร์ด: Offset เครื่องมือภายในที่มีข้อมูลไม่กี่พันแถว ผู้ใช้คลิกหมายเลขหน้า และมีจำนวน “1,848 ผลลัพธ์” ที่มองเห็นได้ การเลื่อนของหน้าไม่สำคัญ ความลึกยังคงตื้น และการข้ามไปยังหน้าเป็นคุณสมบัติที่มีประโยชน์ Offset ได้เปรียบในเรื่องค่าใช้จ่ายในการพัฒนา
ฟีดแบบเลื่อนไม่สิ้นสุด (Infinite scroll feeds): Cursor ไม่มีใครข้ามไปยังหน้า 47 ของฟีด ผู้ใช้มักจะโหลด “เพิ่มเติม” เท่านั้น การเขียนข้อมูลเกิดขึ้นตลอดเวลา และข้อมูลซ้ำซ้อนสามารถมองเห็นได้และน่าอับอาย นี่คือกรณีตัวอย่างของการใช้ Cursor
Public API: Cursor คุณไม่สามารถควบคุมผู้ใช้งานของคุณได้ ใครบางคนจะเขียนลูปเพื่อเรียกดูทุกหน้า และด้วย Offset หน้าที่ลึกๆ จะกลายเป็นปัญหาของคุณตอนตี 3 Cursor ช่วยให้ทุกหน้ามีค่าใช้จ่ายต่ำ และช่วยให้คุณสามารถพัฒนาโครงสร้างภายในเบื้องหลังโทเค็นที่ไม่โปร่งใสได้ คู่มือการแบ่งหน้า REST API ของเราครอบคลุมรายละเอียดเกี่ยวกับ URL และข้อกำหนดในส่วนหัว
การส่งออกและงานซิงค์: Cursor งานแบตช์ที่ดึงคำสั่งซื้อ 2 ล้านรายการทั้งหมดต้องการการรับประกันสองประการ: ไม่มีแถวใดหายไปแม้จะมีการเขียนพร้อมกัน และมีค่าใช้จ่ายคงที่ต่อหน้า Offset ไม่มีคุณสมบัติทั้งสองประการนี้ Cursor ยังให้จุดดำเนินการต่อฟรีเมื่องานหยุดทำงานที่แถว 1.4 ล้าน
กฎง่ายๆ ที่ซื่อสัตย์: Offset สำหรับอินเทอร์เฟซขนาดเล็ก, ที่มนุษย์เรียกดู, และเน้นการนับจำนวน; Cursor สำหรับสิ่งใดก็ตามที่มีขนาดใหญ่, มีการเปลี่ยนแปลงตลอดเวลา, หรือเป็นสาธารณะ
API จริงจัดการกับเรื่องนี้อย่างไร
Stripe ใช้การแบ่งหน้าแบบ Cursor ทั้งหมด ปลายทางรายการทุกรายการยอมรับ starting_after (รหัสออบเจกต์) และ limit และการตอบกลับจะรวม has_more ในการดึงหน้าถัดไปของรายการค่าใช้จ่าย คุณจะส่งรหัสของค่าใช้จ่ายล่าสุดที่คุณได้รับ เอกสารการแบ่งหน้าของ Stripe แสดงรูปแบบนี้; โปรดทราบว่าไม่มีการนับจำนวนรวมที่ใดเลย ซึ่งเป็นการละเว้นโดยเจตนาเมื่อพิจารณาจากปริมาณการเขียนข้อมูลของพวกเขา
REST API ของ GitHub ยังคงเปิดเผย page และ per_page บนปลายทางส่วนใหญ่ พร้อมด้วยส่วนหัว Link ที่ชี้ไปยังหน้าถัดไปและหน้าสุดท้าย แต่ควรอ่าน เอกสารการแบ่งหน้าของ GitHub อย่างละเอียด: พวกเขาสั่งให้ไคลเอนต์ทำตามส่วนหัว Link อย่างเคร่งครัด แทนที่จะสร้าง URL ของหน้าเอง และปลายทางใหม่ๆ ก็ได้เปลี่ยนไปใช้ Cursor แล้ว เนื่องจากปัญหาการเรียกหน้าลึกๆ ด้วย Offset ในคลังเก็บข้อมูลขนาดใหญ่มันสร้างภาระอย่างมาก
Slack ได้ย้าย Web API ของตนไปใช้การแบ่งหน้าแบบ Cursor และตอนนี้ระบุว่าเป็นแนวทางที่เมธอดใหม่ๆ ทั้งหมดใช้ เมธอดเช่น conversations.history จะส่งคืน response_metadata.next_cursor และสตริง cursor ที่ว่างเปล่าหมายความว่าคุณมาถึงจุดสิ้นสุดแล้ว ดังที่อธิบายไว้ใน เอกสารการแบ่งหน้าของ Slack
API ที่มีการเข้าชมสูงสามแห่ง และทิศทางของการเดินทางก็เป็นไปในทางเดียวกัน: ไปสู่การใช้ Cursor
การออกแบบโครงสร้างการตอบกลับ
ความสำเร็จของ Cursor API ขึ้นอยู่กับโครงสร้างการตอบกลับของมัน ทำให้มันเรียบง่ายและคาดเดาได้:
{
"data": [
{
"id": "ord_8846",
"customer_id": "cus_2201",
"total_cents": 12900,
"created_at": "2026-08-30T16:01:44Z"
}
],
"has_more": true,
"next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wOC0zMFQxNjowMTo0NFoiLCJpZCI6Im9yZF84ODQ2In0"
}
สี่กฎที่ทำให้มันแข็งแกร่ง:
- ควรส่งคืน
has_moreเสมอ ไคลเอนต์ไม่ควรอนุมานว่าสิ้นสุดแล้วจากหน้าสั้นๆ; หน้าอาจสั้นลงกลางคันได้หากคุณทำการกรองหลังจากดึงข้อมูลมาแล้ว - ส่งคืน
next_cursor: nullในหน้าสุดท้าย และบันทึกไว้ในเอกสาร กฎการใช้สตริงว่างของ Slack ก็ใช้ได้เช่นกัน; เลือกหนึ่งอย่างและอย่าสลับกันใช้ - ปฏิเสธ cursors ที่ไม่ถูกต้องด้วยรหัส 400 ไม่ใช่ 200 ที่ว่างเปล่า Cursor ที่ผิดเพี้ยนเป็นข้อผิดพลาดของไคลเอนต์ และการซ่อนข้อผิดพลาดจะทำให้ต้องใช้เวลาแก้ไขหนึ่งวัน
- ลงชื่อหรือกำหนดเวอร์ชันของข้อมูล cursor หากมีการเข้ารหัสสิ่งใดๆ นอกเหนือจากคีย์การจัดเรียง คุณจะขอบคุณตัวเองในระหว่างการย้าย schema ครั้งต่อไป
การทดสอบทั้งสองรูปแบบใน Apidog
ข้อผิดพลาดในการแบ่งหน้าซ่อนอยู่ที่ขอบเขต: หน้าสุดท้าย, หน้าว่าง, cursor ที่แถวหลักถูกลบ การคลิกด้วยมือจะไม่สามารถตรวจจับได้ แต่สถานการณ์การทดสอบแบบลูกโซ่จะทำได้ และนี่คือจุดที่ Apidog เข้ามามีบทบาทในเวิร์กโฟลว์
สำหรับปลายทาง Cursor ให้สร้างสถานการณ์การทดสอบที่มีสองขั้นตอน:
- เรียกใช้ปลายทางและดึง cursor เพิ่ม post-processor ในคำขอแรกด้วย JSONPath
$.next_cursorและจัดเก็บไว้ในตัวแปรเช่นnextCursorApidog ช่วยให้คุณคัดลอก JSONPath ได้โดยตรงจากแผงการตอบกลับ; คำแนะนำแบบเต็มอยู่ใน วิธีการตั้งค่า assertions และดึงตัวแปรด้วย JSONPath - วนซ้ำคำขอหน้าถัดไป ห่อคำขอที่สองในขั้นตอน ForEach หรือ loop ส่ง
{{nextCursor}}เป็นพารามิเตอร์ cursor ดึง$.next_cursorซ้ำทุกรอบ และออกเมื่อhas_moreเป็น false ตรวจสอบทุกครั้งว่าไม่มีidซ้ำจากหน้าก่อนหน้า และขนาดหน้าไม่เกินlimit
สำหรับปลายทาง Offset โครงสร้างเดียวกันนี้ใช้ได้กับตัวแปรนับ: เพิ่ม page ตรวจสอบว่าความยาวของ data เท่ากับ per_page จนถึงหน้าสุดท้าย และตรวจสอบว่า total ยังคงสอดคล้องกันตลอดการเรียกดู
จากนั้นเพิ่มกรณีขอบ (edge cases) เป็นขั้นตอนของตัวเอง โดยแต่ละขั้นตอนมีการยืนยันที่ชัดเจน:
- หน้าว่าง: ส่งคำขอตัวกรองที่ตรงกับศูนย์แถว; ตรวจสอบว่า
dataเป็น[],has_moreเป็น false, และสถานะเป็น 200 - Cursor ไม่ถูกต้อง: ส่ง
cursor=not-a-real-cursor; ตรวจสอบสถานะ 400 และรหัสข้อผิดพลาดที่เครื่องอ่านได้ - แถวหลักที่ถูกลบ: สร้างคำสั่งซื้อ, ดึง cursor ที่ยึดกับมัน, ลบคำสั่งซื้อ, จากนั้นใช้ cursor; ตรวจสอบว่าการเรียกดูยังคงดำเนินต่อไปจากตำแหน่งที่ถูกต้องแทนที่จะเกิดข้อผิดพลาด การเปรียบเทียบ Keyset จัดการกับสิ่งนี้ได้อย่างเป็นธรรมชาติ และการทดสอบจะพิสูจน์ได้
เมื่อสถานการณ์ผ่านการทดสอบในเครื่องแล้ว ให้รันใน CI ทุกครั้งที่มีการผสานโค้ด ดาวน์โหลด Apidog ฟรี แล้วคุณจะสามารถมีสถานการณ์การเรียกดู cursor แบบเต็มรูปแบบ พร้อมลูปและการยืนยันรวมอยู่ด้วย ใช้งานได้ภายในเวลาไม่ถึงครึ่งชั่วโมง
คำถามที่พบบ่อย
การแบ่งหน้าแบบ Cursor ดีกว่าเสมอไปหรือไม่?
ไม่ Offset เหมาะสมกว่าเมื่อผู้ใช้ต้องการหมายเลขหน้า, ยอดรวม, และการเข้าถึงแบบสุ่มบนชุดข้อมูลขนาดปานกลาง ซึ่งเป็นลักษณะของเครื่องมือผู้ดูแลระบบภายในส่วนใหญ่ Cursor ดีกว่าเมื่อชุดข้อมูลมีขนาดใหญ่, มีการเขียนข้อมูลบ่อยครั้ง, หรือ API เป็นสาธารณะ จุดที่ล้มเหลวคือการใช้ Offset เป็นค่าเริ่มต้นสำหรับปลายทางรายการสาธารณะและค้นพบค่าใช้จ่าย O(n) หลังจากเปิดใช้งาน
ฉันจะนับจำนวนรวมทั้งหมดด้วยการแบ่งหน้าแบบ Cursor ได้อย่างไร?
รัน SELECT COUNT(*) แยกต่างหากด้วยตัวกรองเดียวกัน ไม่ว่าจะเป็นปลายทางที่แตกต่างกัน หรือพารามิเตอร์คิวรีแบบเลือกใช้ เช่น include_count=true แคชอย่างรุนแรง; การนับจำนวนโดยประมาณที่อัปเดตทุกนาทีก็เพียงพอสำหรับ UI เกือบทั้งหมด Stripe ข้ามการนับรวมทั้งหมดไปเลย ซึ่งบอกให้รู้ว่าบ่อยครั้งแค่ไหนที่ไคลเอนต์ต้องการมันจริงๆ
ฉันสามารถเสนอการแบ่งหน้าทั้งสองรูปแบบในปลายทางเดียวได้หรือไม่?
คุณทำได้ และ GitHub ก็ทำอย่างมีประสิทธิภาพในช่วงเปลี่ยนผ่าน แต่ควรหลีกเลี่ยงใน API ใหม่ๆ สองรูปแบบหมายถึงสองชุดของกรณีขอบ, สองเมทริกซ์การทดสอบ, และความสับสนของไคลเอนต์ว่าจะใช้อันไหนดี เลือกเพียงหนึ่งรูปแบบต่อปลายทาง หากคุณกำลังออกแบบข้อตกลงตั้งแต่เริ่มต้น รูปแบบใน คู่มือการแบ่งหน้า REST API ของเราจะช่วยให้การตั้งชื่อพารามิเตอร์มีความสอดคล้องกันทั่วทั้งอินเทอร์เฟซของคุณ
จะเกิดอะไรขึ้นหากแถวหลักของ Cursor ถูกลบไป?
ด้วย Keyset Pagination จะไม่มีอะไรพัง การเปรียบเทียบ WHERE (created_at, id) < (?, ?) ไม่จำเป็นต้องให้แถวหลักมีอยู่จริง มันจะค้นหาไปยังตำแหน่งขอบเขตและดำเนินต่อไป นี่เป็นข้อได้เปรียบที่แท้จริงเหนือการออกแบบ “cursor เป็นการค้นหาแถว” และเป็นกรณีขอบที่คุ้มค่าแก่การยืนยันในสถานการณ์ทดสอบ Apidog ของคุณ ก่อนที่ผู้ใช้งานจะพบมันเอง
