일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
- nosql
- javascript
- Data structure
- 완전탐색
- HTML
- 운영체제
- Docker
- redis
- CSS
- DFS
- PYTHON
- Algorithm
- 알고리즘
- spring
- BFS
- 트랜잭션
- 프로그래머스
- CS
- OS
- 데이터베이스
- websocket
- db
- It
- java
- mysql
- jpa
- 자료구조
- 백준
- 트러블슈팅
- 영속성 컨텍스트
- Today
- Total
If at first you don't succeed, try again
[DB] 페이징 조회 쿼리 성능 개선 - MySQL 본문
* 개요
https://cyr0331.tistory.com/99
[DB] Index 실습(대용량 데이터 처리)
* 개요얼마 전 대용량 시스템 공부를 진행하면서, DB에 약 700만건의 테스트 데이터를 삽입하였다.그 후 페이징 처리를 하기 위해서 조회를 하였는데, 다음과 같은 결과가 나왔다.select * from article
cyr0331.tistory.com
지난 게시글을 통해 Index 실습을 진행하였다.
인덱스를 생성함으로써 약 700만개의 데이터를 조회하는 시간을 17초에서 0.02초로 줄일 수 있었다.
그런데 과연 인덱스를 사용함으로써 문제는 전부 다 해결되었을까?
* 뒷 페이지 조회
문제가 전부 해결됐는지 확인하기 위해 이번엔 50000페이지를 조회해보도록 하겠다.
select * from article where board_id = 1 order by article_id desc limit 30 offset 1499970;
뒷 페이지를 조회하니, 3.14초나 소요가 된다.
쿼리 플랜을 확인해보자.
explain select * from article where board_id = 1 order by article_id desc limit 30 offset 1499970;
이전에 생성했던 인덱스가 잘 적용된 것을 확인할 수 있다. 그런데도 문제가 생긴다.
3초 정도가 대수냐고 할 수도 있지만, 실제 서비스에서는 3초의 시간도 사용자에게 불편하게 느껴질 것이다.
또한 50000페이지보다 더 뒷 페이지로 가면 갈 수록, 소요 시간은 더 늘어날 것이다.
어떻게 해결해야 할까? 인덱스에 대해 조금 더 깊게 알아보자.
* MySQL의 엔진 및 인덱스의 종류
MySQL의 기본 스토리지 엔진은 InnoDB이다.
- 스토리지 엔진 : DB의 데이터 저장 및 관리 장치
그리고, InnoDB는 테이블마다 Clustered Index를 자동 생성한다.
* MySQL의 Clustered Index란?
클러스터드 인덱스는 PK를 기준으로 정렬된 인덱스이다.
Primary Index(주 인덱스)라고도 불리고, 일반적으로 PK에 생성된다.
클러스터드 인덱스는 leaf node의 값으로 행 데이터(row data)를 가진다.
article 테이블에는 article_id(PK)를 기준으로 하는 클러스터드 인덱스가 생성되어 있고, 데이터를 가진다.
PK를 통한 조회는 자동으로 생성된 클러스터드 인덱스로 수행되는 것이다.
* MySQL의 Clustered Index를 이용한 조회
그럼 위에서 생성했던 인덱스 idx_board_id_article_id가 아니라 PK 조건을 걸어서 조회를 해보자.
article_id의 값은 임의로 지정했다.
select * from article where article_id = 155628446000697344;
매우 빠르게 조회된 것을 확인할 수 있다. 쿼리 플랜을 확인해보자.
explain select * from article where article_id = 155628446000697344;
별도의 인덱스를 생성한 것이 없는데, key를 보니 PRIMARY라고 되어있다.
그럼 내가 생성한 인덱스는 무슨 인덱스인가?
* Secondary Index란?
내가 생성한 인덱스는 Secondary Index이다. Non-Clustered Index라고도 부른다.
세컨더리 인덱스의 leaf node는 다음 데이터를 가지고 있다.
- 인덱스 컬럼 데이터
- 데이터에 접근하기 위한 포인터
여기서 데이터는 클러스터드 인덱스가 갖고 있다.
세컨더리 인덱스는 인덱스에 지정한 컬럼을 key로 가진다.
그리고 leaf node의 데이터는 데이터에 접근하기 위한 포인터(PK)를 가진다.
내가 생성했던 board_id, article_id 인덱스에 이 것을 대입해보면,
article_id는 인덱스 컬럼 데이터이면서, 데이터에 접근하기 위한 포인터이다.
세컨더리 인덱스를 이용한 데이터 조회는 인덱스 트리를 2번 타고 있는 것이다.
- 세컨더리 인덱스에서 데이터에 접근하기 위한 포인터를 찾는다.
- 클러스터드 인덱스에서 데이터를 찾는다.
페이징 쿼리가 저 절차대로 수행되고 있는지 분석해보자.
select * from article
where board_id = 1
order by article_id desc
limit 30 offset 1499970;
- (board_id, article_id)에 생성된 세컨더리 인덱스에서 article_id를 찾는다.
- 클러스터드 인덱스에서 article 데이터를 찾는다.
- offset 1499970을 만날 때까지 반복하며 skip한다.
- limit 30개를 추출한다.
이 과정을 확인하면 데이터는 offset 1499970부터 30개의 데이터만 필요한데,
offset 1499970을 만날 때까지 반복하며 데이터에 접근하고 있는 것이다.
이 비효율적인 과정을 생략할 수는 없을까?
* board_id, article_id만 추출
내가 만든 세컨더리 인덱스는 board_id와 article_id를 포함한다.
세컨더리 인덱스에서 필요한 30건에 대해서 article_id만 먼저 추출하고,
그 30건에 대해서만 클러스터드 인덱스에 접근하면 되지 않을까?
article_id는 클러스터드 인덱스에 접근하지 않아도 가져올 수 있을 것 같다.
쿼리에서 전체를 추출하는 것이 아니라, board_id와 article_id만 추출하도록 수행해보자.
select board_id, article_id from article
where board_id = 1
order by article_id desc
limit 30 offset 1499970;
전체 데이터를 조회할 때는 약 3초가 소요됐었는데, board_id와 article_id만 추출하는 것은 0.4초가 소요되었다.
쿼리 플랜을 확인해보니, Extra = Using index가 추가되었다. 인덱스만 사용해서 데이터를 조회했음을 알 수 있다.
이 인덱스는 무엇일까?
* Covering Index
이렇게 인덱스의 데이터만으로 조회를 수행할 수 있는 인덱스를 Covering Index라고 한다.
커버링 인덱스는 인덱스만으로 쿼리의 모든 데이터를 처리할 수 있는 인덱스이다.
클러스터드 인덱스의 데이터를 읽지 않고, 세컨더리 인덱스에 포함된 정보만으로 조회가 가능한 인덱스를 의미한다.
그럼 이제 추출된 30건의 article_id에 대해서 클러스터드 인덱스에 접근하면 된다.
* 해결 방법
30건의 article_id를 의 서브 쿼리의 결과로 만들고, article 테이블과 join 해보자.
select * from
(select article_id from article
where board_id = 1
order by article_id desc
limit 30 offset 1499970
) as t left join article
on t.article_id = article.article_id;
50000번 페이지에서 30개의 게시글을 추출하기 위해, 약 3초 가량 걸리던 쿼리가 0.2초로 개선되었다.
커버링 인덱스를 활용하여 offset 1499970에서부터 클러스터드 인덱스에 접근하게 된 것이다.
쿼리 플랜을 확인해보자.
article_id를 추출하기 위한 서브 쿼리 과정에서 파생(DERIVED) 테이블이 생기지만, 이 과정에서 커버링 인덱스가 사용되었다. 작은 규모의 파생 테이블과 조인하여 30건에 대해서만 클러스터드 인덱스에서 데이터를 가져오기 때문에 빠르게 처리할 수 있다.
'DB' 카테고리의 다른 글
[DB] 비관적 락(Pessimistic Lock) & 낙관적 락(Optimistic Lock) (0) | 2025.03.28 |
---|---|
[DB] Lock의 개념 및 테스트 (0) | 2025.03.28 |
[DB] Index 실습(대용량 데이터 처리) - MySQL (0) | 2025.03.08 |
[DB] 트랜잭션 (0) | 2025.02.07 |
[DB] SQL의 개념 (0) | 2021.09.10 |