aedev.net
  • Trang chủ
  • AI
  • Mobile
  • Flutter
  • React Native
  • Backend
  • DevOps
  • Hướng dẫn
No Result
View All Result
SAVED POSTS
aedev.net
  • Trang chủ
  • AI
  • Mobile
  • Flutter
  • React Native
  • Backend
  • DevOps
  • Hướng dẫn
No Result
View All Result
aedev.net
No Result
View All Result
Minh họa kiến trúc backend API gọn gàng cho sản phẩm nhỏ

Phân trang keyset trong PostgreSQL bằng created_at và id

Suong Nguyen by Suong Nguyen
21/10/2026
in Backend
0
585
SHARES
3.2k
VIEWS
Summarize with ChatGPTShare to Facebook

Một màn hình nhật ký chỉ cần nút “Tải thêm”, nhưng truy vấn lại dùng OFFSET ngày càng lớn. Đây là lúc bạn nên xem xét keyset pagination: lấy dữ liệu tiếp sau một mốc đã biết thay vì bỏ qua một số lượng dòng.

Bài này dùng cú pháp PostgreSQL 18, đối chiếu ngày 21/10/2026. Các ví dụ là truy vấn minh họa cho bảng bài viết; bạn cần chạy trên dữ liệu thử của mình và xem query plan trước khi dùng trong production.

1. Khi nào OFFSET vẫn hợp lý?

LIMIT 20 OFFSET 100 dễ hiểu và phù hợp khi sản phẩm thực sự cần nhảy đến một số trang. Tuy nhiên, PostgreSQL vẫn phải xử lý những dòng bị bỏ qua. Tài liệu cũng yêu cầu thứ tự đủ xác định khi dùng LIMIT, nếu bạn muốn các lần lấy dữ liệu nhất quán. Xem LIMIT và OFFSET.

Với danh sách cuộn liên tục, người dùng thường cần “20 bản ghi tiếp theo”, không cần “trang 742”. Keyset khớp với nhu cầu đó. Đổi lại, bạn không còn tính được vị trí bất kỳ chỉ bằng một số trang.

2. Chuẩn bị thứ tự có khóa phân biệt

Giả sử dữ liệu mới nhất đứng đầu. Chỉ sắp theo created_at là chưa đủ: nhiều bài có thể có cùng thời điểm tạo. Thêm id làm khóa phân biệt và giữ hai cột không null.

CREATE TABLE articles (
  id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  title text NOT NULL,
  created_at timestamptz NOT NULL DEFAULT now()
);

CREATE INDEX articles_created_id_idx
  ON articles (created_at DESC, id DESC);

Đây là schema cho ví dụ mới. Với bảng đang phục vụ người dùng, hãy lập kế hoạch migration và cách tạo index phù hợp tải ghi. Không copy lệnh thay đổi cấu trúc vào database thật mà chưa đánh giá ảnh hưởng.

3. Lấy trang đầu và trang tiếp theo

Trang đầu lấy 21 dòng nếu kích thước hiển thị là 20. Dòng thứ 21 giúp xác định có thể còn trang tiếp theo, nhưng không đưa nó vào danh sách trả cho UI.

SELECT id, title, created_at
FROM articles
ORDER BY created_at DESC, id DESC
LIMIT 21;

Sau khi hiển thị 20 dòng, lấy created_at và id của dòng thứ 20 làm cursor. Ví dụ mốc là thời điểm dưới đây và ID 120:

SELECT id, title, created_at
FROM articles
WHERE (created_at, id) <
      (TIMESTAMPTZ '2026-10-21 10:00:00+07', 120::bigint)
ORDER BY created_at DESC, id DESC
LIMIT 21;

PostgreSQL so sánh cặp giá trị theo thứ tự từ trái sang phải. Khi thời điểm bằng nhau, id quyết định thứ tự. Điều này dựa trên quy tắc so sánh row constructor.

Trong code ứng dụng, truyền cursor dưới dạng tham số của driver database. Chuỗi timestamp và số trong ví dụ chỉ nhằm làm rõ truy vấn, không phải hướng dẫn ghép SQL từ input người dùng.

4. Giữ cursor và bộ lọc đi cùng nhau

Một cursor đề xuất có thể chứa:

{
  "createdAt": "2026-10-21T03:00:00.000000Z",
  "id": "120",
  "sort": "created_desc",
  "filterVersion": 1
}

Lưu bigint bằng chuỗi nếu lớp ứng dụng không biểu diễn chính xác mọi số nguyên 64 bit. Giữ đủ độ chính xác của timestamp do database trả về; làm tròn xuống mili giây có thể thay đổi ranh giới khi cột có phần microsecond.

Mã hóa cursor thành Base64 chỉ giúp truyền chuỗi thuận tiện. Nó không chứng minh dữ liệu hợp lệ và cũng không thay thế kiểm tra quyền. Nếu người dùng đổi bộ lọc, chẳng hạn từ tất cả bài sang một tác giả, hãy bắt đầu lại từ trang đầu hoặc dùng cursor được ràng buộc đúng bộ lọc.

5. Kiểm tra trước khi kết luận nhanh hơn

Tài liệu index nhiều cột giải thích vai trò của thứ tự cột đối với B-tree. Nếu truy vấn thực tế có tenant_id, category hoặc trạng thái, index minh họa ở trên chưa chắc là lựa chọn tốt nhất.

Hãy đo bằng EXPLAIN (ANALYZE, BUFFERS) trên môi trường thử. So sánh số dòng đã đọc, thời gian và buffer với cùng tập dữ liệu. Không chỉ nhìn tên “Index Scan”: bảng nhỏ có thể được planner xử lý hiệu quả bằng cách khác.

Một bài kiểm tra chức năng hữu ích là tạo nhiều dòng có cùng created_at, đi hết danh sách rồi kiểm tra không trùng ID và không bỏ sót dữ liệu khi tập dữ liệu giữ nguyên. Sau đó thử chèn bài mới giữa hai lần tải. Bài mới thường nằm trước cursor nên sẽ xuất hiện khi người dùng làm mới đầu danh sách.

Giới hạn cần hiểu

  • Nếu sửa cột đang dùng để sắp xếp, một dòng có thể di chuyển qua ranh giới cursor. Keyset không tự cung cấp snapshot của toàn bộ phiên duyệt.
  • Khi dữ liệu bị xóa hoặc bộ lọc thay đổi, tập kết quả cũng thay đổi. Hãy mô tả hành vi đó trong yêu cầu sản phẩm.
  • Keyset phù hợp cho “tải thêm”; nhu cầu báo cáo, đếm tổng hoặc nhảy trang có thể cần thiết kế khác.

Điểm bắt đầu tốt là một thứ tự ổn định, cursor giữ đúng độ chính xác và phép đo trên truy vấn thật. Đừng đổi toàn bộ màn hình sang keyset chỉ vì tên kỹ thuật nghe có vẻ tối ưu hơn.

Nguồn tham khảo

  • PostgreSQL 18: LIMIT và OFFSET.
  • PostgreSQL 18: Row and Array Comparisons.
  • PostgreSQL 18: Multicolumn Indexes.
Tags: DatabaseHiệu năngPostgreSQL
SummarizeShare234
Suong Nguyen

Suong Nguyen

Related Stories

Minh họa kiến trúc backend API gọn gàng cho sản phẩm nhỏ

Idempotency-Key cho API tạo đơn: retry mà không tạo trùng nghiệp vụ

by Suong Nguyen
22/10/2026
0

Thiết kế phạm vi khóa, transaction và response lưu lại để API tạo đơn xử lý an toàn khi client gửi lại request.

Minh họa kiến trúc backend API gọn gàng cho sản phẩm nhỏ

Thiết kế backend API gọn gàng cho sản phẩm nhỏ

by admin
02/07/2026
0

Một backend API tốt cho sản phẩm nhỏ không cần quá phức tạp, nhưng phải rõ resource, validate kỹ, trả lỗi nhất quán và dễ mở rộng.

Minh họa AI hỗ trợ lập trình viên xây dựng sản phẩm phần mềm

AI đang thay đổi cách lập trình viên xây dựng sản phẩm

by admin
02/07/2026
0

AI không thay thế lập trình viên ngay lập tức, nhưng đang thay đổi cách chúng ta phân tích yêu cầu, viết code, review và vận hành sản...

Next Post
Minh họa môi trường phát triển React Native với laptop, Android emulator và iOS simulator

Expo Go và development build: khi nào dự án nên chuyển?

0 0 votes
Article Rating
Subscribe
Login
Notify of
guest
guest
0 Comments
Oldest
Newest Most Voted
AE Dev

AE Dev mang đến các bài viết về lập trình, mobile app, backend, frontend, WordPress và công nghệ, phù hợp cho developer muốn học hỏi và cập nhật kiến thức thực tế.

Recent Posts

  • React Native: tránh bàn phím che form với KeyboardAvoidingView và ScrollView
  • Phỏng vấn JavaScript: giải thích event loop, microtask và async/await
  • Deep link mobile an toàn: đúng domain chưa đủ, còn route và quyền đọc

Categories

  • AI
  • Backend
  • Đánh giá
  • DevOps
  • Flutter
  • Hướng dẫn
  • Lập trình Mobile
  • Phỏng vấn
  • React Native
  • Uncategorized

Weekly Newsletter

  • Buy JNews
  • Support Forum
  • Pre-sale Question
  • Contact Us

© 2026 AE Dev - Blog công nghệ, lập trình và chia sẻ kiến thức IT.

Welcome Back!

Login to your account below

Forgotten Password?

Retrieve your password

Please enter your username or email address to reset your password.

Log In
No Result
View All Result
  • Trang chủ
  • AI
  • Mobile
  • React Native
  • Backend
  • DevOps
  • Hướng dẫn
  • Flutter

© 2026 AE Dev - Blog công nghệ, lập trình và chia sẻ kiến thức IT.

wpDiscuz
0
0
Would love your thoughts, please comment.x
()
x
| Reply