페이징 쿼리는 전체 데이터를 부분적으로 나누어 데이터를 조회하거나 처리할 때 사용된다.
데이터를 상대적으로 작은 단위로 나누어 처리하기 때문에 데이터베이스나 애플리케이션의 리소스 사용 효율이 증가,
로직 처리 시간을 단축 시킬 수 있다.
MySQL에서 페이징 쿼리는 일반적으로 LIMIT, OFFSET구문을 사용하여 작성한다.
select *
from subscribe
limit 500
offset 0;
LIMIT, OFFSET방식의 단점
- 뒤에 있는 데이터를 읽을 수록 점점 응답 시간이 길어질 수 있다.
- 왜? DBMS는 지정된 OFFSET수만큼 모든 레코드를 읽은 이후에 데이터를 가져오기 때문이다.
- 리소스 낭비: OFFSET 값이 커질수록 읽어야 하는 데이터 행(Row)이 많아지므로 CPU와 I/O 사용량이 급격히 증가합니다.
- 데이터 정합성 문제: 페이징 도중에 새로운 데이터가 삽입되거나 삭제되면, 사용자가 다음 페이지로 넘어갔을 때 중복된 데이터를 보거나 일부 데이터를 건너뛰게 되는 현상이 발생합니다.
해결 방안: No Offset (Cursor-based Pagination)
가장 대표적인 해결책은 마지막으로 본 데이터의 식별자(ID 등)를 기준점으로 삼는 것이다. 이를 '커서 기반 페이징'이라고 부른다.
방법: WHERE 조건문 활용
-- 기존 방식 (OFFSET)
SELECT * FROM orders ORDER BY id DESC LIMIT 10 OFFSET 10000;
-- 개선된 방식 (No Offset)
SELECT * FROM orders WHERE id < [마지막으로_확인한_ID] ORDER BY id DESC LIMIT 10;
왜 더 빠른가요?
- 인덱스 활용: WHERE 절에 인덱스가 걸린 컬럼(주로 PK나 생성일시)을 사용하면, DBMS는 인덱스를 타고 해당 위치로 직행합니다.
- 상수 시간 복잡도: 데이터가 1억 건이 있어도 내가 마지막으로 본 위치 다음부터 10개만 읽으면 되므로, 뒤쪽 페이지를 조회해도 응답 시간이 일정하게 유지됩니다.
또 다른 대안: 커버링 인덱스 (Covering Index)
만약 비즈니스 요구사항상 반드시 특정 페이지로 바로 이동하는 '번호판 UI'가 필요해서 OFFSET을 써야만 한다면, 커버링 인덱스를 활용해 성능을 최적화할 수 있습니다.
- 원리: 실제 데이터 레코드 전체를 읽기 전에, 인덱스에 포함된 정보(예: ID)만으로 먼저 필터링을 끝내는 방식입니다.
- 효과: 실제 데이터 블록에 접근하는 횟수를 획기적으로 줄여 OFFSET의 부하를 완화합니다.
SELECT * FROM orders JOIN (
SELECT id FROM orders ORDER BY id DESC LIMIT 10 OFFSET 10000
) as temp ON temp.id = orders.id;
요약
| 구분 | LIMIT OFFSET | No Offset (Cursor) |
| 속도 | 뒤로 갈수록 느려짐 | 항상 일정함 |
| 정합성 | 데이터 변화 시 누락/중복 발생 가능 | 비교적 안전함 |
| 구현 난이도 | 매우 쉬움 | 조금 복잡함 (UI 제약 발생) |
| 적합한 곳 | 데이터가 적은 서비스, 관리자 페이지 | 무한 스크롤(SNS), 대규모 서비스 |
'cs(with 매일메일)' 카테고리의 다른 글
| [260211수] PRG 패턴에 대해서 설명해 주세요. (0) | 2026.02.19 |
|---|---|
| [260210화] 멀티 쓰레딩에 대해서 설명해 주세요. (0) | 2026.02.10 |
| [260206금] 낙관적 락과 비관적 락에 대해 설명해주세요. (0) | 2026.02.06 |
| [260205목] 자바 프로그램이 실행되는 흐름을 설명해주세요. (0) | 2026.02.06 |
| [260204수] 가상화에 대해 설명해주세요. (0) | 2026.02.04 |