Phân trang là một trong những yêu cầu cơ bản đối với API lấy dữ liệu là danh sách. Phân trang nhằm giảm tải lượng dữ liệu cần truy vấn cũng như giảm tải lượng dữ liệu truyền về, vì việc lấy hết dữ liệu trong một danh sách rất dài là một điều lãng phí đối với hầu hết tính năng thông thường.
Bài viết ngày hôm nay tôi xin phép trình bày hai kĩ thuật phân trang phổ biến mà dễ dàng triển khai nhất. Mỗi loại có ưu nhược điểm thế nào và nên áp dụng trong trường hợp nào thì mời bạn đọc tiếp bài viết dưới đây nhé.
/articles?limit=10&offset=0
Chắc hẳn url trên khá quen thuộc với mọi người, đây là kĩ thuật phân trang dựa trên limit
và offset
. Nguyên tắc hoạt động của nó khá đơn giản: limit
là giới hạn bản ghi còn offset
là bắt đầu lấy dữ liệu sau offset
hàng.
Ví dụ trên sẽ lấy 10 hàng bắt đầu từ hàng đầu tiên.
Thể hiện của kĩ thuật phân trang này là một tính năng trông giống như hình bên dưới.
Các trang như 1,2,3... đến 30 được hiển thị, người dùng có thể dễ dàng bấm vào trang muốn xem để xem nội dung trong trang đó.
Về phần xử lý phía máy chủ, hầu hết việc lấy dữ liệu ra theo limit
và offset
là từ trong cơ sở dữ liệu. Hiện tại hầu như cơ sở dữ liệu nào cũng hỗ trợ cú pháp truy vấn limit
và offset
. Ví dụ như trong PostgreSQL:
SELECT * FROM articles LIMIT 10 OFFSET 0;
Với mỗi limit
và offset
người dùng gọi lên thay thế vào câu truy vấn bạn sẽ lấy được kết quả mong muốn.
Trong quá trình người dùng đang lấy dữ liệu liên tục, nếu như thời điểm đó có bản ghi được thêm vào hoặc bị xóa sẽ gây ra hiện tượng trùng lặp hoặc thiếu sót dữ liệu ở trang tiếp theo. Vì bản ghi được thêm sẽ đẩy dữ liệu tiếp theo xuống dưới, còn bản ghi bị xóa thì lại kéo dữ liệu lên.
Một điểm trừ nữa là khi số lượng bản ghi rất lớn, truy vấn theo offset
có khả năng bị chậm. Nguyên nhân là do cách cơ sở dữ liệu xử lý offset
. Hầu hết truy vấn theo offset
phải duyệt qua tất cả các hàng cho đến đủ số lượng offset
rồi mới bắt đầu lấy dữ liệu. Ví dụ bạn có offset
= 1.000.000 thì nó phải duyệt qua 1 triệu hàng rồi bắt đầu lấy từ hàng 1.000.001.
Tóm lại, offset
và limit
phù hợp trong trường hợp mong muốn triển khai nhanh, danh sách phân trang được hiển thị theo số thứ tự, khi đó người dùng nhanh chóng điều hướng đến trang họ muốn. Cần cân nhắc sử dụng kĩ thuật này trên tập dữ liệu lớn vì khi đó hiệu năng khả năng cao là bị ảnh hưởng.
Bạn đã gặp kiểu phân trang này bao giờ chưa? Chỉ bao gồm hai nút Next và Previous như hình dưới đây.
Khả năng cao là đang áp dụng kĩ thuật phân trang bằng cursor. Điều đặc biệt của phương pháp này là không có danh sách số thứ tự trang như kĩ thuật limit
, offset
bên trên.
Một url phân trang bằng cursor có thể trông giống như thế này:
/articles?cursor=4n5pxq24kp
Nguyên tắc hoạt động của cursor khá đơn giản. Ở lượt truy vấn lấy danh sách lần đầu tiên trả về một cursor
, sau đó bạn sử dụng cursor
này để lấy dữ liệu trong trang tiếp theo. Cứ như thế cho đến khi cursor
không trả về nữa tức là dữ liệu đã hết.
{
"articles": [...],
"next_cursor": "4n5pxq24kn",
"prev_cursor": "4n5pxq24kp",
}
Dễ thấy đối với kĩ thuật này bạn không thể nhảy đến một trang xác định nào, vì "con trỏ" chỉ được trả về sau mỗi lần gọi. Thông thường cursor
được mã hóa theo quy tắc mà chỉ máy chủ biết, khi truyền lên nó sẽ giải mã để lấy ra dữ liệu cần thiết bên trong.
Ở phía máy chủ, lệnh truy vấn không dùng LIMIT
và OFFSET
, thay vào đó là cách truy vấn "lớn hơn hoặc bằng" (>=) kết hợp với index dữ liệu.
Ví dụ, giả sử con trỏ 4n5pxq24kp
được máy chủ mã hóa từ id
= 10. Khi lấy trang tiếp theo câu lệnh truy vấn giống như là:
SELECT * FROM articles WHERE id > 10 LIMIT 10;
Dễ thấy, nếu đánh index
trên trường id
của bảng articles
thì câu lệnh không cần phải duyệt qua 10 bản ghi đầu tiên để bỏ qua nữa mà nó lấy luôn dữ liệu đằng sau 10. Độ phức tạp lúc này là O(1).
Phương pháp này còn khắc phục được trường hợp dữ liệu được thêm hoặc xóa trong lúc đang phân trang. Đơn giản vì nó không dựa vào offset
để lấy bản ghi tiếp theo mà nó dựa vào phép so sánh. Giả sử bản ghi thứ 10 bị xóa đi thì trang tiếp theo vẫn lấy được đủ 10 bản ghi kể từ bản ghi thứ 10 thay vì thứ 9 nếu áp dụng offset
.
Rõ ràng cách tiếp cận này về mặt hiệu suất cao hơn hẳn so với limit
và offset
, tuy nhiên thời gian triển khai và độ phức tạp có thể cao hơn so với phương pháp thông thường. Người dùng không thể điều hướng đến một trang mong muốn và yêu cầu bạn phải có một trường dữ liệu có thể sắp xếp đã được đánh index.
Trên đây, tôi vừa trình bày hai kĩ thuật phân trang phổ biến. limit
, offset
thì đơn giản dễ triển khai tuy nhiên gặp vấn đề về hiệu năng trên tập dữ liệu rất lớn. Cursor thì cho hiệu năng tốt nhưng lại có một vài hạn chế khi sử dụng cùng độ phức tạp cao hơn. Mỗi phương pháp đều thể hiện ra ưu nhược điểm, chúng ta không nhất thiết phải chọn cursor vì hiệu năng cao mà thay vào đó biết cách lựa chọn phương pháp cho phù hợp với bài toán mà bạn đang xử lý.
Xin chào, tôi tên là Hoài - một anh Dev kể chuyện bằng cách viết ✍️ và làm sản phẩm 🚀. Với nhiều năm kinh nghiệm lập trình, tôi đã đóng góp một phần công sức cho nhiều sản phẩm mang lại giá trị cho người dùng tại nơi đang làm việc, cũng như cho chính bản thân. Sở thích của tôi là đọc, viết, nghiên cứu... Tôi tạo ra trang Blog này với sứ mệnh mang đến những bài viết chất lượng cho độc giả của 2coffee.dev.Hãy theo dõi tôi qua các kênh LinkedIn, Facebook, Instagram, Telegram.
Bình luận (0)