Skip to content

Commit

Permalink
[5주차] 홍성주 3장 정리 (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
penrose15 authored May 4, 2024
1 parent e249c50 commit d445040
Show file tree
Hide file tree
Showing 2 changed files with 239 additions and 4 deletions.
8 changes: 4 additions & 4 deletions study/week_04/홍성주/홍성주.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ ROWID는 인덱스 스캔시 테이블 레코드를 찾아가기 위한 주소

ROWID는 테이블 레코드를 찾아가기 위한 논리적 주소 정보를 담고 있다.

-> C언어의 포인터와는 전혀 다른 개념이다.
- C언어의 포인터와는 전혀 다른 개념이다.

-> 인덱스로 테이블 블록 액서스 시 ROWID를 분해하여 DBA 정보를 얻어야 한다.
- 인덱스로 테이블 블록 액서스 시 ROWID를 분해하여 DBA 정보를 얻어야 한다.

-> ROWID가 가리키는 테이블블록을 버퍼캐시에서 먼저 찾고 없으면 디스크에서 블록을 읽어온다.
- ROWID가 가리키는 테이블블록을 버퍼캐시에서 먼저 찾고 없으면 디스크에서 블록을 읽어온다.

ROWID를 활용하는 과정은 생각보다 고비용이다.

Expand Down Expand Up @@ -91,7 +91,7 @@ create index emp_x01 on emp (deptno) include (sal)
데이터가 정렬된 상태로 모여있어 Between, 부등호 조건으로 넓은 범위를 읽을 때 유리하다.


```
```sql
create table index_org_t (a number, b varchar(10), constraint index_org_t_pk primary key (a) )
organization index;
```
Expand Down
235 changes: 235 additions & 0 deletions study/week_05/홍성주/홍성주.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
# 3.3 인덱스 스캔 효율화

인덱스 선행 칼럼에 조건절이 없거나 =조건이 아니라면 인덱스 스캔과정에 비효율이 생긴다.

## 액서스 조건과 필터 조건

인덱스를 스캔하는 단계에서 처리하는 조건절은 액서스 조건과 필터 조건으로 나뉜다.

* 액서스 조건 : 인덱스의 스캔 범위를 결정하는 조건절, 인덱스 수직적 탐색으로 스캔 시작점을 찾고 리프 블록을 스캔하다 어디서 멈출지 결정하는데 영향을 미친다.

* 필터 조건 : 테이블로 액서스 할지를 결정하는 조건절, 테이블 액서스 단계에서 처리하는 모든 조건절은 필터 조건이다.

```sql
-- 조건절 1
...
where C1 = 1
and C2 <= 'B'
and C3 = ''
and C4 = 'D'

-- 조건절 2
...
where C1 between 1 and 3
and C2 = 'B'
and C3 = ''
and C4 = 'D'
```

위와 같은 경우 조건절 1의 경우 C1, C2까지의 인덱스 레코드는 모두 연속해서 모여있고 아래부터는 흩어져있다.
조건절 2는 C1 까지만 인덱스 레코드가 연속해서 있고 아래부터는 흩어져 있다.

선행컬럼이 모두 =인 상태에서 첫번째로 나타나는 범위검색 조건이 인덱스 스캔 범위를 결정한다.

조건절 2같이 선행컬럼이 범위 검색인 경우 그 조건이 범위 검색을 하고 아래는 모두 인덱스 필터 조건이 된다.

## 인덱스 선행 칼럼이 등치 조건이 아닐 때 생기는 비효율
인덱스 스캔 효율성은 인덱스 컬럼을 조건절에 모두 등치조건으로 사용할 때 가장 좋다.

인덱스 컬럼 중 일부가 조건절에 없거나 등치 조건이 아니더라도 선행 컬럼이 아니라면 비효율이 없다.

그러나 인덱스 선행 컬럼이 조건절에 없거나 부등호, between, like같은 범위 검색이라면 비효율이 발생한다.

단, Like는 인덱스 수직 탐색을 Like 내부의 조건 개수 만큼 반복하여 인덱스 스캔 효율을 높일 수 있다.
(Between을 Like in으로 변경해도 가능)

그러나 Like in 범위가 너무 넓으면 과도한 인덱스 수직 탐색 비용으로 비효율이 발생될 수 있다.

## Between과 Like 스캔 범위 비교

Like와 Between 둘다 범위검색 조건으로 비효율이 발생한다.

그러나 데이터 분포와 조건절 값에 따라 인덱스 스캔량이 달라질 수 있다.

```sql

-- Like
where 판매월 Like '2019%'

-- Between
where 판매월 Between '201901' and '201912'
```

Like 의 경우 시작점이 '201900' 까지 읽어야 하고 끝지점이 '201912' 이상('201912'전체와 '201913'까지)까지 읽어야 한다

반면 Between의 경우 정확히 '201901'부터 '201912'까지만 읽으면 된다.

## 범위검색 조건을 남용할 때 생기는 비효율

범위 검색을 남용하면 인덱스 스캔 비효율이 발생한다. 특히 대형 테이블에서 넓은 검색시 영향이 매우 커질 수 있다

## 다양한 옵션 조건 처리 방식의 장단점 비교

**OR 조건 활용**

`where (:cust_id is null or 고객ID=:custId) and 거래일지 between :t1 and :t2`

OR 조건이 선두에 나오면 인덱스에 활용을 할 수 없다.

(인덱스에 포함이 안된다면 사용해도 된다. 어차피 테이블에서 필터링해야 하니깐)

**Like/Between조건 활용**

변별력이 좋은 필수조건이 있는 경우 좋은 효율을 낼 수 있다.

```sql
-- 인덱스 : 등록일시 + 상품분류코드
select * from 상품
where 등록일시 >= trunc(sysdate)
and 상품분류코드 like :prd_cls_cd || '%'

```

그러나 필수조건의 변별력이 좋지 못한 경우 성능에 문제가 발생한다.

```sql
-- 인덱스 : 상품대분류코드 + 상품코드
select * from 상품
where 상품대분류코드 = :prd_lcls_cd
and 상품코드 like :prd_cd || '%'
```

만약 상품대분류코드로만 검색시 Table full scan이 유리하다고 가정하면, 위의 경우는 옵티마이저가 상품코드를 입력할 떄를 기준으로 index range scan을 선택한다.

그래서 Like, between을 사용하면 아래의 조건을 점검해야 한다.

* 인덱스 선두컬럼 : 인덱스 선두컬럼에 대한 옵션조건을 like/between연산자로 처리하는 것은 비효율을 초래
* Null 허용 컬럼 : Null 허용 컬럼에 대한 옵션 조건을 like/between으로 처리하면 안된다.
* 숫자형 컬럼 : 숫자형이면서 인덱스 액서스 조건으로도 사용 가능한 컬럼으로 옵션 조건 처리는 Like를 쓰면 안된다.
* 가변 길이 컬럼 : Like를 사용하려면 칼럼 값 길이가 정해져야 한다.

**Union all**

조건 컬럼에 null이 들어갈 수 있으면 union all로 어느 하나만 실행되게 하는 방법도 있다.

```sql
select * from 거래
where :cust_id is null
and 거래일자 between :dt1 and :dt2

union all

select * from 거래
where :cust_id is not null
and 고객ID = :cust_id
and 거래일자 between :dt1 and :dt2
```

**NVL/DECODE**

```sql

select * from 거래
where 고객ID = nvl(:cust_id, 고객ID)
and 거래일자 between :dt1 and :dt2

--또는

select * from 거래
where 고객ID = decode(:cust_id, null, 고객ID, :cust_id)
and 거래일자 between :dt1 and :dt2
```
위의 경우 :cust_id에 값이 들어오지 않든 아니든 실행 계획은 똑같아진다. 옵티마이저가 Union all 방식으로 쿼리를 변환한다.

단점이라면 Like 패턴처럼 null 허용 컬럼에 사용이 불가능하다는 것이다.

## 함수호출부하 해소를 위한 인덱스 구성

### PL/SQL 함수의 성능적 특성

PL/SQL 사용자 함수는 생각보다 느리다

이유는
1. 가상 머신상에서 실행되는 인터프리터 언어
2. 호출 시마다 컨텍스트 스위칭 발생
3. 내장 SQL 에 대한 Recursive call 발생

그래서 PL/SQL 함수 호출을 최소화 시키면 효율을 증대시킬 수 있다.

### 효과적인 인덱스 구성을 통한 함수호출 최소화

```sql
select *
from user a
where birth = '1987'
and encrypted_phone_nm = encryption(:phone)
```

위의 쿼리문은 `where birth = '1987'`을 만족하는 건수만큼 함수호출을 발생시킨다.

만약 `create index idx_X01 on user(birth, encrypted_phone_nm)` 의 인덱스를 추가한다면 암호화된 전번도 인덱스에 사용되면서 함수 호출은 1번으로 끝난다.

# 3.4 인덱스 설계

## 인덱스 설계가 어려운 이유

* DML 성능 저하
* 데이터베이스 사이즈 증가
* 데이터베이스 관리 및 운영비용 상승

## 가장 중요한 두가지 선택 기준

1. 조건절에 항상 사용되거나 자주 사용하는 컬럼을 선정
2. 선정한 컬럼 중 '='으로 많이 조회하는 컬럼을 앞에 두어야 한다.

## 스캔 효율성 이외의 판단 기준

* **수행 빈도**
* 업무상 중요성
* 클러스터링 팩터
* 데이터량
* DML 부하
* 저장 공간
* 인덱스 관리 비용

## 결합 인덱스 선택도

인덱스 생성 여부를 결정할 때 선택도가 충분히 낮은지도 중요하다.

선택도는 전체 레코드 중에서 조건절에 의해 선택되는 레코드 비율이며, 선택도 * 총 레코드 수를 곱하여 카디널리티를 구한다.

그러나 칼럼이 필수 조건이라면 어떤 것이 먼저 오든 효율에 큰 영향을 미치지 않는다.

```sql
select *
from customer
where cust_rank = :cust_rank
and cust_id = :cust_id

select *
from customer
where cust_id = :cust_id
and cust_rank = :cust_rank
```

위의 두 쿼리문은 둘다 필수 조건이라면 성능이 똑같다.

그러나 둘 중 하나 이상이 조건절에 누락되거나 범위검색 조건절이라면 문제가 달라진다.

만약 cust_id가 필수인데 조건절에 cust_rank이 누락되거나 범위 조건이라면 cust_rank를 앞에 두는게 좋다

## 중복 인덱스 제거

아래의 경우는 중복 인덱스이므로 X01, X02를 삭제하는 것이 좋다.

* X01 : 계약ID + 청약일자
* X02 : 계약ID + 청약일자 + 보험개시일자
* X03 : 계약ID + 청약일자 + 보험개시일자 + 보험종료 일자


아래의 경우는 중복은 아니나 계약ID 카디널리티가 낮으면 사실상 중복이 된다

* X01 : 계약ID + 청약일자
* X02 : 계약ID + 보험개시일자
* X03 : 계약ID + 보험종료 일자

0 comments on commit d445040

Please sign in to comment.