Nói không với OFFSET.

Nam Vu
3 min readSep 4, 2019

Với hầu hết các Cơ Sở Dữ Liệu Quan Hệ như MySQL, PostgreSQL, Oracle hay SQL Server đều có hỗ trợ từ khóa OFFSET để ra lệnh cho DB việc bỏ qua N dữ liệu trong kết quả query.

Ví dụ trong MySQL nếu ta muốn chỉ lấy 10 bản ghi và bắt đầu từ bản ghi thứ 15, ta sẽ viết câu lệnh như sau:

SELECT * FROM ... ORDER BY ID LIMIT 10 OFFSET 15;

Hay với Oracle 12c thì viết như sau:

SELECT * FROM ... ORDER BY ID OFFSET 15 ROWS FETCH NEXT 10 ROWS ONLY;

OFFSET thường được sử dụng để phục vụ trong việc phân trang hoặc để query toàn bộ các bản ghi trong Table một cách tuần tự và có giới hạn.

Theo lý thuyết thì OFFSET sẽ ra lệnh cho DB phải loại bỏ N kết quả đầu tiên NHƯNG thực tế thì DB vẫn phải đọc và sắp xếp lại toàn bộ bản ghi. Nếu trong trường hợp có quá nhiều dữ liệu dẫn tới việc tham số N của OFFSET sẽ rất lớn dẫn tới việc DB phải tìm hết toàn bộ các bản ghi phù hợp sắp xếp lại và loại bỏ N bản ghi điều này sẽ ảnh hưởng rất lớn performance của hệ thống.

Trong "thực chiến" thì người viết cũng đã gặp phải trường hợp này, chuyện là thế này:

"Để phục vụ cho việc analytics dữ liệu, team phải viết một con Job thực hiện hàng ngày việc upload toàn bộ dữ lên Data Lake, và cách làm mặc định và hiển nhiên của team làm lúc đấy là viết câu query sử dụng OFFSET để lấy toàn bộ dữ liệu ra và gửi lên Data Lake.
Kết quả là lúc chạy thử mọi việc có vẻ trơn tru tốc độ query nhanh như một cơn gió... nhưng khi dữ liệu càng về cuối tức là lúc tham số N của OFFSET lên càng cao thì tốc độ thực thi của câu query trở nên vô cùng thảm hại.
Lúc này team khá là hoang mang, không hiểu điều gì khiến cho việc càng về sau query càng chậm, sau một hồi hỏi anh Google mới phát hiện ra vấn đề ở chỗ OFFSET quá lớn khiến câu query trở nên rất chậm."

Vậy gặp trường hợp trên chúng ta sẽ phải làm gì ??? Tất nhiên là không sử dụng OFFSET nữa, vậy không sài OFFSET ta làm thế nào ? Hoặc hãy tưởng tượng rằng OFFSET không hề tồn tại thì chúng ta sống sao ???

Tất nhiên là không còn cách nào khác là phải dùng mệnh đề WHERE rồi, nhưng WHERE như thế nào và dựa vào đâu để lọc dữ liệu? Chắc chắn là dựa trên ID rồi, thường hệ thống sẽ generate ID theo thứ tự tăng dần, dựa vào đó ta sẽ có thể giới hạn kết quả trả về.

Kịch bản sẽ như sau:
- Câu lệnh đầu tiên sẽ lấy 10 bản ghi đầu tiên sắp xếp theo thứ tự tăng dần theo ID:

SELECT ...
FROM ...
WHERE ...
ORDER BY id DESC
FETCH FIRST 10 ROWS ONLY

- Sau đó ta sẽ lưu lại giá trị ID cuối cùng (theLastID) trong dữ liệu vừa query ra và tạo thêm một mệnh đề WHERE với giá trị ID > [theLastID]

SELECT ...
FROM ...
WHERE ...
AND ID > [theLastID]
ORDER BY id DESC
FETCH FIRST 10 ROWS ONLY

- Và cứ lặp lại như vậy cho đến "tận cùng thế giới" ta có thể duyệt toàn bộ các bản ghi với tốc độ rất cực nhanh và ổn định :D, vì sao vì trường ID luôn luôn đã được hệ thống Indexing.

Nhưng ta vẫn có một vấn đề chưa giải quyết được khi sử dụng giải pháp trên là những bảng dữ liệu không được generate ID theo thứ tự tăng dần hoặc ta muốn nhẩy tới 1 "trang" dữ liệu bất kỳ thì sao? Bạn đọc nào có cao kiến gì thì comment bên dưới share cho mọi người nhen.

Và cuối cùng nhớ cho người viết một 👏 động viên nhé :P

Nguồn tham khảo: https://use-the-index-luke.com/no-offset

--

--