권순용의 DB 이야기

SQL이 성능을 보장받기 위해서 어떻게 해야 하는가? 그 정답은 인덱스에 있을 것이다. 그렇다면 모든 SQL이 인덱스를 이용하면 성능을 보장받을 수 있을까?

SQL이 인덱스를 이용하는지 이용하지 않는지를 가지고 성능을 예측하는 것은 우리에게 많은 시행 착오를 가져다 줄 것이다.

물론, 해당 SQL이 인덱스를 이용해야 할 지 아니면 인덱스를 이용하지 말아야 할지를 결정하는 것이 SQL 최적화의 첫 걸음임에는 틀림 없다. 하지만, 이것만으로 SQL을 최적화할 수 없는 것이 현실이다.

우리는 인덱스를 이용해야 하는 SQL을 구분하여 해당 SQL은 최적의 인덱스를 이용해야 하며 이와 같은 최적의 인덱스를 이용하기 위해서는 결합 인덱스를 선정하는 기준을 이해해야 할 것이다.

지난 강의의 연산자에 의한 결합 인덱스 컬럼 순서 선정에이어 결합 인덱스를 구성하는 두 번째 우선 순위를 확인해 보자.

랜덤 액세스를 이해하자

결합 인덱스의 컬럼 순서는 네 개의 우선 순위에 의해 생성해야 할 것이다. 우선 순위를 어긋나게 인덱스를 생성한다면 우리는 더 이상 해당 인덱스를 이용하여 성능 향상을 기대하기는 어렵다.

  • - 1순위 : 컬럼이 사용한 연산자에 의한 인덱스 컬럼 선정
  • - 2순위 : 랜덤 액세스를 고려한 인덱스 컬럼 선정
  • - 3순위 : 정렬 제거를 위한 인덱스 컬럼 선정
  • - 4순위 : 단일 컬럼의 분포도를 고려한 인덱스 컬럼 선정

두 번째 우선 순위인 랜덤 액세스는 무엇인가? 랜덤 액세스는 데이터를 저장하는 블록을 한번에 여러 개 액세스하는 것이 아니라 한 번에 하나의 블록만을 액세스하는 방식이다.

한 번에 여러 개의 블록을 액세스한다면 같은 양의 데이터에 대해 적은 횟수의 디스크 I/O가 발생하기 때문에 성능이 향상될 수 있다.

테이블을 처음부터 끝까지 액세스하는 테이블 전체스캔(Table Full Scan)의 경우에는 한 번에 여러 개의 블록을 액세스할 수 있기 때문에 한 번에 여러 블록을 액세스하는 다중 블록 I/O를 수행하게 된다.

그렇다면 과연 어떤 작업에서 랜덤 액세스가 발생하는 것일까? 바로 인덱스를 액세스하여 확인한 ROWID를 이용하여 테이블을 액세스하는 경우 랜덤액세스가 발생하게 된다.

ROWID는 해당 데이터를 찾아가는 유일한 주소 값이며 우리가 인덱스를 이용한다는 것은 인덱스로부터 조건을 만족하는 인덱스 값을 액세스한 후 ROWID를 확인하여 ROWID 값으로 테이블을 액세스하는 것을 의미한다.

이와 같이 인덱스 액세스 후 테이블을 액세스하는 경우에 발생하는 I/O는 한 번에 하나의 블록만 액세스하는 랜덤 액세스가 발생하게 된다.

랜덤 액세스의 종류를 분석하자

인덱스 액세스 후 테이블을 액세스하는 것은 랜덤 액세스를 발생시키게 된다.

이와 같이 발생하는 랜덤 액세스는 모두동일한 것인가? 그렇다면 랜덤 액세스에는 어떤 종류가 존재하는가? 우리가 보기에는 동일한 랜덤 액세스로 보이지만 실제로는 랜덤 액세스는 다음과 같이 세 가지로 구분된다.

  • - 확인 랜덤 액세스
  • - 추출 랜덤 액세스
  • - 정렬 랜덤 액세스

첫 번째로 확인 랜덤 액세스는 무엇인가? 확인 랜덤 액세스는 WHERE 조건에 의해 발생하게 된다.

만약, WHERE 조건에는 카드번호 조건과 거래일자 조건이 함께 있으며 인덱스는 카드번호 컬럼으로만 구성되어 있다고 가정하자. 이와같다면 카드번호 컬럼에 의해 인덱스를 액세스하게 될 것이다.

결국, 카드번호 컬럼에 의해 처리 범위가 감소하게 된다. 그렇다면 거래일자 컬럼은 어떤 역할을 수행하게 되는가?

분명 카드번호 조건을 만족하는 데이터 중 거래일자 조건을 만족하는 데이터만을 결과로 추출해야 할 것이다. 하지만, 카드번호 컬럼으로만 인덱스가 구성되므로 거래일자 조건을 인덱스에서 확인할 수 없게 된다.

따라서, 카드번호 조건을 만족하는 모든 데이터에 대해 테이블을 액세스하여 거래일자 컬럼의 값을 확인해야 할 것이다.

이와 같이 WHERE 조건의 컬럼이 인덱스에 존재하지 않아 테이블을 액세스하는 랜덤 액세스를 확인 랜덤 액세스하고 한다.

확인 랜덤 액세스의 특징은 랜덤 액세스의 횟수보다 최종 결과가 동일하거나 더 적게추출된다는 것이다. 확인 랜덤 액세스는 랜덤 액세스가 발생한 후 버려지는 데이터가 존재하기 때문이다.

애써서 테이블을 액세스한 후 버려진다는 것은 매우 안타까운 일이다. 인덱스를 선정 함에 있어 확인 랜덤 액세스의 제거는 성능에 있어매우 중요한 역할을 수행하게 된다.

두 번째로 추출 랜덤 액세스를 확인해 보자.

WHERE 절의컬럼이 아니라 SELECT 절의 컬럼을 확인해 보자. WHERE절의 컬럼은 모두 인덱스에 존재하지만 SELECT 절의 컬럼이 인덱스에 존재하지 않는다면 어떤 현상이 발생하는가?

SELECT 절의 컬럼을 결과로 추출하기 위해서는 반드시 인덱스 액세스 후 테이블을 액세스해야 할 것이다. 이와 같은 현상이 추출 랜덤 액세스에 해당한다.

추출 랜덤 액세스의 특징은 랜덤 액세스의 횟수와 추출되는 데이터의 양이 동일하게 된다. SELECT 절의 컬럼들은 추출되는 데이터를 감소시키거나 증가시키지 못한다.

따라서, 추출 랜덤 액세스는 발생한만큼 결과로 추출된다.

세 번째로 정렬 랜덤 액세스는 추출 랜덤 액세스와 거의 동일하다.

ORDER BY 절 등에 사용된 컬럼이 인덱스에 존재하지 않아 테이블을 액세스하여 정렬을 수행하기 위해 데이터 를 액세스하는 랜덤 액세스를 발생시키는 경우를 정렬 랜덤 액세스라고 한다.

정렬 랜덤 액세스 또한 랜덤 액세스의 횟수와 추출되는 데이터의 건수가 동일하게 된다.

앞서 확인한 것과 같이 추출, 정렬 랜덤 액세스는 랜덤 액세스의 횟수와 추출되는 데이터의 건수에는 변화가 없다.

하지만, 확인 랜덤 액세스는 추출되는 데이터의 건수가 감소할수 있으며 그렇기 때문에 버려지는 데이터가 존재할 수 있다.

이와 같은 이유에서 확인 랜덤 액세스가 랜덤 액세스 중 가장 많은 부하를 발생시키며 우리는 최우선적으로 확인 랜덤 액세스를 제거하기 위해 노력해야 할 것이다.

효과적인 인덱스 선정을 통해 확인 랜덤 액세스를 제거하자

추출 또는 정렬 랜덤 액세스를 제거하는 것도 성능을 향상시키게 되지만 가장 먼저 확인 랜덤 액세스를 제거해야 할 것이다.

SELECT 카드번호, 사용액
  FROM 거래내역
 WHERE 카드번호 = '111'
   AND 거래일자 BETWEEN '20080501' 
                   AND '20080510';

거래내역 테이블에는 카드번호+가맹점 인덱스가 존재한다고 가정하자. 이와 같으며 카드번호+거래일자 인덱스는 추가로 생성할 수 없다고 가정하자.

인덱스의 추가 또는 삭제가 운영 중인 시스템에서는 매우 위험한 작업일 수 있다. 또한, 카드번호 컬럼은 인덱스의 첫 번째 컬럼이므로 인덱스에서 액세스하는 처리 범위를 감소시키게 된다.

하지만, 거래일자 컬럼은 인덱스에 존재하지 않기 때문에 인덱스에서 그 값을 확인할 수 없게 된다. 따라서, 거래일자 컬럼의 값을 확인하기 위해서 테이블을 액세스해야 하며 액세스한 데이터 중거래일자 조건을 만족하는 데이터만을 결과로 추출하게 된다.

위와 같은 랜덤 액세스는 확인 랜덤 액세스에 해당할 것이다.

이 경우에 기존 인덱스에 거래일자 컬럼을 추가한다면 어떻게 되는가?

카드번호+가맹점+거래일자 인덱스를 생성한다면 거래일자 컬럼 앞에 가맹점 컬럼이 존재하므로 거래일자 컬럼은 처리 범위를 감소시키는 역할을 수행하지는 못하지만 거래일자 컬럼이 인덱스에 존재하게 되므로 확인 랜덤액세스는 발생하지 않게 된다.

이처럼 인덱스를 조정하여 확인 랜덤 액세스를 제거할 수 있을 것이다. 이와 같이 랜덤 액세스를 제거하는 방법이야 말로 해당 SQL의 성능을 향상시킬 수 있는 방법이 될 것이다.

다음 강의에서는 추출 랜덤 액세스와 정렬 랜덤 액세스를 제거하는 방법에 대해 인덱스 선정을 통해 확인해 보자.

- 강좌 URL : http://www.gurubee.net/lecture/2230

- 구루비 강좌는 개인의 학습용으로만 사용 할 수 있으며, 다른 웹 페이지에 게재할 경우에는 출처를 꼭 밝혀 주시면 고맙겠습니다.~^^

- 구루비 강좌는 서비스 제공을 위한 목적이나, 학원 홍보, 수익을 얻기 위한 용도로 사용 할 수 없습니다.

by 아발란체 [2013.04.18 10:06:12]

이런 강좌 내용은 돈 주고 사는 책에는 없죠?
없을거 같아요. 호호호.
후다닥 ==3


by 김한식 [2013.10.18 12:59:19]
참 좋은 강의 입니다.
랜덤 엑세스에 대해서 명확하게 이해를 할 수 있었어요.


by 암드 [2013.10.22 15:50:57]

정말 좋은 강의 감사합니다 랜덤엑세스에 대해 잘 이해가 가지 않았는데 명확히 이해가 가내요

by 노주혁 [2013.12.06 15:33:42]
랜덤액세스에 대해 쉽게 정리가 되네요.  감사합니다.

by 오라오라 [2013.12.26 18:04:54]

정말 좋은 강좌 감사합니다.

by 참된신자 [2014.07.30 17:11:16]

감사합니다 :)


by 앙그르르 [2015.01.14 14:46:56]

이해하기 쉽게 좋은 글 써주셨네요. 감사합니다.


by 초보 개발자 [2017.08.07 11:27:38]

항상 좋은글 감사드립니다. 정말 좋은 사이트입니다 감사합니다


by kyhwan89 [2019.06.14 11:04:58]

카드번호+가맹점+거래일자 인덱스를 생성

거래일자 컬럼이 인덱스에 존재 → 확인 랜덤 액세스는 발생 X

위의 설명이 이해가 잘 되지 않는데 설명 부탁드리겠습니다!

감사합니다.


by 뿌리뽑자오라클 [2019.10.16 08:46:11]

저도 보다보니 아래  부분이 왜 그런지 이해가 좀 안가네요.

설명 부탁 드립니다.

 

카드번호+가맹점+거래일자 인덱스를 생성

거래일자 컬럼이 인덱스에 존재 → 확인 랜덤 액세스는 발생 X

위의 설명이 이해가 잘 되지 않는데 설명 부탁드리겠습니다!

감사합니다.


by jkson [2019.10.16 09:05:59]

현재 인덱스 정보에는 카드번호, 가맹점 밖에 없는 상황입니다.

WHERE 절 조건에 해당하는 데이터를 찾으려면

카드번호, 거래일자로 데이터를 찾아야 하는데 인덱스에는 카드번호밖에 없어요.

그러면 해당 인덱스에서 카드번호가 일치하는 데이터를 모두 찾아놓고 인덱스에 있는 ROWID를 통해

테이블로 가서 거래일자가 20080501~20080510인 데이터가 맞는지 확인해봐야

최종데이터를 가져올 수 있습니다.

만약 WHERE절이 모두 인덱스 컬럼이면 굳이 테이블로 가서 확인하지 않아도 되는데 확인해야 되는 상황인 거죠.

그래서 카드번호, 가맹점으로 구성된 인덱스에 거래일자를 추가해주면 테이블로 접근할 필요가 없게 되는 것이죠.

추출인덱스도 마찬가지입니다. 만약 사용액을 또 인덱스에 추가한다고 하면 사용액 데이터를 가져오기 위해

테이블로 갈 필요도 없습니다. 인덱스 정보만으로 SELECT 절 정보를 모두 표시할 수 있으니까요.

ORDER BY도 마찬가지이고요. 그래서 SELECT절 WHERE절 ORDER BY절 모든 컬럼이 인덱스 컬럼이면

인덱스만 탐색하고 테이블은 탐색하지 않는 상황까지도 만들 수 있습니다.

인덱스를 해당 컬럼들(위 예제에서는 카드번호, 가맹점)로 구성된 테이블인데 소트된 상태로 저장되는 별도의 테이블이라고 생각하시면 편합니다.

다만 인덱스는 실제 테이블에 데이터가 존재하는 ROWID 정보도 추가로 가지고 있어

소트된 컬럼으로 데이터를 찾고 추가로 더 필요한 정보가 있으면 ROWID를 통해 테이블로 접근한다고 생각하시면

좀 쉬우실 거예요.

이런 구조로 생각하시면 랜덤액세스가 많아질 경우 왜 성능저하가 일어나는지도 이해하기 쉬우실 겁니다.

하드디스크라고 생각해보면 인덱스블록 읽었다가 인덱스블록 ROWID 정보로 테이블 블록 읽었다가 이게 

수만번 반복된다고 생각해보시면 하드디스크 핀이 이리갔다 저리갔다 왔다갔다 정신 없겠죠.

그래서 소수 정보를 뽑을 때는 인덱스 탐색이 유리하지만 전체 데이터에서 많은 데이터를 가지고 올 때는

블록 첨부터 블록 마지막까지 한번에 쭉 읽는 테이블 풀스캔이 유리한 것이고요.


by 비유 [2020.09.24 19:50:04]

정말 너무 감사합니다. 이해가 너무 잘됩니다


by 솧 [2021.03.14 15:52:37]

포스팅 감사드립니다! 감사인사 전하려고 가입했어요. :)

댓글등록
SQL문을 포맷에 맞게(깔끔하게) 등록하려면 code() 버튼을 클릭하여 작성 하시면 됩니다.
로그인 사용자만 댓글을 작성 할 수 있습니다. 로그인, 회원가입