Giao diện
LIMIT & OFFSET: Phân trang
Khi dữ liệu quá lớn (hàng triệu dòng), không ai tải hết về một lúc. Chúng ta chia nhỏ ra từng trang (pagination) để hiển thị.
Cú pháp (Syntax)
sql
SELECT columns
FROM table_name
LIMIT number_of_records -- Lấy bao nhiêu dòng
OFFSET start_position; -- Bỏ qua bao nhiêu dòng đầuVí dụ thực tế (Real-world Example)
Bạn đang làm API phân trang cho danh sách User, mỗi trang 10 người.
Trang 1 (Lấy 10 người đầu tiên):
sql
SELECT id, username FROM users
ORDER BY id
LIMIT 10 OFFSET 0;Trang 2 (Bỏ qua 10 người đầu, lấy 10 người tiếp theo):
sql
SELECT id, username FROM users
ORDER BY id
LIMIT 10 OFFSET 10;Công thức tổng quát:OFFSET = (page_number - 1) * page_size
💡 HPN Pro Tip: Cái chết của OFFSET (Offset Doom)
Đây là điều phân biệt Junior và Senior.
Khi bạn query LIMIT 10 OFFSET 1000000 (Trang thứ 100,000), database KHÔNG nhảy thẳng tới dòng thứ 1 triệu. Nó thực sự phải đọc và đếm 1,000,010 dòng, sau đó vứt bỏ 1,000,000 dòng đầu và chỉ trả về 10 dòng cuối. Càng về trang sau, API càng chậm (O(N)).
Giải pháp Advanced: Keyset Pagination (Seeking) Thay vì dùng OFFSET, hãy dùng con trỏ (thường là id hoặc created_at).
Ví dụ: Lấy trang tiếp theo của trang có User ID cuối cùng là 50.
sql
-- "Hãy tìm cho tôi 10 người có ID lớn hơn 50"
SELECT id, username FROM users
WHERE id > 50
ORDER BY id ASC
LIMIT 10;Cách này dùng được Index của id, tốc độ cực nhanh (O(logN)) bất chấp dữ liệu lớn cỡ nào.
⚠️ Common Mistake
Quên ORDER BY khi dùng LIMIT. Nếu không có ORDER BY, LIMIT sẽ trả về các dòng ngẫu nhiên. Khi người dùng F5 hoặc qua trang khác, dữ liệu có thể nhảy lung tung hoặc trùng lặp.
Luôn luôn ORDER BY một cột Unique (như ID) khi phân trang!
🧠 Quiz
Câu 1: Tại sao OFFSET lớn gây chậm?
- [ ] A) Database phải tạo bảng tạm
- [x] B) Database vẫn phải đọc và bỏ qua tất cả dòng trước OFFSET, gây I/O lãng phí
- [ ] C) OFFSET bị giới hạn tối đa 10000
- [ ] D) OFFSET không hoạt động với index
💡 Giải thích:
OFFSET 100000nghĩa là database phải scan 100000 dòng rồi bỏ đi, chỉ lấy vài dòng tiếp. Với trang 10000 trở đi, hiệu suất rất tệ — đây là lý do keyset pagination ra đời.
Câu 2: Keyset pagination (cursor-based) hoạt động thế nào?
- [ ] A) Dùng OFFSET nhưng cache kết quả
- [ ] B) Dùng LIMIT với số ngẫu nhiên
- [x] C) Dùng WHERE để lọc từ vị trí cuối cùng đã thấy, thay vì OFFSET
- [ ] D) Dùng subquery để đếm tổng dòng
💡 Giải thích: Thay vì
OFFSET 1000, keyset pagination dùngWHERE id > last_seen_id ORDER BY id LIMIT 20. Database nhảy thẳng tới vị trí cần lấy nhờ index, hiệu suất O(log n) thay vì O(n).
Câu 3: Điều gì bắt buộc khi dùng LIMIT để phân trang?
- [ ] A) Phải có GROUP BY
- [ ] B) Phải có HAVING
- [x] C) Phải có ORDER BY với cột unique để đảm bảo thứ tự nhất quán
- [ ] D) Phải có DISTINCT
💡 Giải thích: Không có ORDER BY, thứ tự dòng trả về là không xác định. Người dùng refresh trang có thể thấy dữ liệu nhảy lung tung hoặc bị trùng lặp giữa các trang.