RDBMS 이론이 소개 된 지 어느덧 30년이 지났다. 30년의 긴 세월이 흐르면서 RDBMS는 우리의 일상에도 많은 부분을 차지하게 된 것 같다. 우리가 사용하는 대부분의 웹 사이트에서 RDBMS를 사용하고 있으며 이로 인해 우리의 정보는 체계적으로 관리되고 있을 것이다. RDBMS의 보급화와 함께 RDBMS의 기술은 계속 향상되고 있지만 이 중에서 RDBMS의 탄생과 함께 태어난 조인은 그 중요성에 비해 많은 천대를 받는 것 같다. 이제라도 우리는 RDBMS의 꽃인 조인을 정확히 이해해 보자.
지금도 수많은 사이트에서 데이터 연결에 대한 불신을 가지고 있는 것이 현실이다. 어떤 사이트에 가보면 데이터 연결 방법 중에 하나인 조인을 사용하지 못하게 하고 또 어떤 사이트에 가보면 서브쿼리를 사용하지 못하게 하는 것을 지켜보는 경우가 있었다. 물론, 서브쿼리도 데이터 연결의 한가지 방법이며 조인과 거의 유사하게 수행된다.
분명 조인은 RDBMS의 꽃이며 이러한 핵심 기술을 이용하지 않는다면 RDBMS를 이용해서 무슨 효과를 기대할 수 있겠는가? 조인의 실제 수행 방식을 이해하지 않으려 하고 조인을 잘못 사용했기 때문에 나타나는 현상을 그대로 받아들이고 그것만을 가지고 모든 것을 평가하는 것이 현실인 것 같다.
조인의 수행 방식을 정확히 이해했다면 우리는 이와 같은 성능 저하 현상을 바로 잡을 수 있다는 사실을 명심해야 할 것이다. ‘조인은 성능을 저하시킨다’는 잘못 알고 있는 사실을 다시 확인해 보는 기회로 삼아보았으면 좋겠다.
조인은 데이터를 감소시킨다
조인은 현재 데이터베이스의 크기를 감소시킬 수 있는 유일한 방법이다. 이와 같이 이야기한다면 의아해 할 것이다. 조인이 어떻게 데이터베이스의 크기를 감소시킬 수 있겠는가? 당연히 조인을 이용하여 데이터베이스의 크기를 감소시킬 수는 없다.
여기서 말하는 것은 데이터베이스의 정규화를 거치게 되면 데이터베이스에 존재하는 테이블은 분리되며 테이블이 분리된다면 일반적으로 데이터베이스의 크기는 감소하게 된다. 이와 같이 테이블을 분리하여 두 개 이상의 테이블에서 우리가 원하는 데이터를 추출하기 위해서는 조인을 이용해야 한다는 것이다.
이와 같기 때문에 정확히 이야기한다면 데이터를 감소시키기 위해 조인을 사용한다기 보다는 크기가 감소된 데이터에 대해 조회를 수행하기 위해 조인을 사용한다는 것이다.
예를 들어, 어느 회사에 직원 테이블이 존재하며 또 하나의 경우는 사원 테이블과 부서 테이블이 별도로 존재한다고 가정하자. 회사에는 1,000,000명의 직원이 존재하며 해당 회사에는 1,000개의 팀이 존재한다고 가정하자. 그렇다면 아래와 같이 구성될 수 있을 것이다.
<그림>처럼 테이블을 구성했다고 가정하자. 그렇다면 각각의 테이블의 크기는 어떻게 되겠는가? 먼저, 직원 테이블을 확인해 보자. 직원 테이블의 하나의 데이터의 길이는 1055Byte의 길이이다. 또한, 직원 테이블에는 1,000,000건의 데이터가 저장되므로 해당 테이블의 전체 크기는 데이터만 1,000,000건*1055Byte=1055MB가 되므로 약 1GB 정도의 크기가 된다. 그렇다면 위의 그림에서 밑에 존재하는 사원 테이블과 부서 테이블을 분리한 경우는 어떠한가?
<그림> 조인이 필요없는 테이블과 조인이 필요한 테이블
사원 테이블은 1,000,000건의 데이터가 저장되어 있으며 하나의 데이터의 길이는 535Byte가 된다. 사원 테이블의 전체 데이터 건수는 1,000,000건이므로 1,000,000*535 Byte = 535MB가 되므로 사원 테이블의 크기는 535MB 정도가 된다. 그렇다면 부서 테이블의 크기는 어떠한가? 부서 테이블의 하나의 데이터의 길이는 525 Byte가 된다.
부서 테이블에는 1,000건의 데이터가 존재하므로 테이블의 전체 크기는 1,000*530Byte이므로 530KB의 크기가 된다. 따라서 위의 그림의 아래와 같이 부서 테이블과 사원 테이블로 구성한다면 두 테이블의 크기를 모두 합해도 535MB 정도의 크기가 될 것이다.
그렇다면 위의 테이블 크기 및 구조를 통해 우리는 무엇을 알 수 있겠는가? 우리가 유추해 낼 수 있는 항목은 크게 두 가지이다.
첫 번째로 두 가지 경우에 대해 모든 데이터를 추출하기 위해 액세스해야 하는 데이터의 양이다. 위의 그림에서 직원 테이블로만 구성되어 있는 경우를 확인해 보자. 직원 테이블의 데이터를 모두 추출하고자 한다면 직원 테이블의 크기만큼 디스크 I/O가 발생하게 된다. 따라서, 직원 테이블의 크기인 1055MB만큼의 디스크 I/O가 발생하게 된다.
반면에 위의 그림에서 사원 테이블과 부서 테이블을 분리한 경우는 어떠한가? 사원 테이블과 부서 테이블을 액세스하여 모든 데이터를 추출해야 한다면 각각의 테이블을 모두 액세스해야 할 것이다. 그렇기 때문에 위에서 계산한 것과 같이 535MB의 디스크 I/O가 발생하게 된다.
이와 같다면 과연 어떻게 테이블을 구성해야 하겠는가? 물론, 위의 그림에서 테이블을 어떻게 구성하는가에 상관 없이 결과는 동일한 데이터가 추출될 것이다. 직원 테이블로만 구성한 경우에는 1055MB의 디스크 I/O를 발생시키게 되며 사원 테이블과 부서 테이블로 분리하여 모든 데이터를 액세스한다면 535MB의 디스크 I/O를 발생시키게 된다.
디스크 I/O를 통해 확인한다면 직원 테이블로 구성하는 것보다는 사원 테이블과 부서 테이블로 분리하는 형태를 선택해야 할 것이다. 사원 테이블과 부서 테이블로 분리하는 형태를 선택한다면 우리는 더 적은 디스크 I/O를 통해 원하는 모든 데이터를 추출할 수 있을 것이다.
두 번째로 원하는 데이터를 추출하기 위한 데이터의 액세스 방법을 예상할 수 있을 것이다. 직원 테이블 하나로 구성하는 경우에는 하나의 테이블만 존재하게 되므로 하나의 테이블을 통해 모든 데이터를 액세스할 수 있게 된다.
그렇기 때문에 인덱스가 존재한다면 인덱스를 이용하는 수행 방식을 이용하거나 또는 인덱스가 존재하지 않는다면 인덱스를 이용하지 않고 테이블을 처음부터 끝까지 액세스하게 될 것이다. 반면에 사원 테이블과 부서 테이블로 분리하는 경우는 두 테이블의 데이터를 동시에 추출하기 위해서는 조인을 사용해야 할 것이다.
이와 같이 테이블을 분리한다면 전체 데이터의 크기는 감소한다. 데이터의 감소는 우리에게 많은 혜택을 주게 된다. 이러한 데이터의 감소를 위해서는 테이블을 분리해야 하며 분리된 테이블에서 원하는 데이터를 추출하기 위해서는 조인을 이용해야 한다. 이처럼 대용량 데이터베이스의 데이터를 감소시키기 위해서는 조인은 필수 불가결할 것이다.
조인을 수행하기 위한 경우의 수를 이해하자
조인을 수행하기 위해서는 다양한 경우의 수가 존재한다고 것을 이해하겠는가? 그렇다면 과연 조인 방식에는 어느 정도 다양한 경우의 수가 존재하는 것일까? 그 중 어떤 조인 방식이 최적의 성능을 보장하겠는가? 이제부터 조인을 수행하기 위한 경우의 수에 대해 확인해 보자.
SQL> SELECT COL1, COL2, COL3
FROM TAB1, TAB2, TAB3
WHERE TAB1.KEY1 = TAB2.KEY2
AND TAB2.KEY2 = TAB3.KEY3;
위와 같은 기본적인 조인 SQL을 수행한다고 가정하자. SQL은 데이터베이스에 존재하는 테이블의 데이터를 액세스하는 언어이다. 그렇다면 위의 조인 SQL이 수행될 수 있는 경우의 수에는 어떤 경우가 존재하는가? 여기서 말하는 경우의 수는 조인이 수행되는 방법을 의미한다. 조인이 수행될 수 있는 경우의 수는 아래와 같은 두 가지 요소에 의해 다양하게 발생할 수 있다.
·테이블의 조인 순서-먼저 액세스되는 테이블과 뒤에 액세스되는 테이블
·조인 방식-중첩 루프 조인, 해쉬 조인, 소트 먼지 조인
첫 번째로 위의 요소 중 테이블의 조인 순서를 확인해 보자. 조인은 두 개 이상의 테이블에서 필요한 데이터를 추출하게 되므로 먼저 액세스하는 테이블과 뒤에 액세스하는 테이블이 존재하게 된다. 그렇기 때문에 세 개의 테이블에 대해 조인을 수행하게 된다면 조인 순서는 3*2*1=6의 경우의 수가 존재하게 된다.
예를 들어, TAB1 테이블이 먼저 액세스되고 뒤에 TAB2 테이블이 액세스되며 마지막에 TAB3 테이블이 액세스되는 조인 순서도 이 경우의 수에 포함이 될 것이다. 결국, 테이블 조인 순서에 의해 발생할 수 있는 경우의 수는 6개가 된다.
두 번째로 조인 방식에 의한 경우의 수를 확인해 보자. 조인 방식은 조인을 수행하는 내부 수행 방법을 의미한다. 앞서 언급했듯이 조인은 세 가지 방식이 존재하게 된다. 따라서, 조인 방식은 세 가지의 경우의 수가 존재할 것이다.
이와 같다면 해당 조인 SQL에서 최종적으로 발생하는 경우의 수는 어떻게 되겠는가? 두 가지 경우의 수의 곱이 전체 경우의 수가 될 것이다. 그렇기 때문에 전체 경우의 수는 6*3=18가지의 경우의 수가 존재하게 된다.
예를 들어, TAB1 테이블, TAB2 테이블 및 TAB3 테이블 순으로 조인이 수행되며 TAB1 테이블과 TAB2 테이블은 해쉬 조인을 사용하고 그 결과 값과 TAB3 테이블은 중첩 루프 조인을 수행하는 경우도 18가지의 경우의 수에 포함될 것이다.
이와 같이 하나의 조인 SQL은 우리가 생각지도 않은 다양한 경우의 수가 존재하며 해당 조인 SQL이 수행될 경우에는 18가지 경우의 수 중 하나의 방법을 선택하여 조인 SQL을 수행하게 된다.
그렇다면 조인 SQL의 수행을 위한 경우의 수와 성능은 어떤 관계를 가지게 되는가? 이는 성능과 매우 밀접한 관계를 가지게 된다. 위의 예제에서 18가지의 경우의 수중 최적의 성능을 보장하는 경우는 몇 가지 안 된다. 나머지 경우의 수는 성능을 저하시키는 경우에 해당된다. 이제부터 우리는 이러한 조인의 경우의 수를 고려하여 조인 SQL을 작성해야만 성능을 보장 받을 수 있을 것이다.
조인의 성능은 다양하다
전체 데이터를 액세스하는 경우에는 하나의 테이블을 액세스하는 경우보다는 분리된 테이블의 데이터를 액세스하는 경우에 디스크 I/O가 감소했다. 디스크 I/O만 고려한다면 성능과 관련된 모든 항목은 고려된 것인가? 그것만은 아닐 것이다. 그렇다면 성능 관련해서 과연 무엇을 고려해야 하는가?
그것은 분리된 테이블에서 원하는 데이터를 추출하기 위해 사용하는 조인의 수행 방식이다. 하나의 테이블을 액세스한다면 인덱스를 이용한 테이블 액세스 방식 또는 테이블을 처음부터 마지막까지 액세스하는 테이블 전체 스캔 방식이 존재하게 된다. 이와 같이 하나의 테이블을 액세스한다면 매우 단순한 액세스가 발생하게 된다.
하지만, 조인을 이용한다면 이와 같이 단순한 방식으로만 수행되는 것은 아니다. 조인을 수행하기 위한 조인 방식이 존재하며 이와 같은 조인 방식을 이용하여 조인을 수행하게 된다.그렇다면 조인 방식에는 어떠한 것이 존재하는가?
·중첩 루프 조인(NESTED LOOP JOIN)
·해쉬 조인(HASH JOIN)
·소트 머지 조인(SORT MERGE JOIN)
위와 같은 조인 방식이 존재한다는 것은 무엇을 의미하는가? 하나의 조인은 위의 방법 중 하나의 조인 방식을 이용할 수도 있으며 경우에 따라서는 위의 조인 방식 중 하나의 조인 방식이 아닌 두 개 이상의 조인 방식을 이용할 수도 있다.
이는 무엇을 의미하는가? 경우의 수가 많다는 것은 다양한 처리 방법이 존재하는 것이며 이와 같이 모든 경우의 조인 방법이 동일한 성능을 보장할 수는 없을 것이다. 그렇기 때문에 어떻게 수행되는가에 따라 성능을 보장할 수도 있고 성능을 저하시킬 수도 있을 것이다.
결국, 하나의 테이블을 액세스하는 경우에는 SQL의 실행 계획이 매우 단순하기 때문에 거의 예외가 없지만 두 개 이상의 테이블에서 데이터를 추출하는 조인의 경우에는 위와 같이 많은 경우의 수가 존재하게 된다.
이와 같이 존재하는 많은 경우의 수에는 하나의 테이블로 구성하여 데이터를 액세스하는 경우보다 성능이 저하되는 경우도 존재하며 또는 하나의 테이블로 구성하여 데이터를 액세스하는 경우보다 성능이 더 좋아지는 경우도 존재하게 된다. 일반적으로 많은 경우의 수는 하나의 테이블로 구성하여 데이터를 액세스하는 경우보다 성능이 저하된다.
문제는 여기에 존재하게 된다. 조인을 수행하는 많은 경우의 수가 단일 테이블을 액세스하는 경우보다 성능이 저하될 수 있으며 이와 같은 경우 중 하나의 수행 방식이 선택된다면 우리는 조인이 항상 성능을 저하시킨다는 고정관념에 빠지게 된다. 그렇게 된다면 이와 같은 성능 저하를 경험한 사람은 다시는 조인을 사용하지 않고 단일 테이블로 구성하여 조인을 사용하지 않으려 할 것이다.
이와 같다면 데이터는 자연스럽게 증가하게 된다. 과연, 이것이 올바른 방법인가? 조인을 사용하는 경우에 다양한 수행 방식 중 가장 최적의 조인 방식을 선택한다면 어떻게 되겠는가? 이와 같다면 단일 테이블을 액세스하는 경우보다 조인을 이용하는 경우가 더 좋은 성능을 보장할 수 있을 것이다. 여기서 우리는 결론을 내릴 수 있을 것이다.
테이블을 분리함으로써 우리는 조인을 사용해야 하며 조인을 수행하기 위한 방식에는 다양한 경우의 수가 존재하게 된다. 다양한 경우의 수 중 우리는 최적의 조인 방식을 찾아야만 한다는 것이다. 이 것이 조인을 효과적으로 사용하면서 데이터를 감소시킬 수 있는 유일한 방법이 될 것이다.
조인은 다양하게 표현된다
앞에서 언급한 SQL의 경우는 누가 보더라도 조인이라는 것을 이해할 것이다. 과연, 분리된 테이블에서 원하는 데이터를 추출하는 방법에는 위와 같이 FROM 절에 필요한 테이블을 나열하는 방법밖에는 존재하지 않는 것일까? 이와 같은 방법 외에도 여러 가지 방법으로 분리된 테이블의 데이터를 연결할 수 있다.
·스칼라 서브쿼리-SELECT 절에 SELECT 절을 사용하는 데이터 연결
·서브쿼리-WHERE 절에 SELECT 절을 사용하는 데이터 연결
위와 같은 종류가 중요한 것은 아니다. 이와 같은 종류도 모두 조인에 해당하기 때문에 두 개 이상의 테이블에서 필요한 데이터를 추출하게 되며 그렇기 때문에 먼저 액세스되는 테이블과 뒤에 액세스되는 테이블이 존재하게 된다.
물론, 조인 방식도 앞서 언급한 세 가지 방식 중 하나를 이용하게 된다. 이와 같기 때문에 데이터 연결을 수행하는 대부분의 방식은 다양한 경우의 수가 존재하며 이 중 성능을 보장하는 경우는 몇 가지 존재하지 않게 된다.
결국, 테이블 분리에 의한 데이터의 연결을 수행하는 방식은 다양한 형태로 존재한다. 하지만, 대부분의 데이터 연결 방식이 다양하게 수행된다. 여기서 중요한 것은 다양한 경우의 수에서 성능을 보장하는 경우의 수는 많지 않다는 것이다. 그렇기 때문에 우리의 역할은 다양한 경우의 수에서 최적의 경우의 수를 찾아야 한다는 것이다. 이와 같이 최적의 경우의 수를 찾지 못한다면 우리는 평생 조인을 사용하지 말고 단일 테이블로 구성하여 단순 액세스를 수행해야 한다고 주장하게 될 것이다.
하지만, 최적의 성능을 보장하는 경우의 수를 찾아냈다면 드디어 조인의 혜택을 제대로 누릴 수 있게 되는 것이다. 조인은 절대 성능 저하의 아키텍처를 가지고 있지 않다. 단지, 우리가 모르고 사용하기 때문에 성능 저하를 발생시킨다고 생각하게 되는 것은 아닌가 생각한다.
'DB 이야기' 카테고리의 다른 글
B-TREE인덱스의구조 (0) | 2008.05.08 |
---|---|
데이터베이스 성능 최적화 인덱스는 필요악이다. (0) | 2008.05.07 |
옵티마이저의 비용계산 방법과 실행원리 (0) | 2008.04.29 |
Relation과 Table의 차이 (0) | 2008.04.24 |
PREFIX 인덱스로 성능을 보장하라 (0) | 2008.04.11 |