2021년 11월 14일 일요일

오라클 인덱스, Bitmap 인덱스(Bitmap Indexes)

 

오라클 인덱스, Bitmap 인덱스(Bitmap Indexes)


테이블의 컬러 컬럼이 BLUE, GREEN, RED, BLACK 4가지의 값을 가진다고 했을 때, 컬럼에 유일한 값이 몇개 되지 않으면 컬럼의 카디널리티cardinality가 낮다고 합니다. 이러한 컬럼에 적합한 인덱스가 Bitmap 인덱스bitmap indexes 입니다.


정보저장의 최소단위인 비트를 이용하여 칼럼 값을 간결하게 저장하고 이를 이용하여 자동으로 ROWID를 생성하는 구조를 가지며 성별 컬럼처럼 분포도가 나쁜 칼럼, NOT, OR를 사용하는 경우 탁월한 성능을 냅니다.



1-mXbrMjjZzi-nEFoVeXJhWAjhwgtvc34MZ1q9y1

[그림 13.2 Bitmap 인덱스 내부구조]

CREATE BITMAP INDEX 명령으로 Bitmap 인덱스를 생성하면 비트리 인덱스처럼 트리구조를 만들고 리프블럭에 값들을 비트로 변환하여 저장 합니다. B*Tree 인덱스의 리프 블록(Leaf Block)은 INDEX KEY VALUE와 ROWID 로 구성이 되어 있지만 Bitmap 인덱스는 START ROWID ~ END ROWID로 압축해서 저장하고 컬럼값 역시 ‘1’ 이라는 비트로 저장해서 원본 데이터의 ROWID를 계산합니다.


Bitmap 인덱스를 생성하고자 하는 테이블 스캔을 한 후 Bitmap Index Generator에 의해 칼럼 값(비트형태의 ‘1’로 저장), 시작 ROWID, 끝 ROWID , Bitmap을 갖는 인덱스 엔트리를 생성 합니다. 생성된 Bitmap들을 B-tree구조에 넣기 쉽도록 KEY값과 START ROWID 순으로 정렬하며 마지막 단계에서는 정렬된 인덱스 엔트리들을 단순히 B*Tree 구조로 삽입 합니다.

.

인덱스를 데이터의 존재 여부를 0 or 1로 표시하는 비트 단위로 저장하고 B*Tree 인덱스 한계를 극복하여 대량의 자료 조회에 적합한 구조이지만 잦은 DML이 발생되는 곳은 리프 블록의 갱신으로 인해 부적합 합니다. 


하나의 인덱스 값을 수정하면 그 인덱스 값을 가지는 모든 행(로우, ROW)에 락lock을 겁니다. 즉 하나의 인덱스 값으로 테이블상의 여러 개의 행을 표현하기 때문에 INSERT, UPDATE, DELETE 등을 사용하는 경우 오라클 LOCK 메커니즘인 행 단위 락(ROW LEVEL LOCKING)을 지원할 수 없습니다.


B*Tree 인덱스가 NULL값을 보관하지 않는 것과는 달리 Bitmap 인덱스는 NULL값에 대한 BIT값을 저장하여 비트리 인덱스의 NULL문제를 해결했으며 AND, OR 연산시 비트연산을 빠르게 수행하여 탁월한 성능을 보입니다.


[기본형식]

CREATE  BITMAP INDEX   index_name 

ON   table_name (Column|Expr[,Column|Expr]...)


실습에서 사용되는 MYEMP, MYDEPT 테이블은 0.환경설정의 0.4 실습 데이터 설치편을 참조하여 생성 바랍니다.


MYEMP 테이블의 deptno 컬럼에 대해 인덱스를 생성한 후 일단 안보이도록 하고 WHERE절에 deptno 컬럼을 사용하여 조회해 봅니다. 다시 deptno 컬럼의 인덱스를 보이도록 한 후 조회해 봐서 성능 차이가 나는지를 확인해 봅니다.

실습


MYEMP 테이블의 deptno 컬럼에 오름차순 인덱스를 생성하고 안보이도록 숨깁니다. 

숨기게 되면 인덱스는 존재하지만 오라클에서 해당 인덱스를 사용하지 않습니다.


deptno 컬럼의 인덱스를 생성한 후 INVISIBLE 상태로 변경 합니다. 컬럼에 대한 인덱스가 이미 있다면 DROP INDEX로 삭제 후 다시 생성하세요.


CREATE INDEX IDX_MYEMP_DEPTNO ON MYEMP(DEPTNO);


<실행결과>

Index IDX_MYEMP_DEPTNO이(가) 생성되었습니다.


ALTER INDEX IDX_MYEMP_DEPTNO INVISIBLE;


<실행결과>

Index IDX_MYEMP_DEPTNO이(가) 변경되었습니다.


현재 deptno 컬럼의 인덱스는 생성되어 있지만 보이지 않으므로 오라클에서 사용하지 않습니다.


WHERE절에 deptno 컬럼을 사용하여 조회해 보겠습니다.


MYEMP 테이블에서 deptno 값이 1 또는 3인 데이터가 몇 건 있는지 확인하세요.


SELECT COUNT(*) FROM MYEMP WHERE DEPTNO IN (1,3);


<실행결과>

 

COUNT(*)

1

10000000


<실행계획>

4_3sBAhVo7Y3CaLtGKL4oLlROcIbWbxKabdlSBR8


실행시간은 필자의 노트북 기준으로 대략1 2초 정도 걸렸으며,  deptno 컬럼 인덱스를 경유하지 않고 MYEMP 테이블 전체를 FULL SCAN하여 deptno 값이 1 인것과 3인 것을 필터링 하여 찾았고 집합함수인 COUNT문 때문에 AGGREGATE 옵션이 실행되어 결과를 한건으로 보였습니다. AGGREGATE 옵션은 주로 GROUP BY와 같이 쓰이는 SUM, COUNT등이 출현하는 경우 한건으로 추출되도록 하는 옵션 입니다.


deptno 컬럼의 인덱스를 보이도록 한 후 다시 쿼리해 보겠습니다.


deptno 컬럼에 생성되어 있는 인덱스를 보이도록 하고 다시 SELECT문을 실행하세요.


ALTER INDEX IDX_MYEMP_DEPTNO VISIBLE;

SELECT COUNT(*) FROM MYEMP WHERE DEPTNO IN (1,3);


<실행결과>

 

COUNT(*)

1

10000000


<실행계획>

8WGiw82xtqSks9arfaqclk92PaT6PcmOvgyexdYR


IDX_MYEMP_DEPTNO 인덱스를 빠르게 멀티 블럭으로 읽어 들이면서 전체 인덱스를 읽는 FAST FULL SCAN을 하면서 deptno 컬럼 값이 1 또는 3인 데이터를 찾고 AGGREGATE 옵션에 의해 한건으로 COUNT의 결과를 보입니다. 비용cost은 이전에 인덱스를 숨기고 실행한 것보다 적게 들었지만 여전히 1.7초 내외의 수행시간이 걸렸습니다. 인덱스를 경유 했지만 여전히 쿼리 성능이 만족스럽지 않은 상황 입니다. 비트리 인덱스는 값이 고유할수록, 값의 분포도가 좋을수록 좋은 성능을 내는 인덱스인데 deptno 컬럼은 중복되는 값이 많아서 인덱스를 경유해도 성능이 급진적으로 개선이 되지는 않습니다.

CARDINALITY 항목은 해당 오퍼레이션에서 추출되는 예상되는 건수를 표시 하는데 테이블에 대한 통계정보가 부정확하면 실제 건수와 차이가 나니 정확한 실행 계획의 생성을 위해 주기적으로 ANALYZE TABLE 명령으로 테이블 통계정보를 생성하는 것이 좋습니다. 이 값이 부정확하면 오라클 옵티마이저oracle optimizer가 실제 인덱스를 경유하는 것보다 FULL TABLE SCAN 하는 것이 효율적인데도 인덱스를 경유하게 하고, 해시조인이 효율적인데도 중첩루프 조인을 하도록 해서 쿼리 성능을 나쁘게 하는 실행계획을 만들어 낼 수 있습니다.

이번에는 먼저 생성한 deptno 컬럼의 인덱스를 삭제하고 Bitmap 인덱스를 생성하여 동일한 쿼리를 실행하고 실행시간 및 실행계획을 확인해 보겠습니다.

실습


IDX_MYEMP_DEPTNO 인덱스를 삭제하고 Bitmap 인덱스를 생성합니다.


deptno 컬럼의 인덱스(IDX_MYEMP_DEPTNO)를 삭제한 후 BIDX_MYEMP_DEPTNO 라는 이름으로 Bitmap 인덱스를 생성하세요.


DROP INDEX IDX_MYEMP_DEPTNO;

CREATE BITMAP INDEX BIDX_MYEMP_DEPTNO ON MYEMP(DEPTNO);


앞에서 작성한 SELECT 쿼리를 다시 실행하고 실행계획 및 성능을 확인하겠습니다.


deptno 값이 1 또는 3인 데이터가 몇 건인지 확인하세요.


SELECT COUNT(*) FROM MYEMP WHERE DEPTNO IN (1,3);


<실행결과>

 

COUNT(*)

1

10000000


<실행계획>

8z5qmade4Yr7DPA_d4jKHMAwH6iu_VfiAEY649XN


실행시간은 약 0.022초 정도 소요되었으며 생성한 Bitmap 인덱스를 경유하였고 비용도 많이 줄어 성능도 향상 되었음을 확인할 수 있습니다. 이렇게 Bitmap 인덱스는 deptno 컬럼처럼 값의 분포도가 좋지 않은 컬럼의 조회 용도로 사용하기에 적합한 인덱스 입니다.

#비트맵인덱스, #bitmap인덱스, #오라클인덱스, #인덱스, #INDEX​

오라클 인덱스, 비트리 인덱스(B*Tree Indexes)

 

오라클 인덱스, 비트리 인덱스(B*Tree Indexes)


B*Tree 인덱스는 오라클의 대표적인 형태의 인덱스로 CREATE INDEX로 별다른 옵션없이 생성되는 인덱스는 B*Tree 인덱스 입니다. 인덱스 구조가 자료구조에서 배운 TREE와 비슷해서 균형잡힌 트리 B*TREE(Balanced Tree)라고 합니다. 

다음은 EMP 테이블에서 사원이름 컬럼을 내림차순(Z -> A 순서)으로 정렬했을 때의 B*Tree 인덱스의 내부 구조 입니다.

yaNbo3Hp-j878TTfps3aVGc9gSopYarsz24xtKo5

[그림 13.1 B*Tree 인덱스 내부구조]

B*Tree 인덱스의 리프 블록leaf blocks에는 실제 인덱스 데이터와 원본 테이블 행의 ROWID가 있는데 각 항목은 인덱스 데이터 + ROWID로 정렬되어 있고, 브랜치 블록branch blocks은 리프 블록을 찾아가기 위한 인덱스 데이터를 가지고 있습니다.  모든 리프 블록이 동일한 깊이에 있기 때문에 B-Tree 인덱스는 균형을 이루고 각각의 행들을 검색하는데는 거의 동일한 시간이 소요됩니다.

리프 블록의 ROWID는 인덱스 경유 후 원본 테이블의 데이터에 접근해서 값을 읽어야 하는 경우 이용되며, ROWID에는 object #, file #, block # 및 row #이 포함됩니다. 여기서 file #은 물리적 데이터베이스 파일, block #은 데이터가있는 블록, row #은 블록 내의 테이블 행에 대한 포인터입니다.


리프 블록은 적절한 다음 및 이전 리프 블록을 가리 킵니다. 이렇게하면 리프 블록 자체가 디스크 전체에 저장 되더라도 인덱스에 논리적 순서가 부여되고 이를 통해 범위 스캔, 내림/오름차순 검색 등을 수행 할 수 있습니다.

테이블의 기본키primary key와 같이 고유 인덱스unique indexes로 생성되면 데이터 값당 하나의 ROWID를 가지고 있고 값이 유일하므로 값에 따라서 정렬됩니다. 하지만 고유 인덱스가 아닌 경우에는 ROWID가 정렬 된 순서로 키에 포함되므로 인덱스 키값 및 ROWID를 기준으로 정렬됩니다. 사원테이블에서 이름이 동일한 사원이 있을 경우 ROWID를 기준으로 정렬됩니다.

만약 ename 컬럼에 NULL인 데이터가 있다면 ename 오름차순 정렬시 맨 뒤에 위치하며 NULL을 포함하는 값은 인덱싱 되지 않습니다. 그래서 WHERE ename IS NULL을 사용하면 ename 인덱스를 경유하지 않으니 주의해야 합니다.

B*Tree 인덱스는 데이터의 일부 작은 하위집합만 선택하는 경우에 성능이 좋습니다. 일반적으로 최대 5~10% 정도인데,  테이블 데이터의 10% 이상을 인덱스를 경유하여 추출하는 경우 원본 테이블 FULL SCAN 보다 성능이 안좋을수 있습니다. 

고유한 값이 많을 수록, 컬럼의 값이 다양할 수록 성능상 좋습니다. 예를들어 성별 컬럼(M or N)이 있다면 이값의 분포도는 대략 50% 일것 입니다. 이런 경우 B*Tree 인덱스는 성능상 큰 도움이 못될 수도 있으며 이때는 Bitmap 인덱스bitmap indexes를 사용하는 것이 효율적 입니다. 테이블의 크기가 작은 경우 기본키primary key를 제외하고는 굳이 별도의 인덱스를 구성할 필요는 없습니다.

만약 여러개의 컬럼으로 B*Tree 인덱스를 구성한다면 첫번째 열, 선행 컬럼은 가장 자주 액세스 되며 유일한 값을 가지는 컬럼을 사용하는 것이 좋습니다.

생성된 B*Tree 인덱스는 오라클에서 자동으로 유지 관리하는데 인덱싱 된 테이블에 값을 삽입/수정 할 때 약간의 오버헤드가 있습니다. 입력 또는 수정이 빈번한 테이블에 많은 인덱스가 있는 경우 문제가 될 수 있으니 주의해야 합니다.

 

또한 B*Tree 인덱스는 실제 컬럼 값을 인덱스 영역에 정렬해서 보관 하므로 데이터의 중복이 생기고 칼럼 값의 선택도/분포도(전체 데이터중 5~10% 정도 선택, 값이 다양하고 유일할 수 수록)가 좋아야 최적의 성능을 내는 구조 입니다. OR 처럼 범위를 넓히면서 검색하는 경우 인덱스를 사용하지만 제 성능을 내기 어려우며 특히 NOT을 사용하는 부정연산에는 취약 합니다.

 

#비트리인덱스, #비트리인덱스란, #btree, #B*Tree, #B*Tree인덱스, #인덱스, #오라클인덱스, #INDEX, #ORACLE

인덱스 재구성 및 삭제(Index Rebuild, Drop), 인덱스 숨기기(Index Invisible)

 

인덱스 재구성 및 삭제(Index Rebuild, Drop), 인덱스 숨기기(Index Invisible)


12.5. 인덱스 재구성 및 삭제


데이터 삽입, 삭제, 수정등이 일어나면 오라클 내부적으로 원본 데이터뿐 아니라 인덱스도 갱신을 시켜줍니다. 이때 특정한 부분에 값이 집중되어 입력 되는 경우등이 있다면 인덱스는 최초 인덱스를 생성할 때의 균형잡힌 트리(Balanced Tree)의 모양이 아닌 트리가 한쪽으로 기울어 지는 현상이 생길 수 있습니다. 이러한 경우 효율적인 인덱스 관리를 위해 인덱스를 재구성 또는 삭제 후 재생성 해야 합니다.


인덱스를 재구성하고 삭제하는 실습을 해보겠습니다.


실습


인덱스를 재구성 합니다.


ename 컬럼에 생성된 IDX_EMP_ENAME 인덱스를 재구성 하세요.


ALTER INDEX idx_emp_ename REBUILD;


[실행결과]

Index IDX_EMP_ENAME이(가) 변경되었습니다.


인덱스를 삭제 합니다.


ename 컬럼에 생성된 IDX_EMP_ENAME 인덱스를 삭제 하세요.


DROP INDEX IDX_EMP_ENAME;


[실행결과]

Index IDX_EMP_ENAME이(가) 삭제되었습니다.



12.6. 인덱스 숨기기


쿼리작업을 할 때 가끔은 인덱스가 없다면 SQL문의 실행계획이 어떻게 될까? 고민 하면서 인덱스를 DROP 하고 실행 계획을 본 후 다시 INDEX를 생성하여 실행 계획을 본 적이 있을 것 입니다. 


적은 량의 데이터를 가진 테이블이라면 문제가 아닐텐데 많은 양의 데이터가 있는 테이블 이라면 삭제하고 생성하는데 부담이 있습니다. 이때 사용할 수 있는 것이 Oracle 11g에서 새로 소개된 INVISIBLE INDEX인데, 인덱스를 INVISIBLE 상태로 만들어서 오라클에서 명시적으로 사용하지 말라고 지시를 할 수 있습니다.


또한 대용량의 데이터를 테이블에 입력하는 경우 원본 테이블에 데이터가 입력되면 오라클이 인덱스를 자동으로 갱신하는데 이러한 작업은 대용량 데이터를 입력할 때 성능 저하의 원인이 될 수 있습니다. 이때 인덱스를 잠시 안보이도록 숨겨두고 작업이 끝난 후 보이도록 하여 재구성하는 것이 효율적 입니다.


ename 컬럼에 인덱스가 있을 때 쿼리를 실행 했을 때와 INVISIBLE 상태로 만든 후 쿼리를 실행했을 때의 실행 계획을 비교해 보겠습니다. 또한 INVISIBLE 상태로 되어 있는 인덱스를 딕셔너리 뷰를 통해 확인해 보겠습니다.



실습


실습을 위한 인덱스를 생성 합니다.


EMP 테이블의 ename 컬럼에 오름차순 인덱스를 생성 하세요.


CREATE INDEX IDX_EMP_ENAME ON EMP(ENAME);


[실행결과]

Index IDX_EMP_ENAME이(가) 생성되었습니다.



WHERE절에 ename 컬럼을 출현시켜 인덱스를 경유하는지 확인해 보겠습니다.


EMP 테이블에서 ‘SMITH’ 사원의 사번, 이름을 조회하세요.


SELECT EMPNO, ENAME FROM EMP WHERE ENAME = 'SMITH';


[실행결과]

 

EMPNO

ENAME

1

7369

SMITH


[실행계획]

OSycxMb3jYCDmP8WjmEW1Nn5-VUyXDBj0dADr7up


ename 컬럼에 생성된  IDX_EMP_ENAME 인덱스를 경유함을 확인할 수 있습니다.



인덱스를 사용하지 않도록 하기 위해 숨겨 봅니다.


ename 컬럼에 생성된 인덱스를 숨기세요.


ALTER INDEX IDX_EMP_ENAME INVISIBLE;


[실행결과]

Index IDX_EMP_ENAME이(가) 변경되었습니다.



WHERE절에 ename 컬럼을 출현시켜 인덱스를 경유하는지 확인해 보겠습니다.


EMP 테이블에서 ‘SMITH’ 사원의 사번, 이름을 조회하세요.


SELECT EMPNO, ENAME FROM EMP WHERE ENAME = 'SMITH';


[실행결과]

 

EMPNO

ENAME

1

7369

SMITH


[실행계획]

-sKdLnS1m3Uz_5D5TtTqn-NkWRTUpyxROrM5SRFA


ename 컬럼에 생성된 인덱스가 숨겨져 있으므로 경유하지 않았습니다.



INVISIBLE 상태에 있는 IDX_EMP_ENAME 인덱스를 딕셔너리 뷰에서 확인해 보겠습니다.


IDX_EMP_ENAME 인덱스가 INVISIBLE 상태에 있는것을 USER_INDEXES 뷰에서 확인하세요.


SELECT INDEX_NAME, VISIBILITY FROM USER_INDEXES

WHERE INDEX_NAME = 'IDX_EMP_ENAME';


[실행결과]

 

INDEX_NAME

VISIBILITY

1

IDX_EMP_ENAME

INVISIBLE


 #인덱스삭제, #인덱스Rebuild, #dropindx, #alterindex, #오라클인덱스, #인덱스, #인덱스강좌

(C#교육동영상)C# ADO.NET 실습 ODP.NET/ODAC 설치 오라클 함수 호출 실습, C#학원, WPF학원, 닷넷학원, 자바학원

  (C#교육동영상)C# ADO.NET 실습  ODP.NET/ODAC 설치  오라클 함수 호출 실습, C#학원, WPF학원, 닷넷학원, 자바학원 https://www.youtube.com/watch?v=qIPU85yAlzc&list=PLxU-i...