
Cách làm ai cũng dùng
Phân trang thì dùng OFFSET với LIMIT. Ai cũng biết:
SELECT * FROM logs LIMIT 100 OFFSET 1000000
Gọn. Dễ hiểu. Chạy ngon lành lúc dev.
Lên production với vài triệu record? API đứng hình 30 giây.
Chuyện gì đang xảy ra?
Sự thật phũ phàng: OFFSET không nhảy cóc tới dòng 1 triệu được.
Nó scan từng dòng một từ 1 tới 1 triệu, rồi vứt hết. Database làm cả triệu thao tác chỉ để... bỏ đi.
Càng lật sâu, càng chậm. Trang 1 thì nhanh. Trang 10,000 thì chết.
Cách fix: Phân trang bằng cursor
Thay vì bảo database "bỏ qua bao nhiêu dòng", bảo nó "bắt đầu từ đâu":
-- Trang đầu (không tốn gì)
SELECT * FROM logs LIMIT 100
-- Các trang sau (nhanh)
SELECT * FROM logs WHERE id > 100 LIMIT 100
Với WHERE id > 100, database dùng index để nhảy thẳng tới dòng 101. Không scan. Không phí công.
Đây cũng là lý do DynamoDB bắt buộc truyền lastEvaluatedKey khi phân trang. Không phải hạn chế - mà là đảm bảo hiệu năng.
Đánh đổi
Cursor pagination có nhược điểm:
- Không nhảy được tới trang bất kỳ
- Không đếm được tổng số trang (trừ khi lưu thêm)
- UI chỉ có nút "Trước" và "Sau"
Với hệ thống dữ liệu lớn, đánh đổi này đáng. Với dữ liệu nhỏ, OFFSET vẫn ổn vì tiện.
Khi nào dùng gì?
Dùng OFFSET khi:
- Dữ liệu dưới 100K dòng
- User cần nhảy trang tự do
- Không quá quan trọng tốc độ
Dùng cursor khi:
- Dữ liệu trên 100K dòng
- Làm infinite scroll hoặc "load more"
- Cần API phản hồi nhanh