728x90

조인의 효율적인 처리를 위한


DB별 대용량 데이터의 파티셔닝과 병렬 처리



파티셔닝은 용량이 큰 테이블을 보다 작은 단위로 나눔으로써 성능이 저하되는 것을 방지하고 관리를 수월하게 하고자 하는 개념이다. 하지만 무조건적인 파티셔닝으로는 원하는 목적을 달성하기 어렵다. 파티셔닝 기법에 대한 개념적 차이를 이해하고, 적절한 파티션 전략을 구사하는 방법을 정리했다.



장현호 h2chang@b2en.com|비투엔컨설팅 기술이사. 신세계I&C와 엔코아컨설팅 등을 거쳐 현재 비투엔컨설팅 전문위원 컨설턴트로 근무하고 있으며, 컨설팅 3팀장을 맡고 있다. 다음, 한미르, SK Global, KT, 한국신용정보, 수출보험공사, CJ홈쇼핑 등 수많은 기업의 성능 최적화, 품질관리, 데이터 아키텍처 구축 컨설팅을 수행했다.



최근 10년간 데이터베이스 기술의 발전과 더불어 컴퓨터의 활용이 증가하면서 기업이 축적하고 있는 데이터 량도 상상할 수 없을 만큼 증가했다. 하드웨어와 소프트웨어기술의 발전은 사용자로부터 더 많은 정보요구를 수용하게 만들었고 이렇게 축적된 정보량은 기하급수적으로 증가하면서 대용량 데이터를 보다 안전하고 효율적으로 관리하기 위한 파티셔닝 방안이 도입됐다. 파티셔닝은 용량이 큰(지속적으로 증가하는) 테이블을 파티션이라는 보다 작은 단위로 나눔으로써 성능 저하를 방지하고 관리를 보다 쉽게 하고자 하는 개념이다. 그렇지만 대용량의 데이터를 무조건 파티셔닝만 한다고 해서 목적이 이뤄지는 것은 아니다. RDBMS의 데이터 저장구조, 파티셔닝 기법에 따라 파티셔닝의 개념적 차이가 존재하기 때문에 이를 이해하고 적절한 파티션 전략을 구사해야 관리 용이성과 성능을 확보할 수 있다.


파티션 키의 선정


앞서 언급한 것처럼 무조건 파티션을 나눈다고 파티션이 가지고 있는 이점을 모두 얻을 수 있는 것은 아니다. 잘못된 인덱스가 처리 속도에 악영향을 미치듯 파티션 키를 어떻게 구성하느냐에 따라 비효율을 초래할 수도 있다. 따라서 파티션 키의 선정은 다음과 같은 기준에 따라 전략적인 관점에서 고려돼야 한다.

첫째, 성능향상을 위해 어떤 부분을 고려해야 하는가?

데이터를 처리하는 방법은 크게 인덱스를 경유하는 랜덤 액세스 방법과 인덱스를 경유하지 않고 전체 데이터를 스캔 하는 두 가지가 있다. 데이터의 분포도가 좋아서 인덱스를 사용할 수 있는 상황이라면 문제될 것이 없지만 분포도가 나빠서 인덱스를 사용할 수 없다면 인덱스를 사용하지 않고도 필요한 부분만 액세스를 할 수 있도록 파티셔닝이 이뤄져야 한다. 즉, 액세스 유형에 맞게 파티셔닝이 이뤄질 수 있도록 파티션 키를 선정해야 한다.

둘째, 용이한 데이터 관리를 위해 어떤 부분을 고려해야 하는가?

이력을 관리하는 데이터는 데이터 관리 전략과 업무규칙에 따라 그 수명이 다하면, 별도 저장장치에 기록되고 데이터베이스에서 삭제된다. 이력 데이터는 활용가치에 따라 생성주기와 소멸주기가 결정되고 주기에 따라 데이터베이스를 정리해야 한다. 만약 사용자가 사용하지 않는 데이터를 삭제해야 되는데 그 데이터가 여러 파티션에 분산돼 있다면, 그 데이터를 추출해 삭제하는데 많은 노력과 시간이 필요할 것이다. 하지만 파티션이 데이터의 생성이나 소멸주기와 일치한다면 파티션을 대상으로 작업이 이뤄지므로 관리가 용이하다. 그러면 파티션 키의 선정에 따라 RDBMS에서 조인을 선정하는 방식과 성능적인 부분에 대해 알아보자.


파티션키가 동일한 경우의 조인


대부분의 상용 RDBMS는 조인에 참여하는 두 테이블의 파티션 형태에 따라 다양한 형태의 파티션 조인을 지원하지만, 의미적으로는 크게 파티션 키가 동일한 경우와 파티션 키가 다른 경우이기 때문에 이 두 가지 형태만 알아보자.

DB2 환경에서 파티션 키가 동일한 경우

DB2에서 파티션 키가 동일한 경우 두 테이블은 같은 데이터베이스 파티션 그룹에 공동 배치되고, 조인 또는 서브쿼리를 위해 둘 이상의 테이블에 액세스할 때, 조인할 데이터가 동일한 데이터베이스 파티션에 위치돼 있는지를 인식한다. 이 때 동일한 파티션에 존재하는 경우 데이터가 저장된 데이터베이스 파티션에서 조인하거나 서브쿼리를 수행한다. 이러한 조인방식을 Collocated Join이라 하며, 데이터가 상주하는 파티션 내에서 로컬 조인이 수행되고 파티션 조인이 완료된 후 데이터를 코디네이트 노드로 보낸다(<그림1> 참조).



오라클 환경에서 파티션 키가 동일한 경우

오라클에서 파티션 키가 동일한 경우에는 파티션끼리만 조인을 시도하는 Partition-wise join방식을 사용한다. 이 방식은 조인되는 2개의 테이블이 모두 조인 컬럼으로 동일하게 파티션 돼 있고 두 테이블 모두 Parallel 대상일 경우, 동일 범위 파티션끼리만 조인을 시도하는 Full partition-wise join을 수행한다.

그런데 Full Partition-wise Join은 사용자가 아무리 많은 Parallel Degree를 지정하더라도 파티션 개수 이상의 parallel process가 실행되지 않는다. 파티션 개수만큼의 Parallel Process가 실행됐다면 파티션 1개당 parallel process 1개가 할당되고 각 파티션 별로 해당 파티션끼리만 조인을 한 후 결과를 반환한다.



이런 처리 방법은 파티션 키가 조인키이므로 조인 시 다른 파티션들의 데이터를 고려할 필요가 없어 가능하다.

이 조인 방법은 플랜의 “PARTITION RANGE ALL”이 각각의 “TABLE ACCESS FULL” 위에 나타나지 않고 “JOIN” 위에 나타나는 것으로 확인할 수 있으며, 이것은 파티션 별로 조인한 후에 결과를 합친다는 의미다. DB2와 오라클은 데이터의 저장구조가 다르고 파티션 구현 방식이 차이가 나기 때문에 파티션 조인을 수행하는 방식에 약간의 개념적인 차이가 존재한다. 그렇지만 파티션 키가 동일한 경우에는 조인 수행 시에 파티션 키가 동일한 파티션끼리 쌍을 이뤄 동일한 파티션 내에서 조인을 수행하고 그 결과를 코디네이트 노드로 전송하는 방식으로 조인을 수행하기 때문에 파티션 조인 중 가장 좋은 성능을 낼 수 있다.




파티션 키가 다른 경우의 조인


파티션 키가 다른 경우의 조인 방식은 기본적으로 한쪽의 파티션 키를 기준으로 파티션 한 후 다른 테이블의 파티션이 존재하는 파티션으로 전송, 조인을 수행한 후 그 결과를 다시 코디네이터 노드로 전송하는 방식으로 조인한다. 이때 다른 파티션 키를 가진 테이블의 크기나 상태에 따라 파티션을 하고 데이터를 전송하는 방식에 약간의 차이는 있지만 기본적인 처리 방식은 거의 비슷하므로 가장 일반적인 사례를 통해 처리 방식을 알아보도록 하겠다.

DB2 환경에서 파티션 키가 다른 경우

DB2에서 파티션 키가 다른 경우 두 테이블은 같은 데이터베이스 파티션 그룹에 존재하지 않는다. 따라서 조인 시에 Outer 테이블의 각 행은 Inner 테이블의 파티션 속성에 기초해 Inner 테이블의 한 파티션으로 보내지고, 그 파티션에서 조인이 수행된다(<그림 3> 참조).



오라클 환경에서 파티션 키가 다른 경우

조인 수행 시에 하나의 테이블만 조인 키에 의해 파티션 돼 있고 다른 하나는 파티션 돼 있지 않는 경우에 병렬 처리가 수행될 경우 오라클은 수행 속도 향상을 위해 partial parition-wise join 방식으로 조인을 수행한다. 이때 오라클은 파티션 조인을 수행하기 위해 파티션 되지 않은 다른 테이블의 파티션 키를 기준으로 동적으로 파티션하고, 각각의 파티션 쌍끼리 조인이 가능하도록 데이터를 구성한다.



이 조인 방법을 수행을 위해서는 PARALLEL Execution Server를 두 SET로 나누고, 첫 번째 SET 쿼리 서버에서는 파티션 되지 않은 테이블을 병렬로 읽어 파티션 키 별로 구분해 두 번째 SET 쿼리 서버에 분배하고, 두 번째 SET 쿼리 서버에서는 SET1에서 받은 로우와 자신에게 할당된 파티션과 조인을 수행하게 된다. 예를 들어 SALE 테이블의 파티션1과 매칭하는 CUSTOMERS의 모든 로우들은 SET2의 서버 1로 보내지게 되고, SET2의 서버들은 SET1 서버들로부터 받은 로우들과 자신에게 소속된 파티션과 조인을 수행한다.

이런 메커니즘 때문에 partial partition-wise join으로 수행되는 경우에는 DOP*2개의 프로세서가 수행된다. 하지만 파티션 테이블의 각 파티션에는 하나의 프로세서만 할당할 수 있어 조인 시에 일을 하게 되는 프로세서의 수는 파티션의 수로 한정된다. 조인 시 SQL의 실행계획에서 PARTITION RANGE ALL 오퍼레이션이 파티션 테이블의 위에 나타나고 파티션 되지 않은 다른 테이블의 DISTRIBUTION 컬럼에 “PART(KEY)”라는 항목이 나타나면 Partial partition-wise join으로 수행된다고 생각하면 된다.




파티션 정책 수립


파티셔닝은 논리적 데이터 모델에서 물리적 데이터 모델로 전환하면서 이뤄지는 작업으로 논리적 데이터 모델의 초기 단계에서 데이터 볼륨이 큰 엔터티들에 대해 수직적/수평적 분할을 고려하게 되는데 이 엔터티들이 향후 파티션으로 구현될 후보가 된다. 그렇다고 관리자가 임의로 파티셔닝 여부를 결정할 수는 없으며 아래와 같은 작업을 거쳐 업무 담당 및 현업부서의 동의를 도출해야 한다. 파티션 정책수립의 초기단계에서 관리자가 해야 할 일은 크게 두 가지로 나뉜다.

1.  시스템의 스토리지 구성을 고려한 데이터 파일구성의 제약사항(한계) 값 등을 조사해야 한다. 즉, 아래와 같은 시스템 한계점(bandwidth)들을 기준으로 관리자가 파티션을 적용할 절대 기준을 마련한다.

• DBMS가 지원하는 파티션 개수의 한계
• DBMS가 지원하는 테이블 크기의 한계
• DBMS 버전이 가진 파티션 사용의 제약사항
   예 1> key update 과부하 등의 예를 2개 정도 든다.
   예 2> split
• 현재 사용하고자 하는 버전의 데이터베이스에서 지원하는 파티션 관리 기능 중 대표적인 관리 명령어를 3개 정도 예를 든다.


2.  파티션이 필요한 테이블들을 조사하기 위해서 현업을 대상으로 교육, 조사할 가이드 및 리서치 자료를 만든다. 이 단계에서는 우선 파티셔닝이 필요한 테이블들의 특성을 크게 아래 세 개의 범주로 나누고 대상 테이블들의 특징을 <표 1>과 같은 양식으로 조사한다.

• 용량이 큰 이력 관리용 테이블

- 이력 데이터는 아니지만 절대적으로 용량이 커서 문제가 될 만한 테이블의 경우 설계단계에서 로우 사이즈가 확정되면 관리자는 각 테이블의 예상 데이터 건수와 보관주기를 바탕으로 도출한다. 이력 데이터의 경우 현업이 필요로 하는 온라인 보관주기와 유효보유기간이 지난 데이터의 처리방안을 결정해야 하며, 지난 이력 데이터들이 수정되는 경우가 발생하는지도 확인해야 한다.

• 대용량 병렬 배치작업의 대상이 되는 테이블

- 테이블 상세설계가 끝나고 프로그램 상세설계단계에 도출이 어려울 수도 있는데 이 경우에는 통합 테스트 시에 I/O 컨텐션이 발생하는 큰 테이블 위주로 선정하면 된다.

• 여러 노드로부터 동시적인 데이터 액세스를 일으켜 LOCK 컨텐션이 예상되는 테이블

- 이 경우는 각 노드에 분산되는 업무의 특성과도 밀접한 관련이 있으며 각 노드의 업무적인 특성을 반영시켜 LOCK을 최소화해 구성해야 한다. 필요에 따라 각 노드의 서버 프로그램 목록을 업무적 기준으로 분류, 각 프로그램이 액세스하는 테이블의 I/O형태를 파악한다.




<표 1>에 취합된 테이블 중에서 테이블 크기가 DBA가 나름대로 정한 절대 기준치를 초과하거나 디스크 I/O 컨텐션이 예상되는 테이블들을 파티션 작업 대상 테이블로 선정한다.


테이블별 파티션 정책


테이블별 파티션 정책을 수립하기 위해서는 파티션 종류의 결정, 파티션 키의 선정, 파티션 수의 결정과 같은 단계를 거쳐야 한다. 여기에서는 각 RDBMS에서 지원하고 있는 파티션의 종류의 특징에 대해 별도로 언급하지 않았지만, 어떤 파티션을 적용할 것인가에 대해 판단하려면 사용하고자 하는 RDBMS의 파티션 종류별 특징의 이해는 필수다. 파티션 키의 선정은 파티션 정책에서 가장 중요한 단계로 파티션의 성공이 여기에 달려 있다고 해도 과언이 아니다. 앞서 이 부분에 대해 별도로 언급했지만 그것은 단지 단일 테이블의 파티셔닝 기준에 불과하다.

효율적으로 파티션 키를 선정하기 위해서는 자신의 액세스 패스 외에 파티션 키가 조인에 미치는 영향도 즉, 조인 효율을 고려해야만 한다. 이것은 인덱스 전략 수립 시 하나의 액세스 패스만 보고 인덱스를 결정하지 않는 것과 같은 이치다. 앞서 파티션 정책에 관한 두 단계에 대해 이미 언급했기 때문에 여기에서는 파티션 수의 결정에 대해서만 생각해 보자. 만약 업무 담당자와 면담에서 다음과 같은 요건이 정의된 테이블이 있다면 2년 간의 데이터를 몇 개의 파티션으로 나눠 관리하는 것이 적절할까?

파티션의 수를 몇 개로 할 것인가를 결정할 다양한 외부 영향 요소들을 면밀히 분석해 봐야 한다. 결국 이것은 데이터베이스 가용성과 성능 사이의 고민이 될 것이며 또한 파티션 데이터의 관리, 백업, 재구성이 용이하도록 파티션의 수를 결정하는 것이 효과적이다. 파티션의 수를 결정하는 중요 고려 사항은 다음과 같다.

• DBMS가 지원하는 데이터파일의 최대 개수

• DBMS가 지원하는 한 데이터파일의 최대 사이즈

• I/O 집중의 단위

- 예를 들면 동일시점 데이터에 대해서 지점별, 사원별, 상품별 등의 단위로 동시 트랜잭션이 집중적으로 모이는 경우 적절한 I/O분산을 고려한 개수.

• 관리단위

- 위 표와 같은 경우 변경이 일어나는 현 시점 데이터와 변경이 일어나지 않는 과거 데이터는 일단 분리하고 조회용으로만 사용되는 과거 데이터에 대해서는 일정 단위로 등분해 저장한다.


결론


지금까지 조인에 참여하는 두 테이블의 파티션 키 상태에 따른 병렬처리 방식과 조인 효율에 대해 알아봤다. 파티셔닝은 논리적 단계에서 물리적 단계로 전환하는 시점에 이뤄지기 때문에 물리적 특성을 반영하는 것이 파티션 정책에서 매우 중요하다. 하지만 이러한 접근은 자칫 나무를 보고 숲을 보지 못하게 되는 오류를 범할 수 있다. 즉, 개별적인 테이블 단위의 파티션 정책 수립은 데이터베이스 전체적인 관점의 효율에는 크게 도움이 되지 않을 수도 있다. 때문에 개별적인 파티션 정책 수립 후에는 파티션의 목적에 따라 파티션 키를 통합하고 액세스 패스를 일치시키는 등과 같은 전략적인 차원의 조율이 이뤄져야 한다.




제공 : DB포탈사이트 DBguide.net

728x90

송혁 : SQL Server 2005 Query optimizer의 변경된 내용

제 2강 : SQL Server 2005의 인덱싱 된 뷰 와 포괄 열 인덱스에 대해서…

이름: 송혁
Email : hyok81@nate.com
넥슨 DSM팀 DBA로 근무


1. 시작

일반적으로 하나의 테이블에는 하나의 클러스터 인덱스만 존재 할 수 있으며, 힙 영역에 클러스터 인덱스를 만들게 되면 해당 데이터 영역은 클러스터 영역으로 변하게 됩니다.
서비스에 따라 데이터 갱신에 대한 비용보다 많은 조회에 의한 비용이 많이 들 수 있으며 이럴 경우 두 개 이상의 클러스터 인덱스가 필요 할 수 있을 것입니다.
기존 SQL Server 2000에서는 인덱싱 된 뷰를 생성 하여 두 개 이상의 클러스터 인덱스가 있는 것처럼 활용이 가능 했으며 2005에서는 인덱싱 된 뷰 와 새롭게 추가된 포괄 열 인덱스로써 구현이 가능 합니다.
인덱싱 된 뷰, 포괄 열 인덱스에 대한 활용방안, 두 가지의 차이점 및 유사점에 대해서 시작 하도록 하겠습니다.

2. 인덱스란?

인덱스를 우리말로 번역을 하면 색인 이란 단어로 번역 될 수 있습니다. 색인을 국어사전에서 찾아보면 이와 같이 정의를 하고 있습니다. “책 속의 내용 중에서 중요한 단어나 항목, 인명 따위를 쉽게 찾아볼 수 있도록 일정한 순서에 따라 별도로 배열하여 놓은 목록”
SQL Server에서 불리는 색인 즉 인덱스도 위 단어처럼 책 속이 아닌 테이블에 있는 내용을 보다 쉽게 찾기 위해 따로 일정한 순서로 정리해둔 목록 이라고 볼 수 있습니다.

SQL Server또는 다른 DBMS의 경우에도 인덱스가 RDBMS에서 얼마나 중요한 것인지 누구나 알고 있을 것 입니다. 보다 빨리 데이터를 조회하고 갱신하기 위해서는 인덱스라는 것이 필요하고 인덱스가 존재 함으로써 DBMS의 성능의 차이도 발생 할 수 있으며, 잘 사용한다면 성능을 극대화 시킬 수 있지만 잘못 사용한다면 오히려 성능 문제를 일으킬 수 있습니다.

SQL Server는 이전부터 클러스터 인덱스와 넌 클러스터 인덱스 두 가지 종류의 인덱스를 제공 하고 있습니다. 두 가지의 가장 큰 차이점은 소스 데이터를 리프레벨에 포함하는 여부 입니다. 다른 일반적인 구조는 두 가지 모두 비슷합니다.
클러스터 인덱스는 소스 데이터를 포함 하고 있기에 기본적으로는 하나밖에 생성을 할 수 없으며, 넌 클러스터 인덱스는 리프 레벨에 소스 데이터가 아닌 키로 정의된 열에 대한 데이터 만을 리프레벨에 가지고 있기에 두 개 이상 생성이 가능 합니다. “그렇다면 소스데이터가 여러 개가 있다면 클러스터 인덱스도 여러 개 만들 수 있지 않을까요?” 이 물음에 대해서는 아래에서 보다 자세히 살펴보도록 하겠습니다.

아래는 BOL에 설명하는 클러스터 인덱스와 넌 클러스터 인덱스의 내부 구조를 보여 주고 있습니다.
두 개 모두 인덱스 이기에 모두 루트와 데이터 페이지가 존재 하며, 클러스터 인덱스의 데이터 페이지는 실질적인 데이터를 가르키며, 넌 클러스터 인덱스의 데이터 페이지(리프레벨)는 인덱스 페이지라고 부르는 것이 더욱 쉽게 이해할 수 있을 것입니다. 여기서는 이 두 개의 구조적 차이와 자세한 내용을 언급하는 것이 아니지만 보다 쉽게 이해하기 위해서 선수조건이 되면 좋을 것 같습니다. 이 부분에 대해 더 자세한 내용은 온라인 설명서 및 SQL관련 서적을 참조하시길 바랍니다.

[클러스터형 인덱스 구조]
ms-help://MS.SQLCC.v9/MS.SQLSVR.v9.ko/udb9/html/26b28045-c3c2-465a-b564-bf2189e93fdc.htm

[비클러스터형 인덱스 구조]
ms-help://MS.SQLCC.v9/MS.SQLSVR.v9.ko/udb9/html/1efeba1f-f848-4861-9af3-594e5ab3b597.htm



3. 인덱싱 된 뷰, 포괄 열 인덱스 란

이제 위에서 말한 하나의 테이블에 두 개 이상의 클러스터 인덱스를 생성 할 수 있는 방법(?)에 대해서 살펴보도록 하겠습니다.

인덱싱 된 뷰는 SQL Server 2005이전부터 활용된 기능으로써 인덱싱 된 뷰를 추가적으로 생성하여 조회 쿼리에 대해서 많은 이점을 주었습니다.
기존 테이블에 스키마 바인딩 뷰를 생성 후 해당 뷰에 고유한 클러스터 인덱스를 추가 하게 되면 논리적인 뷰가 아닌 물리적으로 저장이 되는 인덱싱 된 뷰가 생성 되게 됩니다.

물론 하나의 테이블 만이 아닌 조인을 이용하여 여러 개의 테이블을 참조하는 뷰에 대해서도 인덱싱 된 뷰는 활용이 가능하기에 많은 조인, 정열 및 집계 등에 많은 비용이 든다면 충분히 고려해 볼 수 있는 기능 입니다. 그러나 데이터를 중복으로 가짐으로써 성능 상 문제가 될 수 도 있습니다. 그러기에 모든 환경에서의 최적의 솔루션이 아닌, 자기 환경에 정확한 이해를 바탕으로 도입 하는 것이 무엇 보다 중요합니다.
주로 데이터의 갱신작업 이 많이 일어나는 OLTP환경보다, OLAP환경과 같은 DW에 보다 유리하며, OLTP환경도 서비스 마다 틀리겠지만 일반적인 환경에서는 데이터 갱신 보다 데이터 조회의 비용이 더 많이 발생되는 것이 일반 적입니다.

SQL Server 2005에서 새롭게 추가된 넌 클러스터 인덱스에 INCLUDE라는 포괄 열 인덱스라는 것이 추가 되었습니다.
포괄 열 인덱스라는 INCLUDE절은 기존 넌클러스터 인덱스에 키와 인덱스 리프레벨에 포함되는 데이터를 따로 설정 할 수 있으며 인덱스 키만이 비리프레벨에 존재 하고, 나머지 INCLUDE절에 포함된 열에 대해서는 리프레벨에만 존재 하게 됩니다. 열 개수 및 데이터 형 제한에 대해서 기존 넌클러스터 인덱스보다 유연하여 인덱싱 된 뷰의 기능을 과 유사하게 사용 될 수 있습니다.
하지만, 만약 소스 테이블의 열이 16개 이하이고 LOB 데이터형도 없는 경우, 넌 클러스터 인덱스로 비슷한 기능을 구현은 가능 합니다. 그러나 비리프레벨에 모든 인덱스 키의 값이 존재 하기에 비리프레벨 페이지에 많은 하위 페이지에 대한 포인터 정보를 넣을 수 없어 인덱스 깊이가 증가 될 것 입니다. 이것으로 충분한 성능의 문제를 초래 할 수 도 있습니다.

포괄 열 인덱스가 포용할 수 있는 기존 넌클러스터 인덱스의 제한

   - 인덱스의 열 개수의 제한
   - 인덱스 크기(900바이트)의 제한
   - 인덱스의 데이터 타입의 제한(LOB형)
   - 저장 될 모든 열을 인덱스 키로 설정 함으로서 비 리프레벨에도 모든 열의 값이 존재함.

4. 인덱싱 된 뷰, 포괄 열 인덱스와의 차이점

위에서 알아보았듯이 인덱싱 된 뷰는 기존에도 많은 조회용 쿼리를 사용하는 환경에서 사용되었습니다.
정열 된 순서대로 많은 데이터를 가져와야 하고 클러스터 인덱스는 다른 열에 있어 클러스터 인덱스를 활용할 수 없고 covered Index로 처리도 어려운 경우에 인덱싱 된 뷰를 사용하여 보다 빠른 결과를 가져올 수 있었습니다. 또는 집계 데이터 빈번한 조인이 사용하는 쿼리 등등 사용 예를 볼 수 있습니다.
여기서 설명 하지 못한 내용도 충분히 많을 것 이며, 복합 넌 클러스터 인덱스로 불가능한 부분에 대해서 사용하였습니다. 그러나 SQL Server 2005에서는 포괄 열 인덱스를 지원함으로써 단일 테이블, 집계가 없을 경우에는 포괄 열 인덱스를 사용이 가능하기에 보다 쉽게 구현이 가능 하며 관리상의 이슈도 적어집니다. 아래는 간단히 인덱싱 된 뷰와 포괄 열 인덱스에 대한 차이를 정리하였습니다.

[표 1] 포괄 열 인덱스와 인덱싱 된 뷰 비교

인덱싱 된 뷰

포괄 열 인덱스

인덱스 키가 아닌 데이터가 비리프레벨에 존재 여부

아니오

아니오

LOB형 지원(TEXT등 제외) 여부

열 개수 제한

아니오

아니오(1023 개)

집계 값 포함 여부

아니오

크기 제한

아니오

아니오

데이터 변경 시 worktable사용 여부

아니오

소스 테이블에 대한 키 존재 여부

아니오

예(RID 또는 클러스터키값)

인덱싱 된 뷰는 기존의 넌 클러스터 인덱스 또는 포괄 열 인덱스와는 다르게 원본 테이블에 대해 연결된 키가 존재 하지 않습니다. 스키마 바인딩 뷰 생성 후 만들게 되는 고유한 클러스터 인덱스 키를 이용하여 소스 테이블의 변경된 내역을 반영하게 됩니다. 이러한 소스테이블에 종속된 키가 존재 하지 않아 데이터 갱신 작업 시 wokrtable을 이용하여 실 테이블을 갱신 작업에 대해 저장 후 이 내용을 바탕으로 인덱싱 된 뷰에서 처리하게 됩니다.

아래는 인덱싱 된 뷰가 존재하는 테이블에 대해서 특정 값을 업데이트 쿼리의 실행 계획이며, Table Spool연산자가 추가 및 출력목록에 인덱싱 된 뷰의 클러스터 키와 변경된 컬럼을 확인 할 수 있습니다.



5. 인덱싱 된 뷰 생성시 필요 조건 및 주의 사항.

인덱싱 된 뷰를 만들기 위해서는 몇 가지 주의 사항이 있습니다. 주의 사항 및 지켜줘야 하는 부분에 대해서 잠시 살펴 보도록 하겠습니다.

주의 사항

  1. CREATE VIEW 문이 실행될 때는 ANSI_NULLS 및 QUOTED_IDENTIFIER 옵션이 ON
  2. 뷰를 생성시 참조 되는 원본 테이블은 생성시 ANSI_NULLS옵션이 ON
  3. 테이블이 아닌 다른 뷰를 참조해서는 안됨.
  4. 뷰 생성시 SCHEMABINDING옵션으로 생성
  5. 테이블 및 사용자 함수에 대해서는 스키마명.객체명 사용.
  6. 뷰의 식에서 참조되는 모든 함수는 결정적 함수.
  7. GROUP BY를 사용하는 경우 SELECT List절 에 COUNT가 아닌 COUNT_BIG을 사용(테스트1 참조)
  8. Select List절에 * 같이 사용할 수 없고 컬럼 명을 명시적으로 사용

보다 자세한 내용은 BOL을 참조(인덱싱 된 뷰 만들기)
ms-help://MS.SQLCC.v9/MS.SQLSVR.v9.ko/udb9/html/f86dd29f-52dd-44a9-91ac-1eb305c1ca8d.htm

뷰 생성 후 해당 옵션으로 생성된 것인지 확인 하기 위해서 OBJECTPROPERTY함수를 사용하여 확인

SELECT
     OBJECTPROPERTY ( OBJECT_ID ( 'Sales.vOrders' , 'view' ) , 'ExecIsAnsiNullsOn' )
    ,OBJECTPROPERTY ( OBJECT_ID ( 'Sales.vOrders' , 'view' ) , 'ExecIsQuotedIdentOn' )
  1 = True
  0 = False


[테스트 1 - GROUP BY절을 사용하고 COUNT_BIG함수를 사용하지 않을 경우 오류 메시지]

SET NUMERIC_ROUNDABORT OFF ;
SET ANSI_PADDING, ANSI_WARNINGS, CONCAT_NULL_YIELDS_NULL, ARITHABORT ,
   QUOTED_IDENTIFIER, ANSI_NULLS ON ;

GO
--스키마 바인딩 뷰 생성
IF OBJECT_ID ('Sales.vOrders', 'view' ) IS NOT NULL
DROP VIEW Sales .vOrders ;
GO
CREATE VIEW Sales .vOrders
WITH SCHEMABINDING
AS
   SELECT SUM (UnitPrice *OrderQty *(1.00 -UnitPriceDiscount )) AS Revenue ,
      OrderDate , ProductID , COUNT(*) AS COUNT
   FROM Sales .SalesOrderDetail AS od ,Sales .SalesOrderHeader AS o
   WHERE od .SalesOrderID = o .SalesOrderID
   GROUP BY OrderDate , ProductID ;
GO
--뷰에 고유한 클러스터 인덱스 생성
CREATE UNIQUE CLUSTERED INDEX IDX_V1
   ON Sales .vOrders (OrderDate , ProductID );
GO

' 메시지 10136, 수준16, 상태1, 줄1
뷰 "AdventureWorks.Sales.vOrders"이 (가) 집계 COUNT 를사용하므로이뷰에인덱스을 (를) 만들수없습니다.
대신 COUNT_BIG 을사용하십시오 .'


6. 하나의 테이블에 클러스터 와 힙 두 가지 모두 만들 수 없을까?

그렇다면 두 가지를 모두 가져 서비스에 좋은 영향을 줄 수 있을까?

아시다시피 SQL Server에는 두 가지 데이터 영역이 존재 합니다. 클러스터 인덱스가 있는 테이블은 클러스터 데이터 영역이라고 부르겠으며, 클러스터 인덱스가 존재 하지 않는 테이블은 힙 데이터 영역이라고 부르겠습니다.
클러스터 데이터 영역은 클러스터 키로 논리적으로 정열이 된 데이터를 말하며 힙 영역은 특정 키로 정열 되지 않은 영역으로 말할 수 있습니다.

SQL Server에는 어떠한 영역이 유리하다고 보기는 매우 어려우며, 두 개 모두 각각의 환경에서 주는 이점이 있습니다.
클러스터 영역이라면 클러스터 키로 소스데이터가 정열 되었기에 집계 및 정열 등에 좋은 성능을 가질 수 있습니다. 그러나 다른 넌클러스터 인덱스는 소스데이터를 접근 하기 위한 포인터로서 클러스터 키를 가지고 있어, 넌클러스터 영역에서 소스데이터 영역을 접근 하기 위해서는 클러스터 인덱스 루트 레벨부터 데이터를 찾아가야 합니다. 단순히 한 두 개의 행을 = 비교로 가지고 와야 한다면 넌 클러스터 인덱스에서 클러스터 영역으로 데이터를 찾는 방식이 성능상 불리 할 수 도 있습니다.

하지만 이런 점은 SQL Server가 발전되면서 변화된 모습이며 이전의 SQL Server에서는 클러스터 인덱스가 생성된 테이블이라고 하더라도 넌클러스터 인덱스가 소스데이터를 참조하기 위해서 RID를 가지고 있었다고 합니다. 소스데이터의 이동이 발생 하면 RID가 변경 되기에 속하는 넌클러스터 인덱스에 대한 RID값도 변해야 합니다. 페이지 분할 등의 작업이 이루어 진다면 넌클러스터 인덱스의 RID를 변경 하는 작업도 SQLServer에는 많은 영향을 주었을 것 이며, 이러한 문제를 해결 하기 위해 RID대신 클러스터 키 값으로 변경 된 것으로 생각 됩니다. 이러한 변화로 인해 많은 장점을 주고 있지만, 모든 면에서 장점을 주는 것은 아이며, 변화로 인해 클러스터 영역인 경우 넌클러스터에서 접근 시 꼭 클러스터인덱스 루트레벨부터 다시 찾아야 하는 단점이 생겼습니다.

이런 단점을 보완 할 수 있는 방법은 없을까요? “클러스터 영역과 힙 영역을 모두 가지고 있으면 단점이 보완 될 수 있지 않을까?” 라는 생각을 해보았습니다. 물론 데이터의 중복으로 인해 데이터 갱신작업에는 기존보다 오버헤드가 있어 성능에 많은 영향을 줄 수 있습니다. 그러나 특정 서비스인 경우에는 클러스터 영역과 힙 영역을 모두 가짐으로써 좋은 영향을 줄 수 있다고 생각합니다. 하지만 많은 서비스는 이점을 줄 수 없을 것입니다. 이러한 방법이 서비스에 어떠한 영향을 준다 보다는 “이렇게도 가능 하지 않을까” 라는 생각을 해보며 작성 하였습니다.

구현을 위해서 먼저 힙 테이블을 만듭니다. 그리고 힙 테이블에 인덱싱 된 뷰를 생성합니다. 인덱싱 된 뷰를 만들기 위해서는 스키마 바인딩 뷰에 고유한 클러스터 인덱스를 처음에 만들어야 합니다. 그러기에 인덱싱 된 뷰 는 클러스터 인덱스를 가지는 영역이 되며, 소스 테이블에는 힙 상태를 그대로 유지하게 됩니다. 소스테이블에 넌 클러스터 인덱스를 생성 하게 되면 RID를 가지는 넌클러스터 인덱스, 인덱싱 된 뷰에 넌클러스터 인덱스를 만들면 클러스터 키를 포인터로 가지는 넌클러스터 인덱스가 됩니다.

개인적인 생각으로는 특정 환경에서는 충분히 활용 가치가 있을 수 있다고 생각됩니다. 위에서 설명한 하나의 테이블에 두 개의 영역으로 나눈 후 각각의 영역에 동일한 컬럼을 가지고 넌 클러스터 인덱스를 만들어서 테스트를 해보았습니다.
아래는 SQL Server 2005에 있는 샘플데이터베이스인 AdventureWorks에 있는 테이블을 가지고 간단히 구현을 하였습니다. 마지막에 두 가지 쿼리는 몇 개 되지 않는 행을 반환 받는 쿼리입니다.

USE AdventureWorks
GO
-- 샘플 테이블을 생성
SELECT * INTO DBO .SalesOrderDetail FROM Sales .SalesOrderDetail;

--스키마 바인딩 뷰를 생성
IF OBJECT_ID ('DBO.VSalesOrderDetail', 'view') IS NOT NULL
DROP VIEW DBO .VSalesOrderDetail ;
GO
CREATE VIEW DBO . VSalesOrderDetail
WITH SCHEMABINDING
AS
SELECT
SalesOrderID ,SalesOrderDetailID ,CarrierTrackingNumber
,OrderQty ,ProductID ,SpecialOfferID ,UnitPrice ,UnitPriceDiscount
,LineTotal ,rowguid ,ModifiedDate
FROM DBO .SalesOrderDetail

--뷰에 고유한 클러스터 인덱스를 생성하여 인덱싱 된 뷰로 생성
Create Unique Clustered Index cl_VSalesOrderDetail on VSalesOrderDetail (SalesOrderDetailID );

--인덱싱 된 뷰, 힙 테이블에 SalesOrderID를 넌 클러스터 인덱스로 만들자
CreateIndex IX_VSalesOrderDetail on VSalesOrderDetail (SalesOrderID );
CreateIndex IX_SalesOrderDetail on SalesOrderDetail (SalesOrderID );

--IO양을 확인하기 위해서.
SET STATISTICS IO ON
select * from SalesOrderDetail where SalesOrderID = 55277;
테이블 'VSalesOrderDetail'. 검색수1 , 논리적읽기수86 , 물리적읽기수0 , 미리읽기수0 , LOB 논리적읽기수0 , LOB 물리적읽기수0 , LOB 미리읽기수0.



select*from SalesOrderDetail with(index(2 ))where SalesOrderID = 55277;
테이블'SalesOrderDetail'. 검색수1 , 논리적읽기수30 , 물리적읽기수0 , 미리읽기수0 , LOB 논리적읽기수0 , LOB 물리적읽기수0 , LOB 미리읽기수0.



SET STATISTICS IO OFF


7. 마무리

너무 단편적인 예를 들어 설명한 것은 아닌지 걱정이 앞섭니다. 항상 강조하지만 서비스에 대한 정확한 이해를 바탕으로 한 고려가 필요합니다. 잘못 사용되면 성능적 많은 문제가 발생 할 수 있기 때문입니다.
굳이 제가 하고 싶은 말을 두 가지로 정리해 본다면 아래와 같습니다.
“다른 것처럼 보이는 기능이지만 비슷한 기능으로 구현 할 수 없을까?”
”인덱스에 대해 조금 더 생각을 해보자!”

이전 아티클에서 다음 아티클은 괜찮은 아티클로 뵙겠다고 하였는데, 이번에도 그 약속을 지키지 못한 것 같습니다. 다음에는 그 약속을 꼭 지키도록 하겠습니다.^^;;

아참!! 그리고 마지막으로 질문 하나를 하겠습니다.
“인덱싱 된 뷰를 이용하여 넌클러스터 인덱스 기능을 대처 할 수 있지 않을까요?”

감사합니다.

혹시 이 글에 대한 궁금증 및 잘못된 내용이 있다면 위에 있는 제 메일로 보내주시길 바랍니다.

728x90
[master 데이터베이스 복구]

master 데이터베이스가 손상되면 SQL Server는 서비스 시작이 실패하며 따라서 복구 작업을
포함한 어떤 작업도 할 수 없습니다. 이렇게 master 데이터베이스가 손상되었다면 일단 SQL Server
서비스가 시작할수 있도록 Rebuildm.exe유틸리티를 사용해서 모든 시스템 데이터베이스를 초기화하거나
특정 시점의 master.mdf파일과 mastlog.ldf파일의 본사본을 사용하여 서비스를 시작할 수 있습니다.
master데이터베이스를 복구하고자 하는 경우에는 SQL server서비스를 반드시 단일 사용자 모드로 시작하여야 하며
현재 설치되어 있는 SQL Server의 빌드번호와 백업된 master데이터베이스의 빌드번호가 일치해야 한다.
빌드번호는 서비스팩업데이트나 보안패치 적용시 변경됩니다.

--------------------------------------------------------------
|손상된db | 손상시 문제점               | 복구를 위한 작업    |
--------------------------------------------------------------
|master   | sql server 서비스 시작 실패 | rebuildm.exe수행 후 |
|         |                             | 백업을 사용한 복구  |
---------------------------------------------------------------


<보안패치의 빌드번호 구성>
빌드넘버 : ProductName-KBArticleNumber-X.YY.ZZZZ-LangName.exe
1) ProductName은 제품 이름과 버전 정보입니다.
2) KBArticleNumber는 관련된 Microsoft기술 자료 문서의 ID입니다.
3) X는 주 버전 번호를 나타냅니다.
4) YY는 두 자리 수의 부 버전 번호를 나타냅니다.
5) ZZZZ는 보안 패치 등 핫픽스 번호를 나타냅니다.(빌드번호)
6) LangName은 보안패치가 현지화된 언어의 세 자리 약어입니다.
   예를 들어, KOR(한국어), ENU(영어), JPN(일본)등이 있습니다.
* 현재 master DB 빌드번호 체크
 select @@version
* backup된 master.bak 빌드번호 체크
restore headeronly
   from disk='c:\bak\master.bak'
위 두개의 dB의 빌드번호가 일치되어야 restore가 가능하다.
 
=========================================================================================
case1. 현재 설치되어 있는 SQL Server빌드 번호보다 master 데이터베이스 백업의 빌드 번호가 낮은경우
       - 이와 같은 경우는 먼저 SQL Server를 재설치하고 master데이터베이스를 복구합니다.
       1) 백업된 시스템 데이터베이스 빌드 번호와 동일한 서비스 팩과 보안패치를 준비합니다.
       2) 복구하고자 하는 인스턴스를 프로그램 추가/삭제 를 통해 삭제합니다.
       3) sql2000 및 서비스팩, 보안패치를 순서에 따라 설치합니다.
       4) sql server서비스를 단일 사용자 모드에서 시작하고 master데이터베이스를 복구합니다.
# 삭제가 안될경우에는
http://support.microsfot.com/dedault.aspx?scid=kb;ko;290991의 문서를 참고하여
  수동으로 제거하기 바랍니다.
=========================================================================================
case2. 현재 설치되어 있는 SQL Server 빌드번호와 시스템 데이터베이스 백업의 빌드번호가 일치하는 경우
 
       1) Rebuildm.exe유틸리티를 실행합니다.
          ㄱ) 방법1 : 실행창에서 rebuildm.exe를 입력한 뒤 엔터키를 누릅니다.
   ㄴ) 방법2 : c:\program files\microsoft sqlserver\80\tools\binn" 폴더로 이동하여 rebuildm.exe
       를 더블클릭 합니다.
       2) 버튼을 눌러서 초기 시스템 데이터베이스 파일(mssqlCD가 필요)이 들어 있는 폴더의 경로를 지정합니다.
       3) 데이터 정렬은 sql server설치시의 정렬과 동일한지 확인합니다. 기본적으로 운영체제의 언어에 따라
          설정됩니다. 다시 작성 버튼을 눌러 재구성을 진행합니다.
       4) 시스템 데이터베이스의 재구성이 완료되면 sql server 서비스를 종료합니다. 명령프롬프트를 사용해서
          sql server서비스를 단일 사용자 모드로 시작합니다.
   net start mssqlserver /m
       5) 쿼리분석기로 연결한 뒤 master 데이터베이스의 백업을 사용하여 복구를 진행합니다.
          복구예) restore database master
                     from disk='c:\backup\masterFULL.bak'
# sqlCD에 있는 Rebuildm.exe파일을 사용할때는 x86\DATA폴더를 하드디스크로 복사한 뒤 읽기 전용 속성을
  해제하고 사용해야 합니다.
[참조문서]
http://support.microsoft.com/default.aspx?scid=kb;en-us;273572
=========================================================================================

+ Recent posts