LIMIT and OFFSET allow you to retrieve just a portion of the rows that are generated by the rest of the query:
즉 Limit와 Offset 을 이용하여 쿼리에 의해 검색되어진 일부분을 보여줌으로 페이징 처리가 가능하다.
LIMIT는 원하는 레코드 수를 쿼리에 알려준다.
LIMIT를 사용할 때 결과 행을 고유한 순서로 제한하는 ORDER BY 절을 사용하는 것이 중요하다. 그렇지 않으면 쿼리 행의 예측할 수 없는 하위 집합이 생성된다.
OFFSET은 쿼리를 시작할 위치를 알려준다.
OFFSET으로 처음 5개 레코드를 가져오는 쿼리 이다. 처음부터 쿼리를 시작하도록 명시적으로 지시하고 있다.
select * from public.customer
order by customer_id
limit 5
offset 5;
즉 5번째부터 5개를 가져와야 함으로 6,7,8,9,10 데이터를 가져오는 것이다.
결과적으로 아래와 같은 쿼리가 작성된다.
select *
from [테이블명]
order by [정렬 기준이 되는 컬럼]
limit #{take}
offset (#{page} -1)* #{take} // 실제 페이지는 1이 아닌 0부터 시작하기 때문에 '-1'을 해준다.
페이지번호와 페이지크기만 알면 어떤 페이지든 쉽게 접근 할 수 있어, 사용자가 대량의데이터 중 특정 부분으로 바로 접근하고 할때 유용하다.
-> 유저가 1페이지 상품을 다 둘러보고 2페이지를 눌렀을때 1페이지에서 보았던 상품20개중 마지막 5개를 다시 2페이지에서 만나게 된다. (등록일 기준 내림차순 이므로
반대로 5개 상품을 삭제 했다면 2페이지 넘어갔을때 고객은 5개의 상품을 못하게 된다.
)
중복되는 값이 여러페이지에 걸쳐 , 같은 값이 여러페이지에서 나타난다.
1개의 페이지에 10개의 게시물이 출력된다고 가정하면, 첫 번째 페이지의 sql 은 다음과 같다.
select
graha_mail_id,
subject,
sent_date
from webmua.graha_mail
order by sent_date desc
limit 10 offset 0
2 번째 페이지는 offset 부분만 변경된다.
limit 10 offset 10
order by 절에 Primary 컬럼인 graha_mail_id 를 추가한다.
그럼에도 사람들은 왜 keyset pagination보다 offset을 더 선호하는 걸까? 더 사용하고 있는 걸까?
https://vladmihalcea.com/sql-seek-keyset-pagination/ 이 글에서
원작자는 그 이유가 lack of tool support. 즉, 지원되는 도구가 부족하기 때문이라고 말하고 있다. 대부분의 데이터베이스 도구/프레임워크들은 offset을 활용한 pagination을 제공하고, keyset pagination방식을 위한 방법은 제공하지 않고 있다. 그렇기에 이러한 keyset pagination의 사용이 퍼지기 위해서는 더 다양한 도구/프레임워크에서 지원해줘야 한다.
하나의 예를 들어보자면 Spring Data JPA가 있다. Spring Data JPA에서는 Pageable, Page 객체를 통해 쉽게 pagination 구현을 할 수 있도록 지원하고 있다. Database마다 다를 수 있지만 H2 DB를 사용해 테스트해보면, offset을 사용해 pagination 처리를 하고 있는 것을 확인할 수 있다.
select
graha_mail_id,
subject,
sent_date
from webmua.graha_mail
order by sent_date desc, graha_mail_id desc
limit 10 offset 0
order by [고유값 id ]
OFFSET Pagination과 달리 데이터베이스가 전체 데이터를 스캔하는 일이 없어지므로 큰 데이터셋에서 성능이 향상될 수 있습니다.
OFFSET Pagination은 데이터가 추가되거나 삭제될 때 페이지 경계가 이동할 수 있지만, Keyset Pagination은 이러한 문제를 해결할 수 있습니다.
OFFSET Pagination은 데이터가 변경될 때마다 페이지 내용이 변경될 수 있지만, Keyset Pagination은 페이지 내용이 일관된 상태를 유지합니다.
(추후 수정 가능 , 이부분 내생각엔 일관되지 않는다 . 정렬에 따라 )
근데 keyset pagination 방식도 단점이 존재한다.
OFFSET Pagination과 달리 데이터베이스가 전체 데이터를 스캔하는 일이 없어지므로 큰 데이터셋에서 성능이 향상될 수 있습니다.
바로 임의의 특정 페이지로 바로 이동이 불가능하다는 것이다. 위 쿼리를 봐서 알겠지만 최근에 조회한 row 이후의 데이터들만 가져오도록 하고 있지, 특정 페이지 번호를 통해 조회하고 있지 않다. 그러므로 비즈니스 로직 상 특정 페이지의 데이터를 조회하는 것이 필요하다 했을 때, keyset pagination는 적합하지 않다. 무한 스크롤 방식이라면 문제가 되지 않는다.
) A
<choose>
<when test="take != '' and take != null">
<choose>
<when test="skip != '' and skip != null">
WHERE T.SN <![CDATA[>]]> #{skip}
</when>
<when test="page != '' and page != null">
OFFSET ((#{page} - 1) * #{take})
</when>
<otherwise>
WHERE T.SN <![CDATA[>]]> 0
</otherwise>
</choose>
LIMIT #{take}
</when>
<otherwise>
LIMIT 10000
</otherwise>
</choose>
) A
<choose>
<when test="take != '' and take != null">
<choose>
<when test="page != '' and page != null">
OFFSET ((#{page} - 1) * #{take})
</when>
<when test="skip != '' and skip != null">
WHERE T.SN <![CDATA[>]]> #{skip}
</when>
<otherwise>
WHERE T.SN <![CDATA[>]]> 0
</otherwise>
</choose>
LIMIT #{take}
</when>
<otherwise>
LIMIT 10000
</otherwise>
</choose>
(문제가 되는 경우로 등록일 기준 내림차순 시)
(문제가 되지 않는 경우로 등록일 기준 내림차순 이면서 , 페이지마다 순번을 오름차순일 경우- 추후 알게 된 점이지만 보통 이렇게 처리한다고 한다. )
다만 offset pagination의 경우
offset은 데이터베이스에서 쿼리의 처음 N개 결과를 건너뛰도록 설정되어 있다. 하지만 데이터베이스 N개 결과 이후의 row만 받아오는 것이
모든 row 를 디스크로 부터 읽어오고, N개의 결과까지 순서대로 넘기는 식
즉, offset을 사용하는것은 많은 행을 읽어들여오고, 이후에 삭제하는 과정을 거쳐 큰 offset값은 데이터베이스에 많은 부하를 주게 된다.
그러므로 게시판, 무한 스크롤등 paging이 필요한 곳에서는 정렬된 데이터를 필요로 하므로 order by [ 고유값 컬럼 ]을 사용하는 것이 좋다고 생각된다.
등록일 기준 내림차순 정렬하면 보통 위해서 부터 아래로 10 ->1 으로 표시한다.
이런 경우 실사간으로 데이터가 추가되면 중복되는 건 당연하지만
그렇게 되면 순번이 큰 의미가 없어진다.
[출처]네이버 블로그 글 목록
보통 등록일 기준 내림차순 정렬하면 보통 위해서 부터 아래로 1->10 으로 표시한다.
(요구사항에 의해 반대인 경우도 있음)
이 경우엔 중복조회에 대해 갱신된 데이터가 보이가 되므로 문제가 되지 않는다.
참고자료
Postgresql 의 limit, offset 으로 페이징 처리할 때 주의할 점
https://binux.tistory.com/148