├── 1장_SQL_처리_과정과_IO
├── 1-2_seyun.md
├── 1.1_sql_파싱과_최적화_유효정.pdf
├── 1.3_최락준.md
├── 1장_SQL_처리_과정과_I:O_황준호.md
├── 1주_김민석.md
├── images
│ └── 1-3-1.png
└── 조수진.md
├── 2장_인덱스_기본
├── 2.2_인덱스_기본_사용법_김광훈.md
├── 2.3_인덱스_확장기능_사용법_황준호.md
├── 2주_김민석.md
└── 조수진.md
├── 3장_인덱스_튜닝
├── 3.1_테이블_엑세스_최소화_김광훈.md
├── 3.3.1~3.3.4_인덱스_스캔_효율화_유효정.pdf
├── 3.3.5~3.4.내용_요약_김광훈.md
├── 3.3.5~3.4_황준호.md
├── 3.3_최락준.md
├── 3장_인덱스_튜닝.md
├── 3주_김민석.md
├── 4주_김민석.md
├── images
│ ├── 3-43.jpg
│ └── 3-44.jpg
└── 조수진.md
├── 4장_조인_튜닝
├── .empty
├── 4.1_NL_조인_김광훈.md
└── 6주_김민석.md
├── 5장_소트_튜닝
├── .empty
├── 5.1~5.1_내용_요약_김광훈.md
├── 5.1~5.4_황준호.md
├── 5.3_인덱스를_이용한_소트연산_생략_유효정.pdf
└── 5장_김민석.md
├── 6장_DML_튜닝
├── 6.1_기본DML튜닝_김광훈.md
├── 6.3_최락준.md
├── 6.4_Lock과_트랜잭션_동시성_제어_황준호.md
├── 6장-1_김민석.md
├── images
│ ├── partition1.jpeg
│ ├── partition2.jpeg
│ └── partition3.jpeg
└── 조수진.md
├── 7장_SQL_옵티마이저
├── .empty
└── 7.1_김민석.md
├── README.md
└── img
├── pic3-3.png
├── pic3-47.png
├── pic_1-12.png
├── pic_1-27.png
├── pic_1-4.png
└── table3-8.png
/1장_SQL_처리_과정과_IO/1-2_seyun.md:
--------------------------------------------------------------------------------
1 | ## 1.2. SQL 공유 및 재사용
2 |
3 | ### 소프트 파싱 vs 하드 파싱
4 |
5 | - 프로시저 :
6 | - 라이브러리 캐시 (Library Cache) : SQL 파싱, 최적화, 로우 소스 생성 과정을 거쳐 생성한 내부 프로시저를 반복 재사용할 수 있도록 캐싱해 두는 메모리 공간
7 | - SGA(System Global Area) : 서버 프로세스와 백그라운드 프로세스가 공통으로 액세스하는 데이터와 제어 구조를 캐싱하는 메모리 공간
8 |
9 | 
10 |
11 | - 소프트 파싱(Soft Parsing) : SQL을 캐시에서 찾아 곧바로 실행단계로 넘어가는 것
12 | - 하드 파싱(Hard Parsing): 찾는데 실패 해 최적화 및 로우 소스 생성 단계까지 모두 거치는 것
13 |
14 | 
15 |
16 | ---
17 |
18 | - PostreSQL은 어떤가?
19 | - PostreSQL에서는 Library Cache라는 개념이 존재 하지 않는다.
20 | - 대신 Buffer가 존재하는데 아래와 같다.
21 | - Shared Buffer : 사용자가 요청한 데이터 블록을 저장하는 공간
22 | - WAL(Write Ahead Log) Buffer : 데이터 변경 사항을 저장하는 버퍼
23 | - 여기서 Shared Buffer에 존재하면 소프트 파싱이 되고, 아니면 하드 파싱이 된다.
24 |
25 | 
26 |
27 | ### SQL 최적화 과정은 왜 하드(Hard)한가
28 |
29 | - 옵티마이저가 SQL을 최적화할 때도 데이터베이스 사용자들이 보통 생각하는 것보다 훨씬 많은 일을 수행한다. 즉, 수많은 경우의 수가 존재한다.
30 | - 아래와 같은 정보를 사용해 옵티마이저가 경로를 찾는다.
31 | - 테이블, 컬럼, 인덱스 구조에 관한 기본 정보
32 | - 오브젝트 통계 : 테이블 통계, 인덱스 통계, (히스토그램을 포함한) 컬럼 통계
33 | - 시스템 통계 : CPU 속도, Single Block I/O 속도, Multiblock I/O 속도 등
34 | - 옵티마이저 관련 파라미터
35 | - 이러한 작업을 매번 하면 과부하이기 떄문에, 캐시를 두고 소프트 파싱을 진행하는 것이다.
36 |
37 | ### 바인드 변수의 중요성
38 |
39 | - 이름없는 SQL 문제
40 | - 사용자 정의 함수/프로시저, 트리거, 패키지 등은 생성할 떄부터 이름을 갖음.
41 | - 즉, 컴파일한 상태로 딕셔너리에 저장되며 사용자가 삭제 하지 않는 이상 영구적으로 보관된다.
42 | - 그러나 SQL은 이름이 존재하지 않는다. 그 이유는 SQL 자체가 이름이며, 텍스트 중 작은 하나라도 수정되면 그 순간 다른 객체가 새로 탄생하는 구조이기 때문이다.
43 | - 일회성 SQL도 많고 무효화된 SQL도 많기 때문에 모두 저장하기에는 많은 공간이 필요하기 때문이다.
44 |
45 | - 공유 가능 SQL
46 | - SQL을 캐시에서 찾기 위해 사용하는 키 값은 SQL문 그 자체다. 동일한 결과를 보여주는 SQL이라고 해도 철자하나라도 다르면 다른 SQL이라고 보는 것이다.
47 | - 아래와 같이 작성한다고 해보자.
48 |
49 | ```jsx
50 | SELECT * FRO CUSTOMER WHERE LOGIN_ID = '" + login_id + "'";
51 | ```
52 |
53 | - 100만명이 한번에 조회를 하면 모두 하드파싱을 하게 될 것이다.
54 | - 그래서 프로시저 하나를 공유하면서 재사용하는 것이 마땅하기 때문에 바인드 변수를 사용하게 되는 것이다.
55 |
56 | ```jsx
57 | SELECT * FROM CUSTOMER WHERE LOGIN_ID = ?
58 | ```
59 |
--------------------------------------------------------------------------------
/1장_SQL_처리_과정과_IO/1.1_sql_파싱과_최적화_유효정.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Meet-Coder-Study/nice-sql-tuning/6ebb1473a41c04d97351f3527a5c2311b4fc4973/1장_SQL_처리_과정과_IO/1.1_sql_파싱과_최적화_유효정.pdf
--------------------------------------------------------------------------------
/1장_SQL_처리_과정과_IO/1.3_최락준.md:
--------------------------------------------------------------------------------
1 | # 1.3 데이터 저장 구조 및 I/O 메커니즘
2 |
3 | ## 1.3.1 SQL이 느린 이유
4 |
5 | > sql이 느린 이유 : **디스크 I/O**
6 |
7 | 하나의 CPU는 한 번에 하나의 프로세스를 처리한다.
8 | 그런데 그 프로세스가 I/O작업을 하게 되면 I/O작업이 끝날 때까지 대기 상태로 머문다.
9 | 여러 프로세스들과 경합에서 자원을 할당받은 프로세스가 열심히 일해야 하는 시간에 대기하는 것이다.
10 | sql의 지연은 바로 이 대기 시간때문에 발생한다.
11 |
12 | ---
13 |
14 | ## 1.3.2 데이터베이스 저장 구조
15 |
16 | 데이터베이스의 저장 구조는 다음과 같이 논리적 구조와 물리적 구조로 나눌 수 있다.
17 |
18 | 
19 |
20 | ```
21 | * 블록 : 데이터를 읽고 쓰는 단위
22 | * 익스텐트 : 세그먼트의 저장 공간을 확장하는 단위
23 | * 세그먼트 : 데이터 저장공간이 필요한 오브젝트(테이블, 인덱스, LOB 등)
24 | * 테이블 스페이스 : 세그먼트를 담는 컨테이너
25 | ```
26 |
27 | * 하나의 세그먼트에 해당하는 익스텐트는 같은 데이터파일에 위치하지 않을 수 있다.
28 | * 이는 파일 경합을 줄이기 위해 여러 데이터파일로 분산해서 저장하기 때문이다.
29 |
30 | ---
31 |
32 | ## 1.3.3 블록 단위 I/O
33 |
34 | 데이터베이스에서 데이터를 읽고 쓰는 단위 : `블록`
35 |
36 | * 하나의 레코드를 읽을 때도 블록을 통째로 읽는다.
37 | * 오라클의 기본 블록 크기는 8kb 이다.
38 |
39 | ---
40 |
41 | ## 1.3.4 시퀀셜 액세스 vs 랜덤 액세스
42 |
43 | ### (1) 시퀀셜 액세스
44 |
45 | > 물리적 혹은 논리적 순서에 따라 블록을 읽는 방식
46 |
47 | 인덱스 리프 블록의 경우
48 | * 인덱스 리프 블록은 앞뒤를 가리키는 주소값을 통해 논리적으로 연결되어 있다.
49 | * 주소값 대로 순차적으로 블록을 읽는다.
50 |
51 | 테이블 블록의 경우
52 | * **익스텐트 목록을 세그먼트 헤더에 맵**으로 관리. 이 맵에는 각 **익스텐트의 첫 번째 블록 주소 값이 저장**되어 있다.
53 | * 각 익스텐트의 첫 번째 블록부터 순서대로 읽는 것이 Full Table Scan이다.
54 |
55 | ### (2) 랜덤 액세스
56 |
57 | > 논리적/물리적 순서를 따르지 않고, 레코드 하나를 읽기 위해 한 블록씩 접근하는 방식
58 |
59 | ---
60 |
61 | ## 1.3.5 논리적 I/O vs 물리적 I/O
62 |
63 | ### DB 버퍼 캐시
64 |
65 | * 디스크에서 읽어 들인 데이터를 `DB 버퍼 캐시`에 저장한다
66 | * 이를 통해 반복적인 디스크 I/O Call을 줄일 수 있다.
67 |
68 | ### 논리적 I/O
69 |
70 | * DB 버퍼 캐시를 통해 데이터를 가져오는 것을 논리적 I/O라고 한다.
71 | * 주의할 점은 논리적 I/O는 DB 버퍼 캐시에서 **바로 찾은** 경우를 의미하지 않는다.
72 | * 디스크 I/O를 통해 데이터를 가져오더라도 DB 버퍼 캐시를 경유하기 때문에 결국 논리적 I/O는 **총 읽은 블록 수**가 된다.
73 |
74 | ### 물리적 I/O
75 |
76 | * DB 버퍼 캐시에 데이터가 없어 디스크에 접근하여 데이터를 가져오는 것을 물리적 I/O라고 한다.
77 | * 논리적 I/O는 전기적 신호인데 반해 물리적 I/O 는 실제로 디스크에서 데이터를 가져오기 때문에 논리적 I/O에 비해 훨씬 느리다.
78 |
79 | ### 버퍼캐시 히트율(BCHR)
80 |
81 | 버퍼캐시의 효율을 측정하는 지표로 공식은 다음과 같다.
82 | ```
83 | BCHR = ( 캐시에서 곧바로 찾은 블록 수 / 총 읽은 블록 수 ) * 100
84 | = ( (논리적 I/O - 물리적 I/O) / 논리적 I/O) * 100
85 | ```
86 |
87 | * 버퍼캐시 히트율을 높이는데 가장 중요한 것은 물리적 I/O 를 줄이는 것이 아니다.
88 | * **논리적 I/O 를 줄이는 것**이 버퍼캐시 히트율에 가장 큰 영향을 미친다.(논리적 I/O는 분모이기 때문에 아무리 물리적 I/O가 적다한들 소용 없다.
89 | * 즉 sql의 성능 향상을 위해서는 `총 읽은 블록 수`를 줄이는 것이 핵심이다.
90 |
91 | ---
92 |
93 | ## 1.3.6 Single Block I/O vs Multiblock I/O
94 |
95 | ### Single Block I/O
96 |
97 | * 한번의 I/O Call에서 하나의 block을 가져오는 것.
98 | * 일반적으로 인덱스를 사용할 때는 Single Block I/O 를 사용한다.
99 | * 따라서 소량 데이터를 읽을 때 효율적이다.
100 |
101 | ### Multiblock I/O
102 |
103 | * 한번의 I/O Call에서 여러개의 block을 가져오는 것.
104 | * 대용량 데이터를 한꺼번에 가져올 때 효율적이다.
105 |
106 | ---
107 |
108 | ## 1.3.7 Table Full Scan vs Index Range Scan
109 |
110 | ### Table Full Scan
111 |
112 | * 시퀀셜 액세스 + Multiblock I/O
113 | * 즉 데이터를 한꺼번에 순서대로 읽는 방식이다.
114 |
115 | ### Index Range Scan
116 |
117 | * 랜덤 액세스 + Singleblock I/O
118 | * 데이터에 임의로 접근하여 작은 단위로 읽는 방식이다.
119 |
120 | ### Table Full Scan vs Index Range Scan
121 |
122 | * Table Full Scan 이라고 무조건 느린 것이 아니고, Index Range Scan 이라고 무조건 빠르지 않다.
123 | * **대량의 데이터를 가져와 한번에 작업**하는 경우 (배치, 통계 등)에서는 한번에 많이 처리하는 `Table Full Scan` 방식이 빠르다.
124 | * **대용량의 데이터에서 소량의 데이터를 검색**하는 경우(검색 등)에는 임의로 접근할 수 있는 `Index Range Scan` 방식이 빠르다.
125 |
126 | ---
127 |
128 | ## 1.3.8 캐시 탐색 매커니즘
129 |
130 | ### 메모리 공유자원에 대한 액세스 직렬화
131 |
132 | * 버퍼시는 SGA의 구성요소이므로, 버퍼캐시에 저장된 데이터는 공유된다.
133 | * 이 때 버퍼캐시의 사용은 동시에 이뤄지지 않고 순차적으로 접근하도록 구현되어 있는데, 이를 `래치`라 한다.
134 | * 래치는 일종의 버퍼 캐시 자물쇠이다. 해당 자격이 있는 프로세스만이 데이터에 접근하도록 하는 것이다.
135 | * 이러한 (잠그는) 방식은 결국 여러 프로세스가 접근 할 때 캐시 I/O 의 지연을 발생 시킨다.
136 | * 이러한 지연을 줄이는 방법 또한 결국 `논리적 I/O 수를 줄이는 것`이다.
--------------------------------------------------------------------------------
/1장_SQL_처리_과정과_IO/1장_SQL_처리_과정과_I:O_황준호.md:
--------------------------------------------------------------------------------
1 | ## 1장. SQL 처리 과정과 I/O
2 |
3 | ### 1.1 SQL 파싱과 최적화
4 |
5 | - SQL (Structured Query Language)
6 | - 구조적, 집합적, 선언적 질의 언어
7 | - 전체적인 과정
8 | - 사용자 --SQL--> 옵티마이저 --실행계획--> 프로시저
9 | - SQL 최적화
10 | - DBMS 내부에서 프로시저를 작성하고 컴파일해서 실행 가능한 상태로 만드는 전 과정
11 | - SQL이 실행되는 과정
12 | 1. SQL 파싱
13 | - 파싱 트리 생성 (SQL 개별 구성요소를 분석해서 파싱 트리 생성)
14 | - Syntax 체크 (문법 오류 확인)
15 | - Semantic 체크 (의미상 오류 확인. 없는 테이블인지, 없는 컬럼인지, 권한은 있는지 등)
16 | 2. SQL 최적화
17 | - 옵티마이저가 다양한 실행 경로 중 하나를 선택한다
18 | 3. 로우 소스 생성
19 | - 옵티마이저가 선택한 실행 경로를 실제 실행 가능한 코드 또는 프로시저로 포맷팅
20 | - 로우 소스 생성기가 수행함
21 | - SQL 옵티마이저
22 | - 최적의 데이터 액세스 경로를 선택해주는 DBMS의 핵심 엔진
23 | - 단계 : 후보 실행계획을 찾음 -> 각 실행계획의 예상비용 산정(데이터 딕셔너리에 미리 수집해둔 통계 및 시스템 통계 정보 이용) -> 최저비용 선택
24 | - 옵티마이저 힌트
25 | - 옵티마이저가 항상 최적의 실행계획을 찾는 건 아니다. SQL이 복잡할수록 실수할 가능성이 크다
26 | - 직접 사용할 인덱스를 결정해줄 수 있다
27 | - 일부만 지정해주고 나머지는 옵티마이저가 알아서 선택하도록 해줄수도 있다
28 |
29 | ### 1.2 SQL 공유 및 재사용
30 |
31 | - 소프트 파싱 vs 하드 파싱
32 |
33 | - DBMS가 SQL을 파싱한 후 해당 SQL이 라이브러리 캐시에 존재하는지 확인하여
34 | - 있으면? 곧바로 실행 (소프트 파싱)
35 | - 없으면? 최적화 -> 로우소스 생성 -> 실행 (하드 파싱)
36 |
37 | - SQL 최적화를 할때 옵티마이저가 사용하는 정보
38 |
39 | - 테이블, 컬럼, 인덱스 구조
40 | - 오브젝트 통계 (테이블 통계, 인덱스 통계, 컬럼 통계)
41 | - 시스템 통계 (CPU속도, Single Block I/O 속도, Multiblock I/O 속도)
42 | - 옵티마이저 관련 파라미터 등등
43 |
44 | - 이렇게 많은 정보를 사용하여 무거운 연산을 통해 도출한 내부 프로시저를 한 번만 사용한다면 비효율적이다. 그래서 라이브러리 캐시가 필요하다
45 |
46 | - 바인드변수를 잘 활용해야 한다
47 |
48 | - 예를 들어 다음과 같이 라이브러리 캐시가 남으면 안되고,
49 |
50 | ```sql
51 | SELECT * FROM CUSTOMER WHERE LOGIN_ID = 'hwang'
52 | SELECT * FROM CUSTOMER WHERE LOGIN_ID = 'kim'
53 | SELECT * FROM CUSTOMER WHERE LOGIN_ID = 'lee'
54 | SELECT * FROM CUSTOMER WHERE LOGIN_ID = 'park'
55 | ...
56 | ```
57 |
58 | - 다음과 같이 남아야 한다
59 |
60 | ```sql
61 | SELECT * FROM CUSTOMER WHERE LOGIN_ID = :1
62 | ```
63 |
64 | ### 1.3 데이터 저장 구조 및 I/O 메커니즘
65 |
66 | - SQL이 느린 이유는 거의 I/O 때문
67 |
68 | - 데이터베이스 저장 구조
69 |
70 | - 데이터 파일 : 디스크 상의 물리적인 OS 파일
71 | - 테이블 스페이스 : 세그먼트를 담는 컨테이너
72 | - 세그먼트 : 데이터 저장공간이 필요한 오브젝트
73 | - 익스텐트 : 공간을 확장하는 단위
74 | - 블록 : 데이터를 읽고 쓰는 단위
75 |
76 | - 테이블 또는 인덱스 블록을 읽는 방식
77 |
78 | 1. 시퀀셜 엑세스 : 차례로 블록을 읽는 방식
79 | 2. 랜덤 액세스 : 레코드 하나를 읽기 위해 한 블록씩 접근하는 방식
80 |
81 | - 논리적 I/O vs 물리적 I/O
82 |
83 | - 논리적 I/O
84 | - SQL문을 처리하는 과정에 메모리 버퍼캐시에서 발생한 총 블록 I/O
85 | - 물리적 I/O
86 | - 디스크에서 발생한 총 블록 I/O를 말한다
87 |
88 | - 버퍼캐시 히트율(BCHR) = ( 1- ( 물리적I/O / 논리적I/O ) ) * 100
89 |
90 | - 물리적I/O는 통제불가, 논리적I/O는 통제가능
91 | - 논리적I/O를 줄임으로써 물리적I/O를 줄이는 것이 SQL 튜닝이다
92 | - BCHR이 높다고 해서 효율적인 SQL라는 뜻은 아니다 (같은 블록을 비효율적으로 반복하면 BCHR은 높아진다)
93 |
94 | - Single Block I/O vs Multiblock I/O
95 |
96 | - I/O call할때
97 | - 한번에 한 블록씩 요청해서 메모리에 적재하면 Single Block I/O
98 | - 한번에 여러 블록씩 요청해서 메모리에 적재하면 Multiblock I/O
99 | - 인덱스를 사용할때는 기본적으로 Single Block I/O를 사용한다 (소량을 읽을때 주로 사용하므로)
100 | - 많은 데이터 블록을 읽을땐 Multiblock I/O가 효율적이다 (같은 익스텐트에 속한 블록을 모두 가져온다)
101 |
102 | - Table Full Scan vs Index Range Scan
103 |
104 | - Table Full Scan : 테이블 전체를 스캔해서 읽는 방식
105 | - 시퀀셜 엑세스와 Multiblock I/O 방식으로 디스크 블록을 읽는다
106 | - 한 블록에 속한 모든 레코드를 한번에 읽어들이고 캐시에서 못찾으면 한번의 수면을 통해 많은 블록을 한꺼번에 I/O하는 매커니즘
107 | - Index Range Scan : 인덱스를 이용하여 읽는 방식
108 | - 랜덤 액세스와 Single Block I/O 방식으로 디스크 블록을 읽는다
109 | - 캐시에서 블록을 못찾으면 레코드 하나를 읽기 위해 매번 잠을 자는 I/O매커니즘
110 | - 많은 데이터를 읽을때는 오히려 성능이 떨어질 수 있다
111 |
112 | - 캐시 탐색 매커니즘
113 |
114 | - 다음의 경우 버퍼캐시 탐색 과정을 거친다
115 | - 인덱스 루트 블록을 읽을때
116 | - 인덱스 루트 블록에서 얻은 주소 정보로 브랜치 블록을 읽을때
117 | - 인덱스 브랜치 블록에서 얻은 주소 정보로 리프 블록을 읽을때
118 | - 인덱스 리프 블록에서 얻은 주소 정보로 테이블 블록을 읽을때
119 | - 테이블 블록을 Full Scan할때
120 | - 버퍼 캐시에서는 블록 번호를 해시함수로 관리한다
121 | - 래치
122 | - 버퍼 블록을 여러 프로세스가 동시에 접근하면 안된다. 그래서 직렬화(줄세우기)가 필요하다. 직렬화가 가능하도록 지원하는 매커니즘을 래치라고 한다
123 | - 버퍼캐시에는 캐시버퍼 체인래치, 캐시버퍼 LRU 체인래치 등이 작동한다
124 | - 버퍼블록 자체에서 직렬화 매커니즘이 존재한다 (버퍼 락)
125 |
--------------------------------------------------------------------------------
/1장_SQL_처리_과정과_IO/1주_김민석.md:
--------------------------------------------------------------------------------
1 | # 1장 SQL 처리 과정과 I/O
2 |
3 | ## 1.1 SQL 파싱과 최적화
4 |
5 | - SQL : Structured Query Language
6 | - 구조적 질의 언어
7 | - 구조적 / 집합적 / 선언적
8 | - 원하는 결과집합은 구조적/집합적으로 선언
9 | - 결과집합을 만드는 과정은 절차적 => 프로시저 필요
10 | - 프로시저를 최적화 하는 것이 옵티마이저
11 | - 또한 이 과정을 SQL 최적화라고 함
12 |
13 |
14 | - SQL 최적화
15 | - SQL 파싱 -> SQL 최적화
16 |
17 |
18 | - SQL 옵티마이저
19 | - 사용자가에게 전달 받은 쿼리를 실행할 후보군 선정
20 | - 후보군 중 가장 최저 비용을 나타내는 실행계획 선택
21 |
22 |
23 | - 옵티마이저 힌트
24 | - 자동으로 옵티마이저를 하지만 사용자가 힌트를 주어 의도적으로 다른 실행계획을 선택 할 수 있게 함
25 |
26 |
27 | ## SQL 공유 및 재사용
28 |
29 | - 소프트 파싱 / 하드 파싱
30 | - 캐시되어 있지 않은 경우 SQL 파싱, 최적화, 로우 소스 생성 등을 거쳐 내부 프로시저를 생성하는데 이를 하드 파싱이라 하고 캐시된 하드 파싱을 바로 찾아 쓰는 것을 소프트 파싱이라 한다.
31 |
32 |
33 | - 바인드 변수의 중요성
34 | - SQL은 전체가 하나의 이름이 된다. 따라서 이름을 따로 만들 필요가 없다
35 | - 따라서, 바인드 변수를 프로그래밍적으로 하드 코딩 하게 되면 그 수만큼 캐싱되어 쌓이게 되는 문제가 발생한다.
36 |
37 | ## 데이터 저장 구조 및 I/O 메커니즘
38 |
39 | - SQL이 느린 이유
40 | - I/O는 매우 느리다.
41 | - 쿼리를 빠르게 하기 위해서는 I/O를 줄이고 프로세서가 일어나서 동작하는 시간을 최대한 늘여야한다.
42 |
43 |
44 | - 데이터베이스 저장 구조
45 | - 테이블스페이스 > 세그먼트 > 익스텐트 > 블록
46 | - 익스텐트 단위로 공간 확장
47 | - 실제 데이터는 블록에 저장
48 | - 한 블록은 같은 테이블 저장
49 | - 한 익스텐트는 같은 테이블 저장
50 |
51 |
52 | - I/O는 블록 단위로 이루어진다
53 |
54 | - 액세스 방식
55 | - 시퀀셜
56 | - 연결된 순서대로 차례대로 읽는다
57 | - 인덱스 리프 블록 읽을 때
58 | - 테이블 블록 읽을 때(Full Table Scan)
59 | - 랜덤
60 | - 논리적, 물리적 순서를 따르지 않고 레코드 하나를 읽기 위해 한 블록씩 접근
61 |
62 |
63 | - 논리적 I/O vs 물리적 I/O
64 | - DB 버퍼 캐시 : 디스크에서 읽은 데이터 블록의 캐시
65 | - 라이브러리 캐시: SQL/ 실행계획/ 프로시저를 저장한 캐시
66 | - 논리적 I/O : 읽어야 하는 총 블록
67 | - 물리적 I/O : 읽어야 하는 총 블록 중 디스크에서 읽어 온 블록
68 | - BCHR(Buffer Cache hit Ratio)
69 | - 논리적 I/O 중 물리적 I/O를 제외하고 DB 버퍼 캐시에서 읽어 온 비율
70 | - 논리적 I/O 를 줄이는 것이 성능을 올리는 방법
71 | - `SQL 튜닝은 결국 논리적 I/O를 줄이는 것`
72 |
73 | - 싱글 블록 I/O vs 멀티 I/O
74 | - 싱글 블록 I/O
75 | - 한번에 하나의 블록만 읽는 것
76 | - 인덱스 읽을 때
77 | - 멀티 블록 I/O
78 | - 데이터 블록 읽을 때
79 |
80 |
81 | - Table Full Scan vs Index Range Scan
82 | - Table Full Scan
83 | - 데이터 전체를 스캔해서 읽는 것
84 | - Index Range Scan
85 | - 인덱스를 이용한 테이블 액세스
86 | - 두 가지 방식을 적절하게 사용하는 것이 중요
87 |
88 |
89 | - 캐시 탐색 메커니즘
90 | - 메모리 버퍼 캐시 탐색 시 해시 알고리즘 사용
91 | - 메모리 버퍼 캐시는 SGA 구성요소 이기 때문에 공유 자원
92 | - 따라서 동시성 관리 필요
93 | - 내부적으로 동시에 접근 불가하고 순차적으로 접근함(직렬화)
94 | - 줄서기 담당이 래치(Latch)
95 |
--------------------------------------------------------------------------------
/1장_SQL_처리_과정과_IO/images/1-3-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Meet-Coder-Study/nice-sql-tuning/6ebb1473a41c04d97351f3527a5c2311b4fc4973/1장_SQL_처리_과정과_IO/images/1-3-1.png
--------------------------------------------------------------------------------
/1장_SQL_처리_과정과_IO/조수진.md:
--------------------------------------------------------------------------------
1 | # 1장 SQL 처리 과정과 I/O
2 |
3 | ## 1.1 SQL 파싱과 최적화
4 | ### 1.1.1 구조적, 집합적, 선언적 질의 언어
5 | - SQL은 기본적으로 구조적이고 집합적이고 선언적인 질의 언어다
6 | - 원하는 결과집합을 구조적, 집합적으로 선언하지만, 그 결과집합을 만드는 과정은 절차적일 수 밖에 없다 = 프로시저가 필요하다!
7 | - DBMS 내부의 엔진인 옵티마이저가 프로시저를 생성한다 = 프로시저가 대신 프로그래밍 해주는 셈
8 | - 프로시저를 작성하고 컴파일 해서 실행 가능한 상태로 만드는 전 과정을 `SQL 최적화`
9 | ### 1.1.2 SQL 최적화
10 | 1. SQL 파싱
11 | - 파싱 트리 생성
12 | - Syntax 체크
13 | - Semantic 체크
14 | 2. SQL 최적화
15 | 3. 로우 소스 생성(실행 가능한 코드 또는 프로시저 형태로 포맷팅하는 단계)
16 | ### 1.1.3 SQL 옵티마이저
17 | - 쿼리를 수행하기 위한 실행계획들을 찾아냄
18 | - 데이터 딕셔너리에 미리 수집해둔 오브젝트에 통계 및 시스템 통계 정보를 이용해 각 실행계획의 예상 비용 산정
19 | - 최저 비용을 나타내는 실행계획 선택
20 | ### 1.1.4 실행계획과 비용
21 | - MySQL에서 실행 계획을 확인하기 위한 키워드 `EXPLAIN`
22 | - 실행하는 실행계획은 옵티마이저가 비용을 근거로 선택한 것
23 | - 비용은 쿼리를 실행하는 동안 발생할 것으로 `예상되는` `I/O 횟수, 예상 소요 시간`을 표현한 것
24 |
25 | **옵티마이저가 실행계획을 세우기 위해 고려하는 수많은 정보들**
26 |
27 | - 테이블, 컬럼, 인덱스 구조에 관한 기본 정보
28 | - 오브젝트 통계: 테이블 통계, 인덱스 통계, (히스토그램을 포함한) 컬럼 통계
29 | - 시스템 통계: CPU 속도, Single Block I/O 속도, Mutliblock I/O 속도 등
30 | - 옵티마이저 관련 파라미터
31 | ### 1.1.5 옵티마이저 힌트
32 | - 상황에 따라 사용하거나 사용하지 않을 수 있음
33 | - MySQL에서 힌트를 사용하기 위해서 주석 기호에 +를 붙여 작성한다
34 |
35 | 예시: `SELECT /*+ ... 8/`
36 |
37 | ## 1.2 SQL 공유 및 재사용
38 | ### 1.2.1 소프트 파싱 vs 하드 파싱성
39 | 
40 | - SGA: 서버 프로세스와 백그라운드 프로세스가 공통으로 액세스하는 데이터와 제어 구조를 캐싱하는 메모리 공간
41 | - 라이브러리 캐시(쿼리 캐시): SQL파싱, 최적화, 로우 소스 생성 과정을 거친 내부 프로시저는 라이브러리 캐시에 저장되어 재사용된다
42 | - 소프트 파싱: 파싱한 SQL이 라이브러리 캐시에 존재하면 바로 실행
43 | - 하드 파싱: 파싱한 SQL이 라이브러리 캐시에 존재하지 않아서 최적화, 로우 소스 생성과정을 모두 거치는 것
44 | - 재사용 이유? 최적화 과정에 딕셔너리, 통걔 정보를 읽어 효율성을 판단하는 과정은 CPU를 많이 사용하는 과정
45 | ### 1.2.2 바인드 변수의 중요
46 | - 라이브러리 캐시에 저장되는 SQL에는 이름이 없다
47 | - SQL자체가 검색을 위한 key -> 대, 소문자, 뛰어쓰기 등 아주 조금만 달라도 매번 하드 파싱하고 캐시에 저장한다
48 | - (중요)!바인드 변수를 사용할 것(자바 PreparedStatement)!
49 |
50 | **바인드 변수**
51 | - PL/SQL 또는 SQL 쿼리문에서 WHERE 절에 value 값을 (?) 로 사용하는 변수
52 | - 아래와 같이 파라이터 Driven 방식으로 내부 프로시저를 생성한다
53 |
54 | `create procedure LOGIN (login_id varchar2) { ... }`
55 |
56 | ## 1.3 데이터 저장 구조 및 I/O 메커니즘
57 | ### 1.3.1 SQL이 느린 이유
58 | - SQL이 느린 이유는 디스크 I/O 때문. 디스크 I/O가 SQL 성능을 좌우
59 | - I/O를 진행하는 동안 프로세스는 대기 상태에 빠진다
60 | - 스토리지 성능이 빨라지고 있지만 여전히 시간이 가장 많이 소요되는 작업
61 | ### 1.3.2 데이터베이스 저장 구조
62 | - 데이터파일: 디스크 상의 물리적인 OS 파일
63 | - 테이블 스페이스: 세그먼트를 담는 컨테이너, 여러 개의 데이터파일로 구성됨
64 | - 세그먼트: 테이블, 인덱스, LOB컬럼 등과 같이 데이터 저장공간이 필요한 오브젝트, 파니셔닝 되지 않았다면 한 오브젝트는 하나의 세그먼트에 저장됨
65 | - 테이블 세그먼트 헤더에 익스텐트 맵(각 익스텐트의 첫 번째 블록 DBA 저장)이 있음
66 | - 익스텐트: 공간을 확장하는 단위. 세그먼트 구성요소. 연속된 블록 집합. 세그먼트 공간이 부족하면 테이블스페이스로부터 익스텐트를 추가 할당 받음
67 | - 한 세그먼트의 익스텐트끼리는 연속된 공간이 아닐 수 있음
68 | - 블록(or 페이지): 실제 데이터가 저장되는 공간. 익스텐트의 구성요소. 한 블록에 저장된 레코드는 모두 같은 테이블 레코드
69 | - 익스텐트 내의 블록은 서로 인접한 연속된 공간
70 | - DBA(Data Block Address): 데이터 블록이 디스크 상에서 몇 번 데이터파일의 몇 번째 블록인지 나타내는 고유 주소값
71 | 
72 | ### 1.3.3 블록 단위 I/O
73 | 데이터 I/O 단위는 블록이다. 한 레코드만 읽고 싶어도 그 레코드가 포함된 해당 블록을 통째로 읽음(테이블, 인덱스 모두 해당)
74 | ### 1.3.4 시퀀셜 액세스 vs 랜덤 액세스
75 | 테이블 또는 인덱스 블록을 액세스하는 방법
76 | - 시퀀셜 액세스: 논리적 혹은 물리적으로 연결된 순서에 따라 차례로 블록을 읽는 방식
77 | - 인덱스 리프 노드로 범위 검색
78 | - 테이블 세그먼트 헤더 맵으로 풀 테이블 스캔
79 | - 랜덤 엑세스: 레코드 하나를 읽기 위해 한 블록씩 접근하는 방식
80 | ### 1.3.5 논리적 I/O vs 물리적 I/O
81 | - 자주 읽는 블록을 매번 디스크에서 읽는 것은 비효율적! -> 캐싱!
82 | - DB Buffer Cache: 디스크에서 읽은 데이터 블록을 캐시, 공유메모리 영역
83 | - 데이터 블록을 읽을 땐 항상 버퍼 캐시부터 탐색
84 | - 논리적 I/O: SQL을 처리하는 과정에서 메모리 버퍼 캐시에서 발생한 총 블록 I/O
85 | - 논리적 I.O (유사) 메모리 I/O
86 | - 물리적 I/O: 디스크에서 발생한 총 블록 I/O
87 | - 메모리 I/O(전기적 신호) <<<<< 디스크 I/O(액세스 암을 통해 물리적 작용) 대략 10,000배 느림
88 | - 버퍼캐시 히트율(Buffer Cache Hit Ratio, BCHR) = (논리적 I/O - 물리적 I/O) / 논리적 I/O * 100
89 | - OLTP 애플리케이션은 시스템 레벨에서 평균 99% 히트율을 달성해야 한다
90 | - BCHR은 시스템 상황에 따라 달라지므로 물리적 I/O는 결국 통제 불가능한 외생변수
91 | - 논리적 I/O는 내생변수
92 |
93 | => SQL 튜닝을 통해 논리적 I/O를 줄이고, 그로 인해 물리적 I/O도 줄어들어 성능을 향상시키는 것!
94 | ### 1.3.6 Single Block I/O vs Multiblock I/O
95 | - Single block I/O: 한 번에 한 블록씩 요청해서 메모리에 적재하는 방식
96 | - 인덱스를 사용하는 방식
97 | - Multiblock I/O: 한 번에 여러 블록씩 요청해서 메모리에 적재하는 방식
98 | - 테이블 전체 스캔
99 | - 많은 데이터를 가져올 때 multiblcok I/O 단위가 클수록 유리
100 | - multiblock I/O를 이용해도 인접 블록만 가져올 수 있다(같은 익스텐트 내에서면 여러 블록씩 가져올 수 있다는 이야기!)
101 | ### 1.3.7 Table Full Scan vs Index Range Scan
102 | - Table Full Scan이라고 항상 느리고, Index Range Scan이라고 항상 빠른 것이 아니다
103 | - 많은 데이터를 처리하는 집계용 SQL, 배치 프로그램 등은 Table Full Scan을 사용해서 자주 성능이 향상된다
104 | ### 1.3.8 캐시 탐색 메커니즘
105 | - Direct Path I/O를 제외한 모든 블록 I/O는 메모리 버퍼캐시를 경유한다
106 | - 버퍼캐시는 해시 구조로 관리한다(같은 입력값은 동일한 해시 체인=버킷에 연결)
107 | - 해시 체인에 버퍼 헤더를 담고 있다, 거기서 얻은 포인터로 버퍼 블록을 액세스 한다
108 | 
109 | - 버퍼캐시, 버퍼캐시 체인은 공유자원이므로 직렬화 메커니즘이 필요하다
110 | - 캐시버퍼 체인 레치, Lock
111 |
112 | => 직렬화 메커니즘으로 경합을 줄이기 위해 SQL 튜닝을 통해 논리적 I/O를 줄여야한다
113 |
114 | ### 기타
115 | - OLTP: online transaction processing
116 | - DW: data warehouse
117 |
118 | ### 참고 링크
119 | https://myinfrabox.tistory.com/92
120 |
121 | https://velog.io/@sezzzini/%EA%B8%88%EC%9C%B5-IT-ETL-%EA%B3%BC-DW#dw
122 |
123 | ### 질문
124 | 1. 1.1장 질문
125 | - 데이터 딕셔너리가 뭔가요?
126 | - SQL plus의 Auto trace 기능?
127 | 2. 1.2장 질문
128 | 3. 1.3 장 질문
129 | - 59p Full Scan 중에 Chian이 발생한 로우를 읽을 때도 Single Block I/O 방식으로 발생. Chian이 뭐지,,?
130 | - 인덱스를 이용한 접근은 Single Block I/O를 사용한다고 했는데, 인덱스의 리프 노드로 시퀀셜 액세스 해도 Single Block I/O로 동작할까? Mutliblock I/O로 동작할까?
--------------------------------------------------------------------------------
/2장_인덱스_기본/2.2_인덱스_기본_사용법_김광훈.md:
--------------------------------------------------------------------------------
1 | # 2.2 인덱스 기본 사용법
2 | ## 2.2.1 인덱스를 사용한다는 것
3 | #### (1) Index range scan
4 | Index range scan 은 인덱스 시작점을 찾아서 거기서부터 스캔을 시작하고 중간에 멈추는 것을 의미한다.
5 |
6 | 일단 아래 쿼리를 예제로 살펴보자.
7 |
8 | ```mysql
9 | SELECT * FROM employees WHERE first_name BETWEEN 'Ebbe' AND 'Gad';
10 | ```
11 |
12 |
13 |
14 | 위 그림의 화살표를 살펴보면 루트 노드에서부터 비교를 시작해서 브랜치 노드를 거치고 최종적으로 리프 노드까지 찾아 들어가야만 비로서 실재로 원하는 시작 지점을 찾을 수 있다.
15 |
16 | 일단, 시작해야 할 위치를 찾으면 그때부터는 리프 노드의 레코드만 순서대로 읽으면 된다.
17 |
18 | 이처럼 쭉 읽는 것을 스캔이라고 한다. 만약 스캔하다가 리프 노드의 끝까지 읽으면 리프 노드 간의 링크를 이용해 다음 리프 노드를 찾아서 다시 스캔하고 반복한다.
19 |
20 | 위 예제는 인덱스만을 읽는 경우를 보여준 것이다.
21 |
22 |
23 |
24 | 실제로 B-Tree 인덱스의 리프 노드를 스캔하면서 실제 데이터 파일의 레코드를 읽어 와야 하는 경우도 많다. 이제 그 과정을 보자.
25 |
26 |
27 |
28 | 위 그림을 보면, B-Tree 인덱스에서 루트와 브랜치 노드를 이용해 특정 검색(스캔) 시작 가지고 있는 리프 노드를 검색한다.
29 |
30 | 그 이후, 필요헌 방향(오름차순 or 내림차순) 으로 인덱스를 읽어 나가는 과정을 볼 수 있다.
31 |
32 | 중요한 점은 해당 인덱스를 구성하는 컬럼의 정(역)순으로 `정렬 된 상태`로 레코드를 가져온다는 것이다.
33 |
34 | 이는 별도의 정렬 과정이 수반되는 것이 아닌 `인덱스 자체의 정렬 특성` 때문에 자동으로 그렇게 된다.
35 |
36 |
37 |
38 | 또 다른 중요한 점은 리프 노드에 저장된 레코드 주소로 데이터 파일의 레코드를 읽어온다.
39 |
40 | 이때 레코드 한 건 한 건 단위로 `랜덤 I/O` 가 한 번씩 실행된다.
41 |
42 |
43 |
44 | 예를들어 3건의 레코드가 검색 조건에 일치한다고 한다면 데이터 레코드를 읽기 위해 랜덤 I/O 가 최대 3번이 필요하다.
45 |
46 | 따라서 인덱스를 통해 읽어야 할 데이터 레코드가 20~25% 를 넘으면 인덱스를 통한 읽기보다 테이블의 데이터를 직접 읽는 것이 더 효율적이다.
47 |
48 |
49 |
50 | #### (2) Index Full scan
51 | 인덱스를 사용하는 것은 동일하지만 인덱스의 처음부터 끝까지 모두 읽는 방식이다.
52 |
53 | 대표적으로 쿼리의 조건절에 사용된 칼럼이 인덱스의 첫 번째 칼럼이 아닌 경우 인덱스 풀 스캔 방식이 사용된다.
54 |
55 | 예를 들어 인덱스는 (A, B, C) 칼럼의 순서대로 만들어져 있지만 쿼리의 조건절은 B 혹은 C 칼럼으로 검색하는 경우이다.
56 |
57 |
58 |
59 | 위 그림을 보면, 인덱스 리프 노드의 제일 앞 또는 제일 뒤로 이동 후 인덱스의 리프 노드를 연결하는 Linked List 를 따라서 처음부터 끝까지 스캔하는 것을 볼 수 있다.
60 |
61 |
62 |
63 | 참고로, Real MySQL 책에서는 인덱스 풀 스캔 방식을 사용하는 경우 인덱스를 사용하지 못한다 또는 인덱스를 효율적으로 사용하지 못한다고 표현했다.
64 |
65 |
66 |
67 | #### (3) 인덱스 정렬 --> 추가 내용
68 | 일반적인 오라클 같은 상용 DBMS 에서는 아래와 같이 인덱스 생성 시점에 오름차순 혹은 내림차순 정렬을 설정할 수 있다.
69 |
70 | ```mysql
71 | CREATE INDEX ix_teamname_userscore ON employees (team_name ASC,user_score DESC);
72 | ```
73 |
74 | 하지만 MySQL 5.7 까지는 앞으로 만들어질 버전에 대한 호환성을 위해 문법상으로만 제공이되고, 실질적으로는 조건은 무시되고 무조건 오름차순으로만 정렬이 되었다.
75 |
76 | 그 이후로 MySQL 8.0 에서 부터는 ASC, DESC 모두 지원하게 되었다.
77 |
78 |
79 |
80 | 이로인하여 인덱스를 역으로 스캔하지 않아도 되기 때문에 이전 버전에 비하여 상당히 효율적인 것을 볼 수 있다.
81 |
82 | 추가로 공식문서에서는 Descending index 는 InnoDB storage engine 만 지원된다고 적혀있다.
83 |
84 |
85 |
86 | 해당 기능에 대한 내용은 [해당 블로그](https://tech.kakao.com/2018/06/19/mysql-ascending-index-vs-descending-index/) 에서 자세히 설명하고 있다.
87 |
88 |
89 |
90 | ## 2.2.2 인덱스 Range scan 할 수 없는 이유
91 | 인덱스 스캔을 하기 위해서는 시작지점과 끝지점이 있어야 한다.
92 |
93 | 예를들어 년도에 상관 없이 5월에 태어난 학생을 찾으라는 쿼리를 작성한다고 해보자.
94 |
95 | 2010년, 2020냔, 2021년 등등 스캔해야 하는 지점이 너무 많기 때문에 이러한 케이스는 전교샹 전부를 스캔할 수 밖에 없다.
96 |
97 | 이제 쿼리를 보면서 MySQL 에서 인덱스 Range scan 이 가능한지 보자
98 |
99 |
100 |
101 | #### (1) 조건절 + OR 절
102 | ```mysql
103 | CREATE INDEX idx_test ON customer (name);
104 |
105 | SELECT *
106 | FROM customer as c
107 | WHERE (c.name = '광훈' OR c.phone_number = '01044445555');
108 | ```
109 |
110 |
111 |
112 | 위 결과를 보면, 전화번호가 XXX 이고 이거나 이름이 XXX 인 어느 한 시작지점을 찾을 수 없기 때문에 Index range scan 을 할 수 없다.
113 |
114 |
115 |
116 | #### (2) OR Expansion
117 | ```mysql
118 | CREATE INDEX idx_test ON customer (name);
119 |
120 | SELECT *
121 | FROM customer as c
122 | WHERE c.name = '광훈'
123 | UNION ALL
124 | SELECT *
125 | FROM customer as c
126 | WHERE c.phone_number = '01044445555'
127 | AND (c.name != '광훈' or c.name is null);
128 | ```
129 |
130 |
131 |
132 | Unique Index Scan 으로 동작했기 때문에 시작과 끝만 정의를 한다면 Index Range scan 으로 동작할 것을 알 수 있다.
133 |
134 |
135 |
136 | #### (3) IN 절
137 |
138 | ```mysql
139 | CREATE INDEX idx_test ON customer (name);
140 |
141 | SELECT *
142 | FROM customer as c
143 | WHERE c.name in ('광훈', '세윤');
144 | ```
145 |
146 |
147 |
148 | IN 절은 OR 조건을 표현하는 다른 방식이기 때문에 역시 Index range scan 은 불가능하다.
149 |
150 | 마찬가지로 IN 절은 UNION ALL 방식으로 작성해보자.
151 |
152 | ```mysql
153 | SELECT *
154 | FROM customer as c
155 | WHERE c.name = '광훈'
156 | UNION ALL
157 | SELECT *
158 | FROM customer as c
159 | WHERE c.name = '세윤';
160 | ```
161 |
162 |
163 |
164 | Unique Index Scan 으로 동작했기 때문에 시작과 끝만 정의를 한다면 Index Range scan 으로 동작할 것을 알 수 있다.
165 |
166 |
167 |
168 | ## 2.2.3 더 중요한 인덱스 사용 조건
169 | 아래와 같이 인덱스를 (소속팀 + 사원명 + 연령) 순으로 구성한 예제를 보자.
170 |
171 |
172 |
173 | ```mysql
174 | SELECT 사원번호, 소속팀, 연령, 입사일자, 전화번호
175 | FROM 사원
176 | WHERE 사원명 = '홍길동';
177 | ```
178 |
179 | 만약 위와 같은 쿼리를 실행시켜보자.
180 |
181 | 홍길동이라는 이름을 가진 사원은 A 소속팀, B 소속팀 등등 여러명이 있을 수 있다. 따라서 이름이 같을 지라도 서로 멀리 떨어지게 된다.
182 |
183 |
184 |
185 | 따라서 해당 쿼리로는 인덱스 스캔 시작점을 찾을 수 없고 어디서 멈춰야될지 모르기 때문에 인덱스 리프 블록을 처음부터 끝까지 모두 스캔해야 한다.
186 |
187 | 정리하자면, 인덱스를 Range scan 하기 위한 가장 첫 번째 조건은 인덱스 선두 컬럼이 조건절에 있어야 한다. (가공하지 않은 상태로)
188 |
189 |
190 |
191 | ## 2.2.4 인덱스를 이용한 소트 연산 생략
192 | 인덱스를 사용하면 인덱스가 정렬되어 있기 때문에 소트 연산 생략 효과도 부수적으로 얻을 수 있다.
193 |
194 |
195 |
196 |
197 | ```mysql
198 | -- 인덱스 생성
199 | CREATE INDEX idx_test ON equipment (number, updated_at, updated_order );
200 |
201 | -- 소트 연산 생략 예제
202 | SELECT *
203 | FROM equipment as e
204 | WHERE e.number = 'C' AND e.updated_at = '20180507' ORDER BY e.updated_order;
205 | ```
206 |
207 |
208 |
209 | 위와 같이 장비번호와 변경일자를 모두 '=' 조건으로 검색할 때 PK 인덱스를 사용하면 결과집합은 변경순번 순으로 출력된다.
210 |
211 | 실행 결과를 살펴보면 소트 연산은 실행되지 않는 것으로 보인다.
212 |
213 |
214 |
215 | ## 2.2.5 ORDER BY 절에서 컬럼 가공
216 | "인덱스 컬럼을 가공하면 인덱스를 정상적으로 사용할 수 없다." 는 말은 주로 조건절에서 사용한 칼럼을 말한다.
217 |
218 | 그런데 조건절이 아닌 ORDER BY 또는 SELECT-LIST 에서 컬럼을 가공함으로 인해 인덱스를 정상적으로 사용할 수 없는 경우도 종종 있다.
219 |
220 | ```mysql
221 | -- ORDER BY 에 가공 된 컬럼 제공 예제
222 | -- 인덱스 생성
223 | CREATE INDEX idx_test ON equipment (number, updated_at, updated_order );
224 |
225 | SELECT *
226 | FROM equipment as e
227 | WHERE e.number = 'C'
228 | ORDER BY e.updated_order, e.updated_at;
229 | ```
230 |
231 | 인덱스에는 가공하지 않은 상태로 값을 저장하였는데, 가공한 값 기준으로 정렬해 달라고 요청했기 떄문에 정렬 연산을 생략할 수 없다. (오라클 기준)
232 |
233 |
234 |
235 | 하지만 MySQL 에서는 실행결과를 확인해보면 Sort 연산이 실행이 된 것을 볼 수 있다.
236 |
237 |
238 |
239 | ## 2.2.6 SELECT-LIST 에서 컬럼 가공
240 | ```mysql
241 | -- MIN, MAX 함수 예제
242 | SELECT MIN(e.updated_order)
243 | FROM equipment as e
244 | WHERE e.number = 'C' AND e.updated_at = '20180507';
245 |
246 | SELECT MAX(e.updated_order)
247 | FROM equipment as e
248 | WHERE e.number = 'C' AND e.updated_at = '20180507';
249 |
250 | ```
251 |
252 |
253 | 위와 같이 최대(최소 값)을 구하는 연산을 할 때도 옵티마이저는 정렬 연산을 실행시키지 않는다.
254 |
255 |
256 |
257 | ## 2.2.7 자동 형변환
258 | MySQL 5.7/8.0 에서는 자동으로 서로 다른 타입을 비교하거나 연산을 할 때, 자동으로 형변환하는 것을 지원해준다.
259 |
260 |
261 |
262 | ```mysql
263 | drop index idx_test on customer;
264 | CREATE INDEX idx_test ON customer ( birth_day );
265 |
266 | SELECT *
267 | FROM customer as c
268 | WHERE c.birth_day = 19940318;
269 | ```
270 |
271 |
272 |
273 | 실행계획을 보면 인덱스 Range scan 으로 동작하지 않은 것 을 볼 수 있다.
274 |
275 | 고객 테이블 생년월일 컬럼이 문자형인데 조건절 비교 값을 숫자형으로 표현했기 때문에 옵티마이저는 인덱스 컬럼을 가공했기 때문에 Full Scan 으로 동작을 한 것이다.
276 |
277 |
--------------------------------------------------------------------------------
/2장_인덱스_기본/2.3_인덱스_확장기능_사용법_황준호.md:
--------------------------------------------------------------------------------
1 | ### 2.3 인덱스 확장기능 사용법
2 |
3 | #### Index Range Scan
4 |
5 | - B-Tree 인덱스의 가장 일반적이고 정상적인 형태의 액세스 방식
6 | - 인덱스 루트에서 리프 블록까지 수직적으로 탐색한 후 필요한 범위만 스캔한다
7 | - 인덱스를 Range Scan 하려면 선두 컬럼을 가공하지 않은 상태로 조건절에서 사용해야 한다
8 | - 반대로 말하면 선두 컬럼을 가공만 하지 않으면 Range Scan은 무조건 가능하므로 인덱스를 탄다고 성능도 무조건 좋다고 생각하면 안된다
9 | - 성능은 인덱스 스캔 범위, 테이블 엑세스 횟수를 얼마나 줄일 수 있느냐로 결정된다
10 |
11 | #### Index Full Scan
12 |
13 | - 수직적 탐색 없이 인덱스 리프 블록을 처음부터 끝까지 수평적으로 탐색하는 방식
14 | - 대개 데이터 검색을 위한 최적의 인덱스가 없을때 차선으로 선택됨
15 |
16 | #### Index Unique Scan
17 |
18 | - 수직적 탐색으로만 데이터를 찾는 스캔 방식
19 | - unique 인덱스를 '=' 조건으로 탐색하는 경우 Index Unique Scan
20 | - 단, unique 인덱스라 하더라도 범위검색 조건(between, 부등호, like)으로 검색할떄는 Index Range Scan으로 처리된다
21 | - 또, unique 결합 인덱스에 대해 일부 컬럼만으로 검색할 때도 Index Range Scan으로 처리된다
22 | - 예 : 주문상품 PK 인덱스를 "주문일자 + 고객id + 상품id"로 구성했는데 "주문일자 + 고객id"로만 검색하는 경우
23 |
24 | #### Index Skip Scan
25 |
26 | - 루트 또는 브랜치 블록에서 읽은 컬럼 값 정보를 이용해 조건절에 부합하는 레코드를 포함할 가능성이 있는 리프 블록만 골라서 엑세스하는 스캔 방식
27 | - 인덱스 선두 컬럼의 카디널리티가 낮고, 후행 컬럼의 카디널리티가 높을때 유용하다
28 |
29 | #### Index Fast Full Scan
30 |
31 | - Index Full Scan보다 빠른 스캔 방법
32 | - 논리적인 인덱스 트리 구조를 무시하고 인덱스 세그먼트 전체를 Multiblock I/O방식으로 스캔하기 때문
33 | - 특징
34 | - Multiblock I/O방식을 사용하므로 디스크로부터 대량의 인덱스 블록을 읽어야 할 때 큰 효과를 발휘한다
35 | - 결과 집합이 인덱스 키 순서대로 정렬되지 않는다
36 | - 쿼리에 사용한 컬럼이 모두 인덱스에 포함돼 있을때만 사용 가능하다
37 | - 인덱스카 파티션 돼 있지 않더라도 병렬 쿼리가 가능하다
38 |
39 | #### Index Range Scan Descending
40 |
41 | - Index Range Scan과 기본적으로 동일하지만 인덱스를 뒤에서부터 스캔하기 때문에 내림차순으로 정렬된 결과집합을 얻는다
42 |
43 | #### 참고. MySQL의 실행계획
44 |
45 | - id : 각 SELECT문에 부여됨
46 | - select_type
47 | - SIMPLE : 단순 SELECT문
48 | - table : 접근하는 테이블 이름
49 | - partitions
50 | - type
51 | - ALL : 테이블 풀 스캔
52 | - index : 인덱스 풀 스캔
53 | - range : 인덱스 레인지 스캔
54 | - const : PK 혹은 UK로 조회하는 경우. 많아야 한 건
55 | - possible_keys : 사용 가능한 인덱스들
56 | - key : possible_keys중 실제로 사용할 인덱스
57 | - key_len : 인덱스에 얼마나 많은 바이트를 사용하고 있는지
58 | - ref
59 | - rows : 원하는 행을 찾기 위해 얼마나 많은 행을 읽어야 할 지에 대한 예측값
60 | - filtered : 행 데이터를 가져와 거기에서 WHERE 구의 검색 조건이 적용되면 몇행이 남는지 예측값
61 | - Extra
62 | - Using where : where 조건으로 데이터를 추출. type이 ALL 혹은 INDEX 타입과 함께 표현되면 성능이 좋지 않다는 의미
63 | - Using filesort : 데이터 정렬이 필요한 경우로 메모리 혹은 디스크상에서의 정렬을 모두 포함. 결과 데이터가 많은 경우 성능에 직접적인 영향을 줌
64 | - Using index condition
65 | - Using temporary
66 | - Select tables optimized away
67 |
--------------------------------------------------------------------------------
/2장_인덱스_기본/2주_김민석.md:
--------------------------------------------------------------------------------
1 | # 2장 인덱스 기본
2 |
3 |
4 | ## 인덱스 구조 및 탐색
5 |
6 | - 데이터를 찾는 두 가지 방법
7 | - 테이블 전체 스캔
8 | - 인덱스 이용
9 |
10 |
11 | - 온라인 트랜잭션 처리에서는 소량의 데이터를 찾기 때문에 인덱스를 사용하고 인덱스 튜닝이 중요하다
12 |
13 |
14 | - SQL 튜닝은 랜덤 I/O와의 전쟁
15 |
16 |
17 | - 인덱스는 대부분 B-Tree
18 | - 수직적 탐색 : 인덱스 스캔 지점을 찾는 과정
19 | - 수평적 탐색 : 데이터를 찾는 과정
20 |
21 |
22 | - 인덱스는 필터링이 아니다. 결합인덱스를 사용할 때 주의 사항.
23 |
24 |
25 | ## 인덱스 기본 사용법
26 |
27 | - 색인이 정렬되어 있더라도(인덱스라도) 가공한 값이나 중간값으로는 스캔 시작점을 찾을 수 없다.
28 | - Index Range Scan
29 | - 인덱스 컬럼이 가공되면 전체를 스캔할 수 밖에 없다.
30 | - Index Full Scan
31 |
32 |
33 | - 인덱스를 range scan 하기 위한 가장 첫번째 조건은 인덱스 선두 컬럼이 조건절에 있어야 한다.
34 |
35 |
36 | - 인덱스는 기본적으로 정렬되어 있기 때문에 소트 연산의 생략이 가능하다
37 |
38 |
39 | - ORDER BY 나 SELECT-LIST 컬럼을 가공하면 인덱스 레인지 스캔이 불가능하다.
40 |
41 |
42 | - 인덱스의 타입을 맞추지 않으면 인덱스 사용이 불가능하다.
43 |
44 |
45 | ## 인덱스 확장기능 사용법
46 |
47 | - Index Range Scan
48 | - 루트에서 리프 블록까지 수직적 탐색 후에 필요한 범위 수평적 스캔
49 | - Index Full Scan
50 | - 수직적 탐색 없이 인덱스 리프 블록을 처음부터 끝까지 수평적 탐색
51 | - Index Unique Scan
52 | - 인덱스로 '=' 조건 검색 시.
53 | - Index Skip Scan
54 | - Index Full Scan을 사용해야 할 경우에 필요없는 부분은 skip하는 방식
55 | - 인덱스 선두 컬럼의 Distinct Value 개수가 적고 후행 컬럼의 Distinct Value 개수가 많을 때 유용
56 | - Index Fast Full Scan
57 | - Index Full Scan을 순서대로 하지 않고 인덱스 세그먼트 전체를 멀티블럭 I/O 하는 방식.
58 | - 속도도 빠르고 병렬로 읽어 올 수 있지만 결과집합이 키 순서대로 정렬되지 않는다.
59 | - Index Range Scan Descending
60 | - Index Range Scan을 필요에 따라 거꾸로 읽는 방식
61 |
62 |
--------------------------------------------------------------------------------
/2장_인덱스_기본/조수진.md:
--------------------------------------------------------------------------------
1 | # 2장. 인덱스 기본
2 |
3 | ## 2.1 인덱스 구조 및 탐색
4 | ### 2.1.1 미리 보는 인덱스 튜닝
5 | **데이터베이스 테이블에서 자료를 찾는 방법**
6 | 1. 테이블 풀 스캔
7 | 2. 인덱스 이용
8 |
9 | **인덱스 튜닝의 핵심 요소**
10 | - 인덱스 스캔 효율화 튜닝
11 | - 랜덤 액세스 최소화 튜닝
12 |
13 | **Tip!** DBMS가 제공하는 많은 기능이 느린 랜덤 I/O를 극복하기 위해 개발됨
14 | ### 2.1.2 인덱스 구조
15 | 인덱스는 B*Tree 구조 = 정렬됨 = 범위 스캔이 가능하다
16 |
17 | **BTree 구성**
18 | - 루트 노드
19 | - 브랜치 노드
20 | - 리프 노드
21 |
22 | **루트와 브랜치 블록의 구성**
23 | - 각 레코드는 하위 블록에 대한 주소값을 가짐
24 | - key 값을 가지고, 키 값은 하위 블록에 저장된 키 값의 범위를 나타냄
25 |
26 | ex) 서 → 연결된 블록은 서보다 크거나 같은 레코드가 저장됌
27 |
28 | - LMC: leftmost child 가장 왼쪽에 위치한 블럭을 가리킴
29 |
30 | → 첫 번째 키값을 가진 레코드보다 작거나 같은 레코드가 저장됌
31 |
32 | **리프 블록의 구성**
33 | - 정렬된 key 값
34 | - 테이블 레코드를 가리키는 주소값(=ROWID)
35 |
36 | ROWID = DBA(Data block address) + 로우 번호
37 |
38 | = 데이터 파일 번호 + 블록 번호 + 로우 번호
39 |
40 | **수직적, 수평적 탐색**
41 | - 수직적 탐색: 인덱스 스캔 시작 시점을 찾는 과정
42 | - 수평적 탐색: 데이터를 찾는 과정
43 | ### 2.1.3 인덱스 수직적 탐색
44 | 조건을 만족하는 첫 번째 레코드를 찾는 과정
45 |
46 | → 찾고자 하는 값 보다 크거나 같은 레코드(key)를 찾으면 그 직전 레코드가 가리키는 하위 블록으로 이동
47 |
48 | → 찾고자 하는 값과 같은 레코드를 찾아도 그 직전 레코드가 가리키는 하위 블록으로 가야 조건을 만족하는 '첫번째' 블록을 찾을 수 있음
49 | ### 2.1.4 인덱스 수평적 탐색
50 | 실제 데이터를 찾는 과정
51 |
52 | = 리프 블록에서 수평적으로 탐색하면서 찾고자 하는 데이터가 더 안나타날 때까지 스캔
53 |
54 | → 인덱스 리프는 더블 링크드 리스트 구조
55 | ### 2.1.5 결합 인덱스 구조와 탐색
56 | 결합 인덱스: 두 개 이상의 컬럼을 결합해서 인덱스를 만드는 것
57 |
58 | 인덱스를 (고객명, 성별)로 구성하든, (성별, 고객명)으로 구성하든 읽는 인덱스 블록 개수는 똑같다
59 |
60 | where에서 '=' 조건 검색할 때는 어느 컬럼을 인덱스 앞쪽에 두든 블록 I/O 개수가 같다(= 성능이 똑같다)
61 |
62 | 인덱스 구성에 따라 성능 차이가 나는 것은 맞지만, 엑셀의 필터처럼 생각하면 안된다!
63 |
64 | ## 2.2 인덱스 기본 사용법
65 |
66 | ### 2.2.1 인덱스를 사용한다는 것
67 |
68 | Index Full Scan vs Index Range Scan
69 |
70 | 일반적으로 인덱스를 정상적으로 사용한다는 것은 Index Range Scan을 의미
71 |
72 | = 리프 블록에서 스캔 시작점을 찾아서 중간에 멈추는 지점을 찾아 색인을 멈추는 것
73 |
74 | → 인덱스 컬럼(정확히는 선두 컬럼)을 가공하거나 인덱스의 중간에 포함된 값으로 색인을 하고자 하는 경우 Index Range Scan이 불가능하고 Index Full Scan을 할 수 밖에 없다
75 |
76 | ### 2.2.2 인덱스 Range Scan 할 수 없는 이유
77 |
78 | 아래와 같이 컬럼을 가공하는 경우, 컬럼의 중간에 위치하는 값으로 검색하는 경우, or이나 in 절로 검색하는 경우는 Index Range Scan이 불가능하다
79 |
80 | 하지만 Or, In 절은 옵티마이저의 쿼리 변환 기능을 통해 여러 개의 Index Range Scan으로 처리되기도 한다
81 |
82 | → IN-List Iterator 방식, union all
83 |
84 | where substr(생년월일, 5, 2) = ‘05’
85 |
86 | where nvl(주문수량, 0) < 100
87 |
88 | where 업체명 like ‘%대한%’
89 |
90 | where (전화번호 =: tell_no or 고객명 =: cust_nm)
91 |
92 | where 전화번호 in (:tell_no1, :tell_no2)
93 |
94 | ### 2.2.3 더 중요한 인덱스 사용 조건
95 |
96 | 인덱스 Range Scan을 하기 위해서는 반드시 **인덱스 선두 컬럼**이 **가공되지 않은 상태로** 조건절에 있어야 한다
97 |
98 | 그렇다면 무조건 Range Scan은 가능하다
99 |
100 | 하지만 인덱스를 잘 탄다고 해서 튜닝이 끝난 것은 아니다
101 |
102 | 예를 들어, 인덱스가 `주문일자 + 상품번호` 순으로 구성되어 있고, 하루 평균 100만 건의 데이터가 쌓인다면
103 |
104 | `Select * from 주문상품 where 주문일자 =: ord_dt and 상품번호 like ‘%ping%’;`
105 |
106 | 위의 쿼리는 매 번 100만 건의 데이터를 스캔해야함!
107 |
108 | ### 2.2.4 인덱스를 이용한 소트 연산 생략
109 |
110 | 인덱스는 정렬된 상태로 저장되어 있다 → select 의 order by 연산을 생략할 수 있다
111 |
112 | 오름 차순의 경우, 조건을 만족하는 가장 작은 값부터 왼쪽에서 오른쪽으로 스캔을 하고,
113 |
114 | 내림차순의 경우 조건을 만족하는 가장 큰 값인 오른쪽에서부터 왼쪽으로 스캔함
115 |
116 | ### 2.2.5 ORDER BY 절에서 컬럼 가공
117 |
118 | `select * from (select to_char(a.주문번호, ‘FM000000’) as 주문번호, a.업체번호, a.주문금액 from 주문 a where a.주문일자 =: dt and a.주문번호 > NVL(:next_ord_no, 0) order by 주문번호)
119 | where rownum < = 30`
120 |
121 | → order by 절에 사용된 주문번호는 to_char로 가공된 주문번호 → sort 가 일어어난다
122 |
123 | → a.주문번호로 바꿔주면 sort 생략가능
124 |
125 | ### 2.2.6 SELECT-LIST에서 컬럼 가공
126 |
127 | `SELECT MIN(변경순번) FROM 상태변경이력 WHERE 장비번호 = 'C' AND 변경일자 = '20180316'`
128 |
129 | `SELECT MAX(변경순번) FROM 상태변경이력 WHERE 장비번호 = 'C' AND 변경일자 = '20180316'`
130 |
131 | → 인덱스를 이용해 정렬 연산 없이 최소, 최대값을 빠르게 찾을 수 있다
132 |
133 | 실행계획에는 INDEX RANGE SCAN (MIN/MAX) 상태변경이력_PK, FIRST ROW라고 뜸
134 |
135 | `SELECT NVL(MAX(TO_NUMBER(변경순번)), 0) FROM 상태변경이력 WHERE 장비번호 = 'C' AND 변경일자 = '20180316'`
136 |
137 | → 이런식으로 짜면 인덱스 컬럼에 수정이 생기므로 정렬 연산을 줄일 수 없다
138 |
139 | ⇒ `SELECT NVL(TO_NUMBER(MAX(변경순번)), 0) FROM 상태변경이력 WHERE 장비번호 = 'C' AND 변경일자 = '20180316'`로 수정 가능
140 |
141 | - 서브 쿼리에서 사용할 경우도 마찬가지
142 |
143 | ```sql
144 | SELECT 장비번호, 장비명, 상태코드
145 | , (SELECT MAX(변경일자))
146 | FROM 상태변경이력
147 | WHERE 장비번호 = P.장비번호) 최종변경일자
148 | , (SELECT MAX(변경순번)
149 | FROM 상태변경이력
150 | WHERE 장비번호 = P.장비번호
151 | AND 변경일자 = (SELECT MAX(변경일자)
152 | FROM 상태변경이력
153 | WHERE 장비번호 = P.장비번호)) 최종변경순번
154 | FROM 장비 P
155 | WHERE 장비구분코드 = 'A001'
156 | ```
157 |
158 | ### 2.2.7 자동 형변환
159 |
160 | 조건절에서 입력되는 데이터 타입과 실제 테이블 타입이 다르면 컴파일 시점에 에러를 내는 DBMS도 있고 Oracle처럼 형변환해주는 DBMS도 있다. 인덱스 컬럼에 자동 형변환이 걸리면 인덱스 사용이 불가능하거나 효율이 떨어지므로 주의해야함!
161 |
162 | - 우선 순위: 숫자형 > 날짜형 > 문자형
163 |
164 | ex. `SELECT * FROM 고객 WEHRE 생년월일 = 19821225`
165 |
166 |
167 | → 문자열 타입인 생년월일이 숫자 타입으로 형변환되면서 인덱스를 사용할 수 없게 됨 ⇒ 테이블 풀스캔
168 |
169 | - Like는 문자열 비교 연산자이므로 문자형 기준으로 숫자형 컬럼이 변환된다
170 |
171 | ex. `SELECT * FROM 고객 WEHRE 고객번호 LIKE '9410%'` ⇒ 테이블 풀스캔
172 |
173 | - 형변환에 의해 실행 에러가 발생할 수 있다
174 |
175 | ex. 문자형 컬럼을 숫자형으로 변경할 때, 숫자로 변경할 수 없는 문자열이 있는 경우
176 |
177 | - 형변환에 의해 결과 오류가 발생할 수 있다
178 |
179 | ex. `select round(avg(sal)) avg_sal, min(sal) min_sal, max(sal) max_sal, max(decode(job, 'PRESDENT', NULL, sal)) max_sal2 from emp`
180 |
181 | → president를 제외한 급여 중 가장 큰 급여 값이 950으로 나온다
182 |
183 | → decode(a, b, c, d)를 처리할 때, a=b이면 c를 반환하고 아니면 d를 반환. 그 때, 데이터 타입은 세번째 인자 c에 의해 결정되는데, Null은 varchar2로 취급된다
184 |
185 | ⇒ sal이 문자열로 리턴되고 문자열 기준 max가 동작됨
186 |
187 |
188 | ⇒ 결론! 자동 형변환에 의존하지말고 명시적으로 형변환 해줄 것(TO_CAHR, TO_DATE, TO_NUMBER)
189 |
190 | ## 2.3 인덱스 확장기능 사용법
191 |
192 | ### 2.3.1 Index Range Scan
193 |
194 | - BTree인덱스의 가장 일반적이고 정상적인 액세스 방식
195 | - 인덱스 루트에서 리프 블록까지 수직적 탐색 → 필요한 범위 스캔
196 | - 인덱스의 선두 컬럼을 가공하지 않은 상태로 조건절에서 사용해야 한다
197 | - 성능을 위해서는 인덱스를 스캔하는 범위, 테이블 액세스 횟수를 얼마나 줄일 수 있느냐로 결정된다
198 |
199 | ### 2.3.2 Index Full Scan
200 |
201 | - 수직적 탐색 없이 인덱스 리프 블록을 처음부터 끝까지 수평적으로 탐색하는 방식
202 | - 데이터 검색을 위한 최적의 인덱스가 없을 때, I/O를 줄이기 위해 Table Full Scan 대신 차선으로 사용
203 |
204 | (ex. 대용량 테이블이어서 Table Full Scan 비용이 크다면 = 대용량 데이터 중 아주 일부 테이블을 액세스 하는 상황)
205 |
206 |
207 | ```sql
208 | create index emp_ename_sal_idx on emp (ename, sal);
209 | select * from emp where sal > 2000 order by ename;
210 | ```
211 |
212 | - 정렬된 결과를 쉽게 얻기 위해(소트 연산을 생략) 옵티마이저가 전략적으로 선택할 수 있다
213 |
214 | → first rows 힌트로 옵티마이저 모드를 변경해서 최초 N건 조회. But fetch를 멈추지않고 데이터를 끝까지 읽는다면 Table Full Scan보다 느리다
215 |
216 |
217 | ```sql
218 | select /*+ first_rows */from emp where sal > 1000 order by ename;
219 | ```
220 |
221 | ### 2.3.3 Index Unique Scan
222 |
223 | - 수직적 탐색만으로 데이터를 찾는 스캔 방식
224 | - Unique 인덱스를 '='조건으로 탐색하는 경우
225 | - Unique 인덱스라도 범위 검색(between, 부등호, like)을 할 때는 Index Range Scan
226 | - Unique 결합 인덱스에 대해 일부 컬럼으로만 검색할 때도 Index Range Scan
227 |
228 | ### 2.3.4 Index Skip Scan
229 |
230 | - 오라클 9i에서 새로 선보인 스캔 방식(MySQL에서는 Loose Index Scan)
231 | - 조건절에 부합하는 레코드를 포함할 '가능성이 있는' 리프 블록만 골라서 액세스하는 스캔 방식
232 | - index_ss, no_index_ss 힌트를 사용해서 스캔 방식 유도
233 | - 인덱스 선두 컬럼의 Distinct Value 개수가 적고 후행 컬럼의 Distinct Value 개수가 많을 때 유용
234 | - 인덱스의 빠진 컬럼의 경계 조건에 해당하는 블럭은 꼭 포함해야함!
235 | - 적용 예시: 복합 인덱스의 선두 혹은 중간 컬럼이 없는 경우, 선두 컬럼이 범위 조건인 경우
236 | - Index Range Scan이 불가능하거나 효율적이지 못한 상황에서 Index Skip Scan을 차선으로 택할 수 있다
237 |
238 | ### 2.3.5 Index Fast Full Scan
239 |
240 | - Index Full Scan 보다 빠르다
241 | - 이유: 인덱스 트리 구조를 무시하고 인덱스 세그먼트 전체를 Multiblock I/O 스캔
242 | - 루트와 브랜치도 읽지만 필요없는 정보라 버림
243 | - 속도는 빠르지만 결과 지합이 인덱스 키 순서대로 정렬되지 않음
244 | - 쿼리에서 사용한 컬럼이 모두 인덱스에 포함되어 있어야 사용 가능
245 | - index_ffs, no_index_ffs 힌트 사용
246 | - Index Range Scan, Index Full Scan과 달리 인덱스가 파티션 돼 있지 않더라도 병렬 쿼리(스캔) 가능
247 | - 병렬 쿼리 시에는 Direct Path I/O 방식을 사용해서 I/O 속도가 더 빨라진다
248 |
249 | ### 2.3.6 Index Range Scan Descending
250 |
251 | - Index Range Scan과 기본적으로 동일
252 | - 뒤에서부터 앞으로 스캔하기 때문에 내림차순으로 정렬된 결과 집합을 얻는다
253 |
254 | ### 질문
255 | - 83p: 루트로부터 모든 리프 블록까지 높이가 항상 같다 → 대량으로 insert되거나 delete되는 경우는 바로 밸런싱되나?
256 | - 그림 2-1 리프 노드가 논리적으로는 시퀀셜인데 실제 물리적으로 연결된 곳에 위치하는 건 아니지 않을까,,,?
257 | - union all?? union은 합집합 기능
258 | - 99p 참고로 애초에 발견한 SQL의 ORDER BY 절에는 ‘주문번호’가 아니라 ‘1’이라고 적혀있었다. ‘1’은 SELECT-LIST에 나열된 첫 번째 컬럼을 의미한다??? 무슨말이지!
259 | - select-list → SELECT와 FROM 사이 - 원하는 COLUMN(속성)만 조회
260 | - 102p 마지막 쿼리 이해가 잘 안됨,, SUBSTR? || 가 인덱스 컬럼을 가공?
261 | - cr, pr? 읽은 블록?
262 |
263 | ### 질문
264 | - 83p: 루트로부터 모든 리프 블록까지 높이가 항상 같다 → 대량으로 insert되거나 delete되는 경우는 바로 밸런싱되나?
265 | - 그림 2-1 리프 노드가 논리적으로는 시퀀셜인데 실제 물리적으로 연결된 곳에 위치하는 건 아니지 않을까,,,?
266 |
--------------------------------------------------------------------------------
/3장_인덱스_튜닝/3.1_테이블_엑세스_최소화_김광훈.md:
--------------------------------------------------------------------------------
1 |
2 | # 3.1 테이블 엑세스 최소화
3 | ## 서론
4 | 1. 아무리 데이터가 많아도 인덱스를 사용하면 데이터가 금방 조회가 된다.
5 |
6 | 2. 대량 데이터를 조회할 때, 인덱스를 사용하면 테이블 전체 스캔보다 느리다.
7 |
8 | ## ROWID
9 | #### 인덱스 스캔을 하는 이유
10 | 검색 조건을 만족하는 소량의 데이터를 인덱스에서 빨리 찾고 거기서 테이블 레코드를 찾아가기 위한 주소 값인 ROWID 를 얻으려는데 있다.
11 |
12 | ROWID 는 물리적주소보다는 논리적주소에 가깝다. 디스크 상에서 테이블 레코드를 찾아가기 위한 위치 정보를 담는다.
13 |
14 | ## 메인 메모리 DB 와 비교
15 | #### 메인 메모리 디비(MMDB)
16 | 데이터를 모두 메모리에 로드해 놓고 메모리를 통해서만 I/O 를 수행
17 |
18 | #### 속도 차이 ??
19 | 오라클과 같은 RDB 는 인덱스에 디스크 상의 주소를 가짐
20 |
21 | 메인 메모리 DB 는 메모리상의 주소정보, 즉 포인터를 갖고 있기 떄문에 인덱스를 경유해 테이블 엑세스하는 비용이 낮다.
22 |
23 | 인덱스 ROWID 는 다시 정리하면 디스크 상에서 테이블 레코드를 찾아가기 위한 논리적인 주소 정보이다.
24 |
25 | ROWID 가 가리키는 테이블 블록을 버퍼캐시에서 먼저 찾아보고 못 찾을 때만 디스크에서 블록을 읽는다. 물론 버퍼캐시에 적재한 후에 읽는다.
26 |
27 | 설령 모든 데이터가 캐싱되어 있더라고 테이블 레코드를 찾기 위해 매번 DBA 해싱과 레치 획득 과정을 반복해야한다.
28 |
29 | 동시 액세스가 심할 때는 캐시버퍼 체인 래치와 Lock 에 대한 경합도 발생한다.
30 |
31 |
32 | ## 인덱스 클러스터링 팩터
33 | 특정 컬럼을 기준으로 같은 값을 갖는 데이터가 서로 모여있는 정도를 의미한다.
34 |
35 | CF 가 좋은 컬럼에 생성한 인덱스는 검색 효율이 매우 좋다.
36 |
37 |
38 | ## 온라인 프로그램 튜닝 vs 배치 프로그램 튜닝
39 | #### 1. 온라인 프로그램 튜닝
40 | 보통 소량의 데이터를 읽고 갱싱하므로 인덱스를 효과적으로 활용하는 것이 중요하다.
41 |
42 | #### 2. 배치 프로그램 튜닝
43 | 대량 데이터를 읽고 갱깃ㄴ하는 배치 프로그램은 항상 전체범위 처리 기준으로 튜닝해야한다.
44 |
45 | 전체를 빠르게 처리하는 것을 목표로 삼아야한다.
46 |
47 | ## 인덱스 컬럼 추가
48 | 테이블 엑세스 최소화를 위해 가장 일반적으로 사용하는 튜닝 기법은 인덱스에 컬럼을 추가하는 것이다.
49 |
50 | ## 클러스터 테이블
51 | #### 1. 인덱스 클러스터 테이블
52 | 클러스터 키 값이 같은 레코드를 한 블록에 모아서 저장하는 구조
53 |
54 | #### 2. 해시 클러스터 테이블
55 | 인덱스를 사용하지 않고 해시 알고리즘을 상요해 클러스터를 찾아간다.
--------------------------------------------------------------------------------
/3장_인덱스_튜닝/3.3.1~3.3.4_인덱스_스캔_효율화_유효정.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Meet-Coder-Study/nice-sql-tuning/6ebb1473a41c04d97351f3527a5c2311b4fc4973/3장_인덱스_튜닝/3.3.1~3.3.4_인덱스_스캔_효율화_유효정.pdf
--------------------------------------------------------------------------------
/3장_인덱스_튜닝/3.3.5~3.4.내용_요약_김광훈.md:
--------------------------------------------------------------------------------
1 | # 3.3.5 인덱스 선행 컬럼이 등치 조건이 아닐 때 생기는 비효율
2 | 인덱스 스캔 효율성은 인덱스 컬럼을 조건절에 모두 등치 조건으로 사용할 때 가장 좋다.
3 |
4 | 리프 블록을 스캔하면서 읽은 레코드는 하나도 걸러지지 않고 모두 테이블 엑세스로 이어지므로 인덱스 스캔 단게에서의 비효율은 전혀 없다.
5 |
6 |
7 | # 3.3.6 BETWEEN 을 IN-LIST 로 전환
8 | BETWEEN 조건을 IN-LIST 로 바꿀 수 있다.
9 |
10 | IN-List 개수만큼 UNION ALL 브랜치가 생성되고 각 브랜치마다 모든 칼럼을 '=' 조건으로 검색하므로 앞서 선두 컬럼에 BETWEEN 을 사용할 때와 같은 비효율은 사라진다.
11 |
12 | # 3.3.8 IN 조건은 '=' 인가
13 | 정리하자면 IN 조건은 = 이 아니다. IN 조건이 '=' 이 되려면 IN-List Iterator 방식으로 풀려야한다.
14 |
15 | 그렇지 않다면 IN 조건은 필터 조건이다.
16 |
17 | 즉, IN 은 엑세스 조건 또는 필터 조건으로 사용할 수 있다.
18 |
19 | # 3.3.9 BETWEEN 과 LIKE 스캔 범위 비교
20 | 결론부터 말하자면 LIKE 보다 BETWEEN 을 사용하는 것이 낫다.
21 |
22 | BETWEEN 은 다소 불편하지만 LIKE 보다 효율적이다. 예를들어 `WHERE LIKE '2019%' AND 판매구분 = 'B'` 로 쿼리를 날린다면 201901, 201900 등등 계속 찾아야한다.
23 |
24 | # 3.3.11 다양한 옵션 조건 처리 방식의 장단점 비교
25 | 인덱스 선두 컬럼에는 OR 조건을 사용해서는 안된다. --> 인덱스 스캔을 못함
26 |
27 | OR 조건 정리
28 | ```
29 | 1. 인덱스 액세스 조건으로 사용 불가
30 | 2. 인덱스 필터 조건으로도 사용 불가
31 | 3. 테이블 필터 조건으로만 사용 가능
32 | ```
33 |
34 | # 3.4.1 인덱스 설계가 어려운 이유
35 | 인덱스가 많으면 생기는 문제
36 | ```
37 | 1. DML 성능 저하
38 | 2. 데이터베이스 사이즈 증가
39 | 3. 데이터베이스 관리 및 운영 비용 상승
40 | ```
41 |
42 | # 3.4.2 가장 중요한 두 가지 선택 기준
43 | Index Range Scan 을 사용하기 위해서는 인덱스 선두 컬럼을 조건절에 반드시 사용해야 한다.
44 |
45 | 따라서 결합 인덱스를 구성할 때 첫 번째 기준은, 조건절에 항상 사용하거나, 자주 사용하는 컬럼을 선정하는 것이다.
46 |
47 | 두 번째 기준은, 그렇게 선정한 컬럼 중 '=' 조건으로 자주 조회하는 컬럼을 앞쪽에 두어야 한다.
48 |
49 | # 3.4.5 소트 연산을 생략하기 위한 컬럼 추가
50 | 인덱스는 항상 정렬 상태를 유지하므로 ORDER BY, GROUP BY 를 위한 소트 연산을 생략할 수 있게 해 준다.
51 |
52 | 따라서 조건절에 사용하지 않는 컬럼이더라도 소트 연산을 생략할 목적으로 인덱스 구성에 포함시킴으로써 성능 개선에 도모할 수 있다.
53 |
54 | # 3.4.6 결합 인덱스 선택도
55 | 인덱스 생성 여부를 결정할 때는 선택도가 충분히 낮은지가 중요한 판단 기준임
56 |
57 | 선택도: 전체 레코드 중에서 조건절에 의해 선택되는 레코드 비율 -> 선택도 x 총 레코스 = 카디널리티
58 |
59 | 인덱스 선택도: 인덱스 칼럼을 모두 '=' 로 조회할 때 평균적으로 선택되는 비율 -> 비율이 높으면 인덱스로서의 효용가치가 없다.
--------------------------------------------------------------------------------
/3장_인덱스_튜닝/3.3.5~3.4_황준호.md:
--------------------------------------------------------------------------------
1 | ### 3.3 인덱스 스캔 효율화
2 |
3 | - 인덱스 선두 컬럼이 = 조건일때 효율적이다
4 |
5 | - 인덱스 선행 컬럼이 범위 검색일 경우 비효율적이다
6 | - 범위 검색을 하는 컬럼을 인덱스 선행 컬럼이 아니도록 수정하는 게 어렵다면 between을 in으로 바꾸면 효과를 얻는 경우도 있다
7 | - 하지만 in-list 개수가 많다면 더 비효율적으로 될수도 있다
8 | - 바꾸기 전에 데이터 분포와 수직적 탐색 비용을 고려해야 한다
9 |
10 | - between보단 like를 쓰는게 더 효율적이다
11 |
12 | - between, like 패턴을 사용하고자 할 땐 아래의 경우에 속하는지 고려해야 한다
13 |
14 | 1. 인덱스 선두 컬럼에 대한 옵션 조건을 between, like 연산자로 처리하면 안된다
15 | 2. null 허용 컬럼에 대한 옵션 조건을 between, like 연산자로 처리하면 안된다
16 | 3. 숫자형 컬럼에 대한 옵션 조건을 like연산자로 처리하면 안된다
17 | 4. like를 옵션조건에 사용할 때는 컬럼 값 길이가 고정적이어야 한다
18 |
19 | - 옵션 조건을 처리하는 가장 효율적인 방법은 union all을 이용하는 것이다
20 |
21 | ```sql
22 | -- 인덱스 : a + b
23 | -- a는 옵션 조건
24 | select *
25 | from table_name
26 | where :a_input is null and b between :b_input1 and :b_input2
27 | union all
28 | select *
29 | from table_name
30 | where :a_input is not null and a = :a_input and b between :b_input1 and :b_input2
31 | ```
32 |
33 | ### 3.4 인덱스 설계
34 |
35 | - 인덱스 설계가 어려운 이유? SQL 각각에 최적화된 인덱스를 마음껏 생성할 수 없기 때문에
36 |
37 | - 인덱스가 늘어나면 늘어날수록 DML 성능저하, 데이터베이스 사이즈 증가, 관리비용 상승 등의 문제가 커진다
38 |
39 | - 결합 인덱스를 구성할때의 가장 중요한 두가지 (판단 기준이 인덱스 스캔 효율성일때)
40 |
41 | 1. 조건절에 항상 사용하거나, 자주 사용하는 컬럼을 선정해야 한다
42 | 2. 그렇게 선정한 컬럼 중 = 조건으로 자주 조회하는 컬럼을 앞쪽에 두어야 한다
43 |
44 | - 인덱스 스캔 효율성 외에도 다른 판단 기준들도 많다
45 |
46 | - 수행 빈도 (효율성 다음으로 가장 중요)
47 | - NL방식으로 조인하는 두 테이블이 있을 때, outer쪽 인덱스보다 inner쪽 인덱스가 더 중요하다
48 | - 업무상 중요도
49 | - 클러스터링 팩터
50 | - 데이터량
51 | - 데이터량이 적으면 인덱스가 많던 적던 크게 이슈가 되지 않는다
52 | - 대용량이라면, 인덱스가 중요하다
53 | - DML부하
54 | - 저장 공간
55 | - 인덱스 관리 비용 등
56 |
57 | - 인덱스는 order by, group by를 위한 소트 연산을 생략할 수 있게 해준다
58 |
59 | - I/O를 최소화하면서도 소트 연산을 생략하려면..
60 | 1. = 연산자로 사용한 조건절 컬럼 선정
61 | 2. order by 절에 기술한 컬럼 추가
62 | 3. = 연산자가 아닌 조건절 컬럼은 데이터 분포를 고려해 추가 여부 결정
63 |
64 | - 항상 사용하는 컬럼을 앞쪽에 두고 그 중 = 조건을 앞쪽에 위치시켜야 효율적이다
65 |
66 | - 예: 조건절로 인덱스 설계하기
67 |
68 | ```sql
69 | -- 조건절1
70 | where a = :v1
71 | and b = :v2
72 | and c >= :v3
73 | -- 조건절2
74 | where a = :v1
75 | and b = :v2
76 | and c >= :v3
77 | and d = :v4
78 | -- 조건절3
79 | where a = :v1
80 | and b = :v2
81 | and c >= :v3
82 | and e = :v5
83 | -- 조건절4
84 | where a = :v1
85 | and b = :v2
86 | and c >= :v3
87 | and d = :v4
88 | and e = :v5
89 | ```
90 |
91 | - a,b,c는 항상 들어가고, d,e은 선택이므로 a,b,c먼저, d,e는 뒤로 배치한다
92 |
93 | - a,b는 = 조건이고, c는 between조건으므로 a,b가 c보다 앞에 배치된다
94 |
95 | - a,b사이와 d,e사이는 어떤게 앞에 오더라도 상관없다
96 |
97 | -> 효율적인 인덱스 순서 : abcde, abced, bacde, baced
98 |
99 | - 인덱스 중복 제거
100 |
101 | - 실습1
102 |
103 | - 상황
104 |
105 | X01: a + b
106 |
107 | X02: a + b + c
108 |
109 | X03: a + b + c + d
110 |
111 | - 해결
112 |
113 | X03만 남기고 X01, X02는 제거해도 된다
114 |
115 | - 실습2
116 |
117 | - 상황
118 |
119 | X01: a + b
120 |
121 | X02: a + c
122 |
123 | X03: a + d
124 |
125 | X04: a + e
126 |
127 | (+) a의 평균 카디널리티가 매우 낮다
128 |
129 | - 해결
130 |
131 | 카디널리티가 매우 낮다면 사실상 중복이다. 카디널리티가 5라면 a 를 =조건으로 조회하면 평균 5건이 남으니까 인덱스를 4개씩 만들 필요가 없다. 모두 제거하고 다음 인덱스 하나만 남겨도 된다
132 |
133 | X01: a + b + c + d + e
134 |
135 | - 실습3
136 |
137 | - 상황
138 |
139 | PK: a + b + c
140 |
141 | N1: d + a
142 |
143 | N2: f + b
144 |
145 | N3: a + e
146 |
147 | N4: a + d
148 |
149 | 밑줄(a, f) : 항상 범위조건으로만 조회
150 |
151 | 컬럼에 입력된 값 종류 개수 : a(2356), b(127), c(1850), d(5956), e(1715), f(2356)
152 |
153 | - 해결1
154 |
155 | a가 항상 범위조건이면 N3와 N4는 둘다 a가 인덱스 액세스 조건이다.
156 |
157 | 그래서 N4를 지우고 N3 맨뒤에 d를 추가한다
158 |
159 | - 해결2
160 |
161 | d와 a로 조회하거나 d단독으로 조회할때는 N1을 사용하고, a만으로 조회할때는 N3을 쓰면 되니까 N4만 제거해도 된다
162 |
163 | - 해결3
164 |
165 | PK: b + a + c
166 |
167 | N1: d + a
168 |
169 | N2: f + b
170 |
171 | N3: a + e
172 |
173 | ~~N4: a + d~~
174 |
175 | - 실습4
176 |
177 | - 상황
178 |
179 | PK: a + b + c + d
180 |
181 | N1: e + d
182 |
183 | N2: d
184 |
185 | N3: a + d
186 |
187 | 컬럼에 입력된 값 종류 개수 : a(736000), b(175), c(3000), d(250000), e(3)
188 |
189 | - 해결
190 |
191 | e의 NDV가 매우 낮기 때문에 e로만 조회할 땐 N1인덱스가 사용되지 않는다
192 |
193 | N2를 제거하고 N1의 e와 d의 순서를 바꾼다
--------------------------------------------------------------------------------
/3장_인덱스_튜닝/3.3_최락준.md:
--------------------------------------------------------------------------------
1 | # 3.3.9 BETWEEN과 LIKE 스캔 범위 비교
2 |
3 | ### 결론
4 |
5 | 범위 표현을 위해서는 LIKE 보다는 BETWEEN 연산자를 사용하자.
6 |
7 | ### 원인
8 |
9 | 범위 조건에서 LIKE와 BETWEEN 중 어떤 것을 사용하는가에 따라 스캔 범위가 달라진다.
10 |
11 | ### 예시
12 | between 의 경우
13 | ```sql
14 | WHERE 판매월 BETWEEN '201901' AND '201912'
15 | AND 판매구분 = 'B'
16 | ```
17 | * 201901 이면서 **B인 레코드에서 스캔을 시작**할 수 있다.(201901 이면서 A인 레코드 생략 가능)
18 |
19 | like 의 경우
20 | ```sql
21 | WHERE 판매월 LIKE '2019%';
22 | AND 판매구분 = 'B'
23 | ```
24 | * **201900 이 스캔 시작점**이 되기 때문에 201901이면서 A인 레코드도 전부 스캔해야 함.
25 |
26 | *스캔 종료 지점을 정하는 것 역시 BETWEEN이 더 유리하다.(ex. 판매구분이 A일 경우)*
27 |
28 | ## 3.3.10.1 범위검색 조건을 남용할 때 생기는 비효율 - LIKE
29 |
30 | ### 결론
31 | 옵션 조건을 처리할 때 LIKE 연산자를 사용하지 말자.
32 |
33 | ### 예시
34 | (1) 올바른 예
35 | 지역코드는 옵션조건일 때, 지역코드의 유무에 따라 아래의 두 쿼리 중 하나가 실행된다.
36 | ```sql
37 | 쿼리1
38 | SELECT * FROM 가입상품
39 | WHERE 회사코드 =:COM
40 | AND 지역코드 =:REG -- 지역코드가 조건절에 추가됨
41 | AND 상품명 LIKE :PROD || '%'
42 |
43 | 쿼리2
44 | SELECT * FROM 가입상품
45 | WHERE 회사코드 =:COM
46 | AND 상품명 LIKE :PROD || '%'
47 | ```
48 |
49 | 
50 |
51 | 왼쪽은 회사코드, 지역코드, 상품명에 'C70', '02', '보급'을 입력한 경우이고(쿼리1)
52 | 오른쪽은 이 중 지역코드를 입력하지 않은 경우이다.(쿼리2)
53 |
54 | * 왼쪽은 **회사코드, 지역코드, 상품명이 모두 액세스 조건**이 되어 스캔 범위가 상당히 줄었다.
55 | * 오른쪽은 회사코드만 액세스 조건이 되어 스캔범위가 넓지만 왼쪽과 오른쪽의 스캔범위 평균은 비교적 적은 것을 알 수 있다.
56 |
57 | (2) 잘못된 예
58 |
59 | 지역코드를 LIKE 연산자로 처리할 때 쿼리는 다음과 같다.
60 | ```sql
61 | SELECT * FROM 가입상품
62 | WHERE 회사코드 =:COM
63 | AND 지역코드 LIKE :REG || '%' -- 옵션 조건을 LIKE로 처리
64 | AND 상품명 LIKE :PROD || '%'
65 | ```
66 | 
67 |
68 | * 지역코드를 입력했을 때의 스캔 범위가 별로 줄어들지 않는다. **회사코드, 지역코드만 액세스 조건**이기 때문이다.
69 |
70 | ## 3.3.10.1 범위검색 조건을 남용할 때 생기는 비효율 - BETWEEN
71 |
72 | ### 결론
73 | 옵션 조건을 처리할 때 BETWEEN 연산자를 사용하지 말자.
74 |
75 | ### 예시
76 | ```sql
77 | SELECT * FROM 일별종목거래
78 | WHERE 거래일자 BETWEEN :시작일자 AND :종료일자
79 | AND 종목코드 BETWEEN :종목1 AND :종목2
80 | ```
81 | 위의 쿼리에서
82 | 거래일자가 옵션 조건일 경우 시작일자를 '1900'으로 하고 종료일자를 '2999'로 지정한다면
83 | 전체범위를 모두 스캔하는 것이다. 매우 비효율적이다.
84 |
85 | ## 3.3.11 다양한 옵션 조건 처리 방식의 장단점 비교
86 |
87 | ### (1). OR 조건 활용
88 |
89 | ```sql
90 | SELECT * FROM 거래
91 | WHERE (:CUST_ID IS NULL OR 고객ID = :CUST_ID)
92 | AND 거래일자 BETWEEN :DT1 AND :DT2
93 | ```
94 |
95 | * 위의 쿼리의 경우 고객ID가 선두 컬럼이기 때문에 OR Expansion이 작동하지 않는다. 따라서 '고객ID + 거래일자'로 구성된 인덱스를 사용할 수 없다.
96 | * 인덱스를 '거래일자 + 고객ID'로 구성하더라도, 고객ID를 테이블 액세스 단계에서 필터링하기 때문에 비효율적이다.
97 |
98 | ### OR 조건을 사용할 수 있는 경우
99 |
100 | * 인덱스 액세스 조건으로 사용 불가
101 | * 인덱스 필터 조건으로도 사용 불가
102 | * 테이블 필터 조건으로만 사용 가능
103 | * 인덱스 구성 컬럼 중 하나 이상이 Not Null 일 경우 인덱스 필터 조건으로 사용 가능(18C 버전이상)
104 |
105 | ### (2). LIKE/BETWEEN 조건 활용
106 |
107 | LIKE/BETWEEN 패턴을 사용할 수 있는 경우
108 |
109 | 1). 인덱스 선두 컬럼으로 사용x
110 |
111 | 인덱스가 '고객ID + 거래일자'일 경우
112 | ```sql
113 | SELECT * FROM 거래
114 | WHERE 고객ID LIKE :cust_id || '%'
115 | AND 거래일자 BETWEEN :dt1 AND :dt2
116 | ```
117 | * 고객ID를 입력하지 않을 경우 **모든 고객에 대한 레코드**를 거래일자로 필터링함.
118 |
119 | 2). null 허용 컬럼에서 사용x
120 |
121 | * 위의 sql에서 :cust_id 대신 null이 들어갈 경우 실제로 null인 데이터는 결과에서 누락된다.
122 |
123 | ```sql
124 | where null LIKE null || '%'
125 | 선택된 레코드가 없습니다.
126 | ```
127 |
128 | 3) 인덱스 액세스 조건이 숫자형 컬럼인 경우 사용x
129 |
130 | * 인덱스 액세스 조건이 숫자형일 경우 like를 사용하면 자동으로 형변환이 일어난다.(문자열로)
131 | * 가공한 컬럼에 대해서는 인덱스 스캔이 발생하지 않는다.
132 |
133 | 4) 가변 길이 컬럼의 경우 사용x
134 |
135 | * '김훈'을 입력한 경우 '김훈남'과 같이 김훈이 들어가는 다른 길이의 레코드까지 조회하면서 스캔 범위가 넓어진다.
136 |
137 | ### (3). UNION ALL 활용
138 |
139 | ```sql
140 | SELECT * FROM 거래
141 | WHERE :cust_id IS NULL -- 고객ID 조건이 없을 경우
142 | AND 거래일자 BETWEEN :dt1 AND :dt2
143 | UNION ALL
144 | SELECT * FROM 거래
145 | WHERE :cust_id IS NULL
146 | AND 고객ID = :cust_id -- 고객ID 조건이 있을 경우
147 | AND 거래일자 BETWEEN :dt1 AND :dt2
148 | ```
149 | * :cust_id 변수 입력 여부와 상관 없이 인덱스를 사용할 수 있다.
150 | * 단점을 쿼리가 길어진다는 것이다.
151 |
152 | ### (4). NVL/DECODE 함수 활용
153 |
154 | ```sql
155 | SELECT * FROM 거래
156 | WHERE 고객ID = nvl(:cust_id, 고객ID)
157 | AND 거래일자 BETWEEN :dt1 AND :dt2
158 | ```
159 | * 이 방법을 이용하면 :cust_id가 null일 경우 거래일자를 인덱스로 사용하고, :cust_id가 null이 아닐 경우 고객ID + 거래일자를 인덱스로 사용한다.
160 | * 단점은 앞서 나온 LIKE 패턴처럼 NULL 허용 컬럼에서 사용할 수 없다.
161 |
162 | ### (5). 옵션 조건 처리 방식일 때 개인적인 결론
163 |
164 | * OR / LIKE / BETWEEN 을 사용하지 않는 것이 좋다. 사용해야만 하는 상황이면 위의 사항을 고려해야 한다.
165 | * Mybatis와 같은 동적 SQL을 사용하면 위의 문제를 해결할 수 있다.
166 | * JPA와 같은 ORM이라면 어플리케이션 레벨에서 분기처리 해주는 것이 좋을 것 같다.
167 |
168 | ## 3.3.12 함수호출부하 해소를 위한 인덱스 구성
169 |
170 | PL/SQL 함수가 느린 가장 큰 이유는 Recursive call 때문이다.
171 | 이 방식은 결과 로우가 100만 건이면 PL/SQL 함수를 100만 번 실행시킨다.
172 |
173 | 이러한 함수호출부하를 줄이는 것은 결국 **액세스 조건을 고려한 인덱스 구성**을 하는 것이다.
174 | ```sql
175 |
176 | select * from 회원
177 | where 암호화된_전화번호 = encryption(:phone_no)
178 | ```
179 | * 위 방식은 데이터가 100만 건일 경우 함수가 100만 번 호출 된다.
180 |
181 | ```sql
182 |
183 | select * from 회원
184 | where 생년 = '1987'
185 | and 암호화된_전화번호 = encryption(:phone_no)
186 | ```
187 | 생년 + 암호화된_전화번호로 구성된 인덱스일 경우 액세스 조건에 따라 필터링 된 횟수만큼만 함수가 호출된다.
--------------------------------------------------------------------------------
/3장_인덱스_튜닝/3장_인덱스_튜닝.md:
--------------------------------------------------------------------------------
1 | ## 3장. 인덱스 튜닝
2 |
3 | ### 3.1 테이블 액세스 최소화
4 |
5 | - 인덱스 ROWID
6 |
7 | - 인덱스 ROWID는 논리적 주소다
8 | - 물리적 주소인 포인터와는 다르다
9 | - ROWID에 의한 테이블 액세스는 생각보다 고비용 연산이다
10 |
11 | - 특정 컬럼 기준으로 같은 값을 갖는 데이터가 모여있을수록 (클러스터링 팩터가 좋은 컬럼에 인덱스를 생성하면) 데이터를 찾는 속도가 빠르다
12 |
13 | - 읽어야 할 데이터가 일정량을 넘는 순간(인덱스 손익분기점이 넘는 순간) Table Full Scan보다 Index Range Scan이 오히려 느려진다
14 |
15 | - Table Full Scan은 시퀀셜 엑세스인데, 인덱스 ROWID를 이용한 테이블 액세스는 랜덤 액세스 방식이기 때문에
16 | - Table Full Scan은 Multiblock I/O인데, 인덱스 ROWID를 이용한 테이블 액세스는 Single Block I/O이기 때문에
17 |
18 | - 인덱스 클러스터링 팩터(CF)에 따라 인덱스 손익분기점이 달라진다
19 |
20 | - 인덱스 CF가 좋을수록 인덱스 손익분기점이 높다
21 | - = 물리적으로 근접해있는 컬럼을 기준으로 인덱스를 생성하면 많은 데이터를 Index Range Scan으로 가져와도 Table Full Scan보다 느려질 가능성이 적어진다
22 |
23 | - 기존에 있는 인덱스에 컬럼을 추가함으로써 테이블 액세스를 줄일수 있다
24 |
25 | - 예를 들어 a,b 컬럼으로 이루어진 인덱스가 있는 테이블에서 아래 쿼리를 시도한다고 가정할때
26 |
27 | - ```sql
28 | select *
29 | from table_name
30 | where a = 30 and c >= 2000;
31 | ```
32 |
33 | - 기존의 인덱스에 c 컬럼을 추가하면 인덱스 스캔양은 그대로지만 테이블 액세스양을 줄일 수 있다.
34 |
35 | - 이전의 경우엔 where로 걸러지는 양이 많을때일 경우의 튜닝 방법이다. 그럼 걸러지는 레코드가 거의 없다면?
36 |
37 | - 예를 들어 아래의 경우 걸러지는 레코드가 없다 (부서번호로 인덱스가 생성되어 있음)
38 |
39 | - ```sql
40 | select 부서번호, sum(수량)
41 | from 판매집계
42 | where 부서번호 like '12%'
43 | group by 부서번호;
44 | ```
45 |
46 | - 이럴 경우엔 쿼리에 사용된 컬럼(수량)을 모두 인덱스에 추가해서 테이블 액세스가 아예 발생하지 않도록 튜닝할 수 있다
47 |
48 | - 모든 컬럼이 인덱스에 포함되어 있어서 인덱스만 읽어서 처리하는 쿼리를 Covered 쿼리라고 하고, 그 인덱스를 Covered 인덱스라고 함
49 |
50 | - 이 방법은 효과는 좋지만 추가해야 할 컬럼이 많아지면 추가하기 곤란한 경우도 많다
51 |
52 | ### 3.2 부분범위 처리 활용
53 |
54 | - 부분범위 처리를 활용하면 인덱스로 액세스할 대상 레코드가 아무리 많아도 아주 빠른 응답속도를 낼 수 있다
55 | - 처음부터 모두 가져오지 않고 일정량(array size)만 요청한다(fetch call)
56 | - 멈출 수 있어야 의미있는 부분 범위 처리다.
57 | - 전체를 읽어서 정렬한다던가 하는 작업이 있다면 앞쪽 일부만 출력할 수 없다.
58 | - 앞쪽 일부만 읽고 반환해도 되도록 인덱스를 구성해야 한다
59 |
60 | ### 3.3 인덱스 스캔 효율화
--------------------------------------------------------------------------------
/3장_인덱스_튜닝/3주_김민석.md:
--------------------------------------------------------------------------------
1 | # 3장 인덱스 튜닝
2 |
3 | # 3.1 테이블 엑세스 최소화
4 |
5 |
6 | SQL 튜닝은 랜덤I/O를 줄이는 것.
7 |
8 | 랜던I/O를 최소화 하는 방법을 알아보자.
9 |
10 |
11 | ## 3.1.1 테이블 랜덤 액세스
12 |
13 | - 아무리 데이터가 많아도 인덱스를 사용하면 금방 조회된다
14 | - 어쩔때는 테이블 전체를 스캔하는 것보다 인덱스가 더 느릴때도 있다.
15 |
16 | 왜 그럴까? 이 질문에 대한 대답을 찾아보자.
17 |
18 | 
19 |
20 | 인덱스를 사용하는 이유는 ROWID(테이블에 있는 레코드를 찾기 위한 주소값)를 얻는데에 있다.
21 |
22 | 즉 인덱스를 사용하면 데이터가 실제로 있는 물리 주소가 아닌 물리 주소를 찾을 수 있는
23 | 주소를 얻는다. 반대로 테이블 Full Scan 시에는 직접적인 물리 주소를 얻기 때문에 위와 같은 문제점들이 나타난다.
24 |
25 |
26 | - ROWID
27 | - 논리적 주소(즉 메모리 주소가 아니다)
28 | - 디스크 상에서 테이블 레코드를 찾아가기 위한 위치 정보를 담고 있다
29 | - 즉, DBA(데이터파일번호 + 블록번호)를 가지고 있다.
30 |
31 | 
32 |
33 |
34 | - I/O 메커니즘
35 | - 인덱스를 통해 ROWID를 찾고 그 안에서 DBA를 읽어 온다
36 | - 캐시 검색
37 | - DBA를 해시 함수에 넣어서 해시 체인을 찾는다
38 | - 해시 체인 안에서 버퍼 헤더를 찾는다
39 | - 버퍼 해더가 있다면 hit
40 | - 캐시에 없다면 DBA를 통해 디스크 램덤 I/O
41 |
42 | `데이터가 캐싱돼있더라도 매번 DBA해싱과 래치 획득 과정을 반복한다. 동시 액세스가 심할때는 캐시버퍼 채인 래치와 버퍼 Lock 경합까지 발생. ROWID를 통한 테이블 액세스는 생각보다 고비용 구조`
43 |
44 | 또한, 이러한 구조는 메인메모리DB와 비교할 때 히트레이트가 아무리 높아도 메모리DB의 속도를 따라갈 수 없는 구조.
45 |
46 |
47 | ## 3.1.2 인덱스 클러스터링 팩터
48 |
49 | 
50 | CF 좋은 케이스
51 |
52 |
53 | 
54 |
55 | CF 안좋은 케이스
56 |
57 | - 클러스터링 팩트
58 | - 특정 컬럼을 기준으로 같은 값을 갖는 데이터가 서로 모여있는 정도.
59 | - 예) "거주지역 = 제주"에 해당하는 데이터가 물리적으로 근접해 있으면 데이터를 찾는 속도가 빠름
60 | - 가능한 이유는 오라클은 버퍼 Pinning 기능을 통해 래치 획득과 해시 체인 스캔 과정을 통해 찾아간 테이블 블록에 대한 포인터를 바로 해제하지 않고 가지고 있기 때문.
61 | - 따라서 인덱스는 레인지 스캔을 하기 때문에 비슷한 데이터가 같은 블록에 위치한다면 바로 물리적인 주소를 획득할 수 있다
62 | - 
63 |
64 |
65 | ## 3.1.3 인덱스 손익 분기점
66 |
67 | 위에서 나왔듯이 인덱스 ROWID를 이용한 데이터 액세스는 생각보다 고비용 구조다. 따라서 읽어야 할 데이터가 일정량을 넘는 순간, 테이블 전체를 스캔하는 것보다 오히려 느려진다. 이 점을 손익분기점이라고 한다.
68 |
69 | - 손익분기점이 발생하는 두가지 이유
70 | - Table Full Scan은 시퀀셜 액세스 VS. 인덱스 ROWID 방식은 랜덤 액세스
71 | - Table Full Scan은 Multi Block I/O VS. 인덱스 ROWID 방식은 Single Block I/O
72 |
73 | 
74 |
75 |
76 | - 인덱스 손익분기점과 버퍼캐시 히트율
77 | - 데이터량이 많아질수록 손익분기점은 더 낮아진다
78 | - 데이터량이 많아질수록 캐시 히트율이 낮아지기 때문이다.
79 | - 데이터량이 많아질수록 CF가 안좋아지기 때문이다.
80 | - 따라서, 손익분기점을 보고 인덱스를 쓸지 테이블 전체 스캔을 할지 결정해야 한다.(항상 인덱스 방식이 좋은건 아니다!!)
81 |
82 | ### 온라인 프로그램 튜닝 vs. 배치 프로그램 튜닝
83 |
84 | - 온라인프로그램 => 소량의 데이터를 읽거나 갱신 => 인덱스와 NL 조인 유리
85 | - 배치 프로그램 => 한번에 전체 데이터를 처리 => Full Scan과 해시 조인 유리
86 | - Full Scan 튜닝을 위하여 파티셔닝과 병렬처리 가능
87 |
88 | ## 3.1.4 인덱스 컬럼 추가
89 |
90 | 인덱스에 컬럼을 추가하는 튜닝 기법.
91 |
92 | 
93 |
94 | 
95 |
96 | 쓸데없이 테이블을 여섯번이나 엑세스.
97 |
98 | 인덱스를 새로 만드는건 매우 비효율적일 수 있기 때문에 컬럼을 하나 추가하는 것으로 튜닝.
99 |
100 | 
101 |
102 | 테이블 액세스 횟수가 1회로 준다.
103 |
104 |
105 | - 실제 사례 학습
106 |
107 | 
108 |
109 | 
110 |
111 | '서비스 번호' 단일 컬럼으로 구성된 인덱스를 사용하기 때문에 매우 비효율적인 액세스 발생.
112 |
113 | 이 인덱스에 '사용 여부' 컬럼 추가.
114 |
115 | 
116 |
117 |
118 | ## 3.1.5 인덱스만 읽고 처리
119 |
120 | 필터 조건에 의해 버려지는 레코드가 거의 없는데도 불구하고 속도를 개선해야 하는 상황이라면?
121 |
122 | 이때는 쿼리에 사용된 컬럼을 모두 인덱스에 넣어서 테이블 액세스가 아예 발생하지 않도록 할 수 있다.
123 |
124 | 이를 Covered 쿼리하고 한다. 그리고 이때 사용한 인덱스를 Covered 인덱스라 한다.
125 |
126 |
127 | ### include 인덱스
128 |
129 | Oracle에는 없지만 SQL Server 2005 버전에 추가된 기능.
130 |
131 | 미리 지정한 컬럼을 리프 레벨에만 함께 저장하여 효율을 높이는 방식.
132 |
133 | `create index emp_x01 on emp (deptno) include (sal)`
134 |
135 | 리프 레벨에만 포함되기 때문에 수직적 탐색에는 당연히 사용될 수 없고 테이블 랜덤 엑세스를 줄이는 용도로만 사용된다.
136 |
137 |
138 | ### 3.1.6 인덱스 구조 테이블
139 |
140 | ROWID 방식이 고비용 구조이기 때문에 테이블 자체를 인덱스 형태로 구성한 방식.
141 |
142 | 오라클에서는 IOT(Index-Organized Table)이라 부르고
143 |
144 | SQL Server에서는 클러스터형(Clustered) 인덱스라 부른다.
145 |
146 | 
147 |
148 | IOT에서는 인덱스 리프 블록이 곧 데이터 블록.
149 |
150 | 만드는 방법
151 |
152 | 
153 |
154 | - 장점
155 | - CF가 매우 좋다. 100%
156 | - 따라서 BETWEEN이나 부등호 조건으로 넓은 범위를 읽을 때 유리
157 | - 데이터 입력과 조회 패턴이 서로 다른 테이블에도 유용
158 | - 책의 예처럼 강제로 CF를 좋게하여 인덱스를 쓰더라도 느린 경우에 속도를 올릴 수 있다.
159 |
160 | 예) 사원 100명. 실적등록은 일자별로 입력. 한블록에 100개씩 하루에 한블록 생성. 조회는 사원번호순으로 진행. 인덱스를 걸더라도 1년치를 조회하면 365 블럭에 모두 액세스. 하지만 사번으로 IOT구성하면 CF가 좋아져서 4번 액세스로 모든 값 가져올 수 있음.
161 |
162 |
163 | ## 3.1.7 클러스터 테이블
164 |
165 | ### 인덱스 클러스터 테이블
166 |
167 | 
168 |
169 | 키값이 같은 레코드를 같은 블럭에 저장. 블록을 넘어서면 체인으로 묶는다.
170 |
171 | 여러 테이블을 같은 블록에 저장할 수 있는데 이를 다중 테이블 클러스터라 한다.
172 |
173 | 클러스터를 생성하고 인덱스도 필수적으로 생성한다. 데이터 저장위치를 판별하기 위해서다.
174 |
175 |
176 | `create cluster c_dept# (deptno number(2)) index;`
177 |
178 | `create index c_dept#_idx on cluster c_dept#;`
179 |
180 | 
181 |
182 | 클러스터 인덱스도 B-Tree로 구성되지만 리프노드는 해당 키 값을 저장하는 첫번째 테이터 블록을 가리킨다. 따라서 인덱스와 레코드의 관계가 1:M 이다. 그리고 키값이 항상 유니크하다.
183 |
184 | 
185 |
186 | 또한, 랜덤 액세스가 값 하나당 한번씩만 발생하고(체인 스캔 제외) 클러스터에 도달해서는 시퀀셜로 스캔하기 때문에 넓은 범위를 읽더라도 효율적이다.
187 |
188 |
189 |
190 | ### 해시 클러스터 테이블
191 |
192 | 인덱스 대신에 해시를 사용한다.
193 |
194 | 
195 |
196 |
--------------------------------------------------------------------------------
/3장_인덱스_튜닝/4주_김민석.md:
--------------------------------------------------------------------------------
1 | # 3장 인덱스 튜닝
2 |
3 | ## 3.3 인덱스 스캔 효율화
4 |
5 | ### 3.3.5 인덱스 선행 컬림이 등치(=) 조건이 아닐 때 생기는 비효율
6 |
7 | - 인덱스 스캔 효율성은 인덱스 컬럼을 조건절에 모두 등치(=) 조건을 사용할 때 가장 좋다.
8 |
9 | ### 3.3.6 BETWEEN을 IN-List로 전환
10 |
11 | - 선행 조건이 BETWEEN으로 비효율이 발생할 때 In-List로 바꿔주면 큰 효과를 얻을 수도 있다.
12 | - 이때 주의 할 점은 IN-List 개수가 많지 않아야 한다.(수직적 탐색이 많아 질 수 있음)
13 |
14 | ### 3.3.7 Index Skip Scan 활용
15 |
16 | - BETWEEN을 IN-List로 변환하는 것 대신 Index Skip Scan을 활용하는 방법도 있다.
17 |
18 | ### 3.3.8 IN 조건은 '=' 인가
19 |
20 | - IN 조건은 '='이 아니다.
21 |
22 | ### 3.3.9 BETWEEN과 LIKE 스캔 범위 비교
23 |
24 | - LIKE보다 BETWEEN이 낫다.
25 |
26 | ### 3.3.10 범위검색 조건을 남용할 때 생기는 비효율
27 |
28 | - 코딩을 쉽게 하려고 인덱스 컬럼에 범위검색 조건을 남용하지 말라.
29 |
30 | ### 3.3.11 다양한 옵션 조건 처리 방식의 장단점 비교
31 |
32 | - 인덱스 선두 컬럼에 대한 옵션 조건에 OR 조건을 사용하지 말라
33 | - 변별력이 좋은 필수 조건이 있는 상황에서는 LIKE/BETWEEN 사용 가능
34 | - UNION ALL 방식은 옵션 조건 컬럼도 인덱스 액세스 조건으로 사용한다
35 | - NVL/DECODE 사용하면 UNION ALL 보다 단순하면서도 비슷한 성능을 낼 수 있다.
36 |
37 | ### 3.3.12 함수호출부하 해소를 위한 인덱스 구성
38 |
39 | - PL/SQL은 매우 느리다.
40 | - 특히 안에서 SQL을 실행하면 Recursive call이 일어나기 때문에 매우 비효율적이다.
41 |
42 |
43 | ## 3.4 인덱스 설계
44 |
45 | ### 3.4.1 인덱스 설계가 어려운 이유
46 |
47 | - 인덱스가 많으면 생기는 문제
48 | - DML 성능 저하
49 | - 데이터베이스 사이즈 증가
50 | - 테이터베이스 관리 및 운영 비용 상승
51 |
52 | ### 3.4.2 가장 중요한 두 가지 선택 기준
53 |
54 | - 인덱스 선택 기준
55 | - 조건절에 항상 사용하거나, 자주 사용하는 컬럼을 선정하다.
56 | - '=' 조건으로 자주 조회하는 컬럼을 앞에 둔다.
57 |
58 | ### 3.4.3 스캔 효율성 이외의 판단 기준
59 |
60 | - 수행 빈도
61 | - 업무상 중요도
62 | - 클러스터링 팩터
63 | - 데이터량
64 | - DML 부하
65 | - 저장 공간
66 | - 인덱스 관리 비용
67 |
68 | ### 3.4.4 공식을 초월한 전략적 설계
69 |
70 | 모든 조건절을 고려하여 인덱스를 다 만들수는 없다. 도메인에 맞게 전략을 구성하여 최적 인덱스를 구성한다.
71 |
72 | ### 3.4.5 소트 연산을 생략하기 위한 컬럼 추가
73 |
74 | 조건절에 사용하지 않는 컬럼이더라도 소트 연산을 생략할 목적으로 인덱스 구성에 포함시켜서 성능 개선 도모
75 |
76 | ### 3.4.6 결합 인덱스 선택도
77 |
78 | 인덱스 생성 여부를 결정할 때는 선택도가 충분히 낮은지가 중요한 판단기준이다.
79 |
80 | ### 3.4.7 중복 인덱스 제거
81 |
82 | 중복되는 인덱스는 제거한다. 중복으로 보이지 않는다 하더라도 평균 카디널리티가 낮다면 사실상 중복으로 본다.
83 |
84 | ### 3.4.8 인덱스 설계도 작성
85 |
86 | 인덱스 설계에도 전체를 조망할 수 있는 설계도면이 필요한다.
87 |
--------------------------------------------------------------------------------
/3장_인덱스_튜닝/images/3-43.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Meet-Coder-Study/nice-sql-tuning/6ebb1473a41c04d97351f3527a5c2311b4fc4973/3장_인덱스_튜닝/images/3-43.jpg
--------------------------------------------------------------------------------
/3장_인덱스_튜닝/images/3-44.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Meet-Coder-Study/nice-sql-tuning/6ebb1473a41c04d97351f3527a5c2311b4fc4973/3장_인덱스_튜닝/images/3-44.jpg
--------------------------------------------------------------------------------
/3장_인덱스_튜닝/조수진.md:
--------------------------------------------------------------------------------
1 | # 3.1 테이블 액세스 최소화
2 |
3 | ### 3.1.1 테이블 랜덤 액세스
4 |
5 | - 인덱스로 검색해도 빠른데 왜 파티셔닝을 할까?
6 | - 인덱스로 검색하는데 왜 느릴까?
7 |
8 | ### ROWID
9 |
10 | - 커버링 인덱스가 아닌 경우 원하는 자료를 찾기 위해 ROWID로 테이블에 액세스 해야한다
11 | - 인덱스의 ROWID는 포인터(물리 주소)가 아니다. 논리 주소이다!(메모리 DB 인덱스는 포인터)
12 |
13 | ### 인덱스를 이용한 I/O
14 |
15 | - ROWID를 분해해서 DBA 정보 확인
16 | - 해싱 결과로 해시 체인 찾기(래치 획득)
17 | - 해시 체인 내부에서 버퍼 헤더 찾기(버퍼 캐시의 메모리 주소)
18 | - 버퍼 헤더가 있으면 포인터로 버퍼 블록 찾아가기(&pinning 포인터 유지)
19 | - 버퍼 헤더가 없으면 디스크에서 데이터 파일 → 블록 → 레코드 찾아가서 캐시에 로딩 & 결과 리턴
20 |
21 | → 버퍼 캐시에서 테이블 블록이 수시로 밀려났다 다시 캐싱됨
22 |
23 | ⇒ 인덱스에서 포인터 사용할 수 X
24 |
25 | ⇒ 인덱스 ROWID를 이용한 테이블 액세스는 생각보다 고비용이다!
26 |
27 | 
28 |
29 | ### Table Full Scan I/O
30 |
31 | - 테이블 세그먼트 헤더에 익스텐트 맵 확인
32 | - 익스텐트 맵을 통해 각 익스텐트의 첫 번째 블록 DBA를 알 수 있음
33 | - 익스텐트 내부의 블록은 연결되어 있으므로 시퀀셜하게 읽으면됨
34 |
35 | ### 3.1.2 인덱스 클러스터링 팩터
36 |
37 | 특정 컬럼을 기준으로 같은 값을 갖는 데이터가 서로 모여있는 정도를 의미 = 군집성 계수
38 |
39 | 익덱스 레코드 정렬 순서와 실제 테이블 정렬 순서가 비슷하면 테이블 액세스량에 비해 블록 I/O가 적게 발생함
40 |
41 | → 버퍼 Pinning(인덱스 ROWID로 테이블 액세스 할 때, 래치 획득과 체인 스캔 후 블록에서 데이터를 읽고 포인터 그대로 유지)
42 |
43 | ### 3.1.3 인덱스 손익분기점
44 |
45 | ROWID로 테이블 액세스는 고비용 구조
46 |
47 | → 읽어야 할 데이터가 일정량 이상 넘어가면 테이블 풀스캔이 효과적일수도
48 |
49 | ⇒ Index Range Scan에 의한 테이블 액세스가 Table Full Scan보다 느려지는 지점을 인덱스 손익분기점
50 |
51 | - 보통 10~100만건 이내 테이블에서 손익 분기점은 5~20% 수준, CF에 따라 크게 달라진다
52 | - 테이블 풀스캔은 1000건을 조회하든, 1000만건을 조회하든 성능이 거의 일정하다
53 |
54 | → 시퀀셜 액세스, Multiblock I/O라서
55 |
56 |
57 | ### OLTP 튜닝
58 |
59 | - 소량 데이터를 읽고 갱신하므로 인덱스를 효과적으로 활용하는 것이 중요
60 | - NL 조인 사용
61 | - 인덱스로 소트 연산 생략
62 |
63 | ### 배치 프로그램 튜닝
64 |
65 | - 전체범위 처리 기준으로 튜닝해야 한다
66 | - 처리 대싱 집합 중 일부를 빠르게 처리하는 것이 아니라 전체를 빠르게 처리하는 것을 목표로
67 | - 대량 데이터를 빠르게 처리하려면 Full Scan과 해시 조인이 유리
68 | - 초대용량 테이블을 Full Scan하면 오래 걸리기 때문에 배치 프로그램은 파티션 활용 전략이 매우 중요한 튜닝 요소
69 | - 파티셔닝은 Full Scan을 빠르게 처리하기 위함
70 |
71 | ### 3.1.4 인덱스 컬럼 추가
72 |
73 | 테이블 액세스 최소화를 위해 가장 일반적으로 사용하는 튜닝 기법은 인덱스에 컬럼 추가 하는 것
74 |
75 | - 인덱스 스캔 후, 테이블 액세스 과정에서 필터되는 데이터가 많은 경우 사용
76 |
77 | cr - Consistent Read(Consistent Read Mode로 버퍼 캐시에서 특정 블록을 읽는 것)
78 |
79 | pr - Physical Read(특정 블록을 읽어서 버퍼 캐시에 로딩)
80 |
81 | ### 3.1.5 인덱스만 읽고 처리
82 |
83 | ### Covered 쿼리, Covered 인덱스
84 |
85 | 쿼리에 사용되는 컬럼을 모두 인덱스에 추가해서 테이블 액세스가 아예 발생하지 않도록 하는 방법
86 |
87 | ### Include 인덱스
88 |
89 | 인덱스 키 외에 미리 지정한 컬럼을 리프 레벨에 함께 저장하는 기능(최대 1023 컬럼까지 지정 가능)
90 |
91 | - 커버링 인덱스랑 차이점: 커버링 인덱스와 달리 수직 탐색에 사용할 수 없다, 정렬도 X
92 | - 인덱스 생성 시, `include (sal)`
93 |
94 | ### 3.1.6 인덱스 구조 테이블
95 |
96 | - 클러스터형 인덱스, IOT
97 | - 인덱스 리프 블록에서 테이블 블록에 있어야 할 데이터를 모두 저장
98 | - 인위적으로 클러스터링 팩터를 좋게 만드는 방법 → 시퀀셜 데이터 액세스
99 | - 테이블 생성할 때, `organization index` 작성(기본 테이블은 힙 구조 테이블)
100 |
101 | ### 3.1.7 클러스터 테이블
102 |
103 | - 인덱스를 이용한 클러스터, 해시 클러스터 존재
104 |
105 | ### 질문
106 |
107 | - 129p 파티션 pruning: 하드 파싱이나 실행 시점에서 SQL 조건절을 분석해서 읽지 않아도 되는 파티션 세그먼트를 액세스 대상에서 제외시키는 기능
108 | - 콤마는 암시적 inner join
109 | - 랜덤 I/O를 줄여야한다고 할 때, 디스크 블록 I/O(테이블 액세스)만 말하는 게 아니라 버퍼 캐시 읽는 것도 포함인듯?
110 | - 142p 실행 계획 읽는 순서?
111 | - 윈도우 함수?
112 | - include의 커버링 인덱스 대비 우위점: 갱신 시 인덱스 트리 재정렬 오버헤드 X
113 | - 152p 클러스터링 인덱스가 수정되면 데이터 전체 재정렬?
114 | - 클러스터 vs 파티셔닝 → 파티셔닝 테이블을 물리적으로 분리해서 I/O 경합으르 줄이기 위함, 클러스터는 CF를 높이기 위한 목적
115 | - 클러스터 없이 쓰면 배치로 여러 데이터 입력되도 다른 블록에 저장될까? CF 나빠질까?
116 |
117 | [SQL 실행계획(Explain Plan) 해석/읽는 법](https://m.blog.naver.com/jump_penguin/20193916878)
118 |
119 | # 3.2 부분범위 처리 활용
120 |
121 | ### 3.2.1 부분범위 처리
122 |
123 | - DBMS는 클라이언트에게 데이터를 전송할 때 일정량씩 나누어 전송
124 | - 일정량(Array Size) 전송하고 서버 프로세스는 CPU를 OS에 반환
125 | - 클라이언트로부터 Fetch Call을 받기 전까지 서버 프로세스는 대기 큐에서 그대로 멈춰 기다림
126 | - 정렬 조건이 있으면 정렬을 완료한 후 전송할 데이터를 나눌 수 있기 때문에 성능 개선 불가능
127 | - 정렬 조건이 인덱스의 선두에 있으면 부분 범위 처리 가능
128 | - 대량 데이터를 파일로 내려받는다면 어차피 데이터를 모두 전송해야 하므로 Array Size를 크게 하고 Fetch Call을 줄임
129 | - 일부 데이터만 Fetch하다가 멈추는 프로그램은 Array Size를 작게 설정하는 것이 유리
130 |
131 | ### 3.2.2 부분범위 처리 구현
132 |
133 | Array Size에 도달하면 멈추었다가 사용자 요청이 있을 때 다시 데이터를 Fetch하는 부분이 필요하다
134 |
135 | ### 3.2.3 OLTP 환경에서 부분범위 처리에 의한 성능개선 원리
136 |
137 | 클라이언트 - DB 서버 2-Tier 환경은 일부만 출력하고 멈출 수 있다
138 |
139 | 클라이언트와 DB 사이에 WAS, AP 서버 등이 존재하는 n-Tire는 클라이언트가 특정 DB 커텍션을 독점할 수 없 → 5.3장에서 방법 설명
140 |
141 | # 3.3 인덱스 스캔 효율화
142 |
143 | ### 3.3.3 액세스 조건과 필터 조건
144 |
145 | - 인덱스 액세스 조건: 인덱스 스캔 범위를 결정하는 조건절
146 | - 인덱스 필터 조건: 테이블로 엑세스 할지 결정하는 조건절
147 | - 테이블 필터 조건: 쿼리 수행 다음 단계로 전달하거나 최종 결과집합에 포함할지 결정
148 |
149 | ### 3.3.4 비교 연산자 종류와 컬럼 순서에 따른 군집성
150 |
151 | - 인덱스 엑세스 조건: 선행 컬럼이 '='인 조건 상태에서 첫 번째로 나타나는 범위 검색 조건까지
152 |
153 | → 조건에 만족하는 인덱스 레코드는 연속해서 모여있음
154 |
155 | - 인덱스 필터 조건: 그 이후는 비교 연산자 종류에 상관없이 결과가 흩어짐
156 |
157 | ### 3.3.5 인덱스 선행 컬럼이 등치(=) 조건이 아닐 때 생기는 비효율
158 |
159 | - 인덱스 스캔 효율은 인덱스 커럼을 모두 등치(=) 조건으로 사용할 때 가장 좋다
160 | - 인덱스 컬럼 중 일부가 조건절에 없거나 등치 조건이 아니더라도 뒤쪽 컬럼일 때는 비효율이 없다
161 | - ex. 인덱스 on (아파트시세코드, 평형, 평형타입, 인터넷 매물) → `where 아파트시세코드 =: a`
162 | - 인덱스 선행 컬럼이 조건절에 없거나, 부등호, BETWEEN, LIKE 같은 범위 검색 조건이면 인덱스를 스캔하는 단계에서 비효율이 생긴다
163 |
164 | ### 3.3.6 BETWEEN을 IN-List로 전환
165 |
166 | - Between 절을 IN-List로 변경해서 개선할 수 있다
167 |
168 | → INLIST ITERATOR 오퍼레이션 발생(IN절의 각 값에 대해 수직 탐색 반복 진행)
169 |
170 | → IN-LIST에 있는 값을 등치 조건으로 실행 후 union all 실행
171 |
172 | → IN-List 항목이 늘어날 수 있으면 불가능(between 값 사이에 현재 알고 있는 값 이외에 추가 발생)
173 |
174 | → IN-List 개수가 많으면 리프 블록 탐색 스캔 비용보다 루트에서 브랜치 블록까지를 반복 스캔하는 I/O 비용이 클 수 있음
175 |
176 | → 인덱스 스캔 과정에서 선택되는 레코드들이 멀리 떨어져있을 때 유용
177 |
178 |
179 | ### 3.3.7 Index Skip Scan 활용
180 |
181 | 선두 컬럼이 Between이어서 나머지 검색 조건을 만족하는 데이터들이 멀리 떨어져있을 때, IN-List대신 Index Skip Scan으로 개선 가능
182 |
183 | - 인덱스 on (판매월, 판매 구분)
184 | - select (*) from 월별고객별판매집계 t where 판매구분 ='A' and 판매월 between '201801' and '201812'
185 | - 블록 I/O 비교: 인덱스 변경 > Skip Scan > IN-LIST >> Between
186 |
187 | ### 3.3.8 IN 조건은 '='인가
188 |
189 | - IN 조건은 '='이 아니다
190 | - '='이 되려면 IN-LIST Iterator로 풀어서 액세스 조건으로 만들어야 한다
191 | - 그렇지 않으면 IN 조건은 필터 조건이다
192 | - IN 조건이 액세스 조건이 된다고 항상 효과적인 것이 아니다
193 | - `NUM_INDEX_KEYS` 힌트로 IN-List 액세스 조건 또는 필터 조건으로 유도할 수 있다
194 | - 세 번째 인자 값(ex. 1)으로 주어진 칼럼까지만 액세스 조건으로 사용
195 | - 힌트 없이 필터 조건으로 사용하고 싶은 컬럼을 가공(ex. RTRIM, || '' 등 )할 수 도 있음 → 인덱스 사용 X
196 |
197 | ### 3.3.9 BETWEEN과 LIKE 스캔 범위 비교
198 |
199 | - LIKE보다 BETWEEN을 사용하는 게 낫다
200 | - BETWEEN은 양 끝단의 레코드 시작점, 멈추는 지점이 설정되여 효율적일 수 있음
201 | - 그러나 LIKE는 양 끝단의 조건 레코드를 모두 훑어야함
202 |
203 | ### 3.3.10 범위 조건 검색을 남용할 때 생기는 비효율
204 |
205 | ```sql
206 | <쿼리1> 회사코드, 지역코드, 상품명을 모두 입력할 때
207 | SELECT 고객ID, 상품명, 지역코드, ...
208 | FROM 가입상품
209 | WHERE 회사코드 =: com
210 | AND 지역코드 =: reg
211 | AND 상품명 LIKE :prod || '%'
212 |
213 | <쿼리1> 회사코드, 상품명을 모두 입력할 때
214 | SELECT 고객ID, 상품명, 지역코드, ...
215 | FROM 가입상품
216 | WHERE 회사코드 =: com
217 | AND 상품명 LIKE :prod || '%'
218 | ```
219 |
220 | ```sql
221 | <쿼리3> 회사코드, 상품명, 지역코드,,,
222 | SELECT 고객ID, 상품명, 지역코드, ...
223 | FROM 가입상품
224 | WHERE 회사코드 =: com
225 | AND 지역코드 LIKE :reg || '%'
226 | AND 상품명 LIKE :prod || '%'
227 | ```
228 |
229 | → 2가지 상황에 따라 처리해야하는 쿼리를 효율적으로 개발하기 위해 쿼리 3으로 변경하면 지역코드가 입력되어도 지역 코드가 액세스 조건으로 사용되지 못하고 필터 조건으로 사용됨
230 |
231 | ⇒ 개발 생산성은 좋아질지 몰라도 인덱스 스캔의 비효율이 생긴다. 특히 대량 테이블을 넓은 범위로 검색할 때 영향이 매우 클 수도 있다
232 |
233 | ### 3.3.11 다양한 옵션 조건 처리 방식의 장단점 비교
234 |
235 | 옵션처리를 위해 아래와 같이 다양한 방법이 있다. 성능 개선을 위해 가장 다루기 어려운 주제이다. 여러 방식의 장단점을 이해해서 상황에 따라 선택해야한다.
236 |
237 | 1. OR 조건 활용
238 |
239 | ```sql
240 | select * from 거래
241 | where (:cust_id is null or 고객ID = :cust_id)
242 | and 거래일자 between :dt1 and dt2
243 | ```
244 |
245 | - 옵티마이저의 OR Expansion이 자동으로 적용되지 않는다
246 |
247 | → 선행조건에 Or 사용시 인덱스 사용X
248 |
249 | → 후행조건에서 사용시, 필터로 사용되지만 oracle는 null을 인덱스에 저장하지 않기 때문에 테이블 액세스 단계에서 필터링
250 |
251 | - 인덱스에 포함되지 않은 컬럼에 대해 옵션 조건은 어차피 테이블 스캔에서 필터링해야하므로 써도 상관없음
252 |
253 | ⇒ 인덱스 액세스 조건으로 사용 불가, 인덱스 필터 조건으로도 사용 불가, 테이블 필터 조건으로만 사용 가능(NotNull 조건 컬럼은 18c부터 인덱스 필터 조건 사용 가능)
254 |
255 | 아래의 경우에는 OR Expansion을 통해 인덱스 사용 가능
256 |
257 | ```sql
258 | select * from 거래 where 고객ID = :custd_id
259 | and ((:dt_type = 'A' AND 거래일자 between :dt1 and :dt2))
260 | or
261 | ((:dt_type = 'B' AND 거래일자 between :dt1 and :dt2))
262 | ```
263 |
264 | 2. LIKE/BETWEEN 조건 활용
265 |
266 | 필수 조건이 변별력이 좋은 경우 나쁘지 않다.
267 |
268 | ```sql
269 | -- 인덱스 : 상품대분류코드 + 상품코드
270 | select * from 상품
271 | where 상품대분류코드 = :prd_lcls_cd // 필수 조건
272 | and 상품코드 like :prd_cd || '%' // 옵션 조건
273 | ```
274 |
275 | LIKE/BETWEEN 을 사용할 때 아래 조건에 속하는지 반드시 점검해야한다(BETWEEN은 위의 2개)
276 |
277 | - 인덱스 선두 컬럼 → 값이 입력되지 않으면 인덱스에서 모두 검색해서 필터링해야함
278 | - NULL 허용 컬럼 → 결과 집합에 오류가 생김(null은 like '%'에 검색되지 X)
279 | - 숫자형 컬럼 → '%'은 문자 비교 연산이라서 숫자형 컬럼은 형변환 → 인덱스 사용X
280 | - 가변 길이 컬럼 → 입력값이 있어도 여러 값이 의도하지 않게 검색될 수 있음
281 | 3. UNIONALL 활용
282 |
283 | ```sql
284 | select * from 거래
285 | where :cust_id is null
286 | and 거래일자 between :dt1 and :dt2 -- 거래 일자가 선두인 인덱스 사용
287 | union all
288 | select * from 거래
289 | where :cust_id is not null
290 | and 거래일자 between :dt1 and :dt2 -- 고객ID가 선두인 인덱스 사용
291 | ```
292 |
293 | → :cust_id 변수에 값을 입력했는지 하지 않았는지에 따라 두 SQL 중 하나만 실행되는 방식
294 |
295 | - null 허용 컬럼이어도 문제 없음
296 | - 두 경우 모두 인덱스 조건으로 사용한다
297 | - SQL 코딩량이 길어진다
298 | 4. NVL/DECODE 함수 활용
299 |
300 | ```sql
301 | select * from 거래
302 | where 고객ID = nvl(:cust_id, 고객ID)
303 | and 거래일자 between :dt1 and :dt2
304 |
305 | 혹은
306 |
307 | select * from 고객
308 | where 고객ID = decode(:cuxst_id, null, 고객ID, :cust_id)
309 | and 거래일자 between :dt1 and :dt2
310 | ```
311 |
312 | → :cust_id 값이 없으면 거래일자가 선두인 인덱스 이용, 있으면 고객ID+거래일자 인덱스 사용
313 |
314 | - NVL, DECODE 둘 다 실행계획 동일
315 | - 고객ID를 가공했는데도 인덱스 사용이 가능한 이유는 OR Expansion 쿼리 변환이 일어났기 때문
316 | - 옵션 조건 컬러도 인덱스 액세스 조건으로 사용할 수 있음
317 | - UNION ALL보다 단순하면서 UNION ALL과 동일한 성능을 냄
318 | - 하지만, NULL 허용 컬럼에서는 사용할 수 없다
319 | - NVL/DECODE 함수를 여러개 사용하면 그 중 가장 변별력이 좋은 컬럼 기준으로 한 번만 OR Expansion이 일어남
320 | 5. Dynamic SQL
321 | - dynamic sql 쓸 수 있으면 사실 옵션 조건에 '=' 사용 가능
322 | - 하지만 금융권 등 dynamic sql을 허용하지 않는 곳도 있고, 허용하더라도 힌트로 액세스 경로를 고정하려고 할 때 동적으로 구성된 조건절과 서로 상충할 수 있어서 고려해야한다
323 |
324 | ### 3.3.12 함수호출부하 해소를 위한 인덱스 구성
325 |
326 | PL/SQL 사용자 정의 함수는 개발자들이 일반적으로 생각하는 것 이상으로 느리다
327 |
328 | <이유>
329 |
330 | - 가상머신(VM) 상에서 실행되는 인터프리터 언어
331 | - 호출 시마다 컨텍스트 스위칭 발생
332 | - 내장 SQL에 대한 Recursive Call 발생
333 |
334 | → 쿼리 내에 풀어서 쓰길 권장
335 |
336 | → 함수 내부 로직이 너무 복잡하면 그대로 쓸 수 밖에 없음. 그럴 땐 효과적인 인덱스 구성을 통해 함수 호출 횟수를 줄이기
337 |
338 | ## 3.4 인덱스 설계
339 |
340 | ### 3.4.1 인덱스 설계가 어려운 이유
341 |
342 | - 인덱스의 양을 적절히 가져가면서 SQL에 최적화된 인덱스를 생성해야하기 때문에 어렵다
343 | - 시스템 전체 시각에서 종합적, 전략적으로 접근해야한다
344 | - 인덱스 추가는 시스템에 부하를 주고, 인덱스 변경은 운영 리스크가 있다.
345 |
346 | ⇒ 개발 단계에서 인덱스를 최적으로 설계하는 것이 중요!
347 |
348 | ### 인덱스가 많아지면 생기는 문제
349 |
350 | - DML 성능 저하(= TPS 저하) - 인덱스 갱신 오버헤드, 인덱스 분할 등
351 | - 데이터베이스 사이즈 증가(= 디스크 공간 낭비)
352 | - 데이터베이스 관리 및 운영 비용 상승(백업,복제, 재구성 등)
353 |
354 | ### 3.4.2 가장 중요한 두 가지 선택 기준
355 |
356 | - 조건절에 항상 사용하거나, 자주 사용되는 컬럼을 선정한다
357 | - '=' 조건으로 자주 조회하는 컬럼을 앞쪽에 둔다
358 |
359 | → 인덱스 스캔 효율
360 |
361 | ### 3.4.3 스캔 효율성 이외의 판단 기준
362 |
363 | - **수행 빈도**
364 | - NL 조인을 하는 경우 Inner 쪽 스캔
365 | - 업무상 중요도
366 | - 클러스터링 팩터
367 | - **데이터량**
368 | - DML 부하(=기존 인덱스 개수, 초당 DML 발생량, 자주 갱신하는 컬럼 포함 여부 등)
369 | - 저장 공간
370 | - 인덱스 관리 비용 등
371 |
372 | ### 3.4.4 공식을 초월한 전략적 설계
373 |
374 | 조건절 패턴이 열 개 있을 때, 패턴마다 인덱스를 하나씩 만들 수는 없다
375 |
376 | → 최적을 달성해야 할 핵심 액세스 경로 한 두 개를 전략적으로 선택해서 최적 인덱스 설계
377 |
378 | → 나머지 액세스 경로는 약간의 비효율이 있더라도 목표 성능을 만족하는 수준으로 인덱스를 구성
379 |
380 | 
381 | 가계약 테이블. 왼쪽 리스트는 '=' 연산자. 오른쪽은 between 연산자로 조회
382 |
383 | → 공식에 의해 '=' 절을 앞에 두는 인덱스를 만드려면 24개 필요
384 |
385 | ### 최종 설계
386 |
387 | - X01: 청약일자 + 취급부서 + 취급지점 + 취급자 + 입력자 + 대리점설계사 + 대리점지사
388 | - X01: 보험개시일자 + 취급부서 + 취급지점 + 취급자 + 입력자 + 대리점설계사 + 대리점지사
389 | - X01: 보험종료일자 + 취급부서 + 취급지점 + 취급자 + 입력자 + 대리점설계사 + 대리점지사
390 | - X01: 데이터생성일시 + 취급부서 + 취급지점 + 취급자 + 대리점설계사 + 대리점지사
391 | - X05: 입력자 + 데이터생성일시(조회 패턴의 85%)
392 |
393 | → 일자 조회 구간이 길지 않으면 인덱스 스캔 효율이 성능에 미치는 영향이 크지 않다
394 |
395 | → 인덱스 스캔 효율보다 테이블 엑세스가 더 큰 부하 요소
396 |
397 | ⇒ 인덱스 개수를 최소화하면 사용 빈도가 높건 중요한 액세스 경로가 새로 도출됐을 때 최적의 인덱스를 추가할 여유도 생긴다
398 |
399 | ### 3.4.5 소트 연산을 생략하기 위한 컬럼 추가
400 |
401 | - 인덱스는 정렬되어 있어서 소트 연산 생략 가능
402 | - 정렬을 위해 인덱스를 사용하기도
403 |
404 | ### I/O 연산 최소화 & 소트 연산 생략
405 |
406 | - '=' 연산자로 사용한 조건절 컬럼 선정
407 | - ORDER BY 절에 기술한 컬럼 추가
408 | - '=' 연산자가 아닌 조건절 컬럼은 데이터 분포를 고려해 추가 여부 결정
409 |
410 | 아래 쿼리에서 소트 연산을 생략하도록 인덱스를 구성해보자!
411 |
412 | ```sql
413 | SELECT 계약ID, 청약일자, 입력자ID, 계약상태코드, 보험시작일자, 보험종료일자
414 | FROM 계약
415 | WHERE 취급지점ID =: trt_brch_id
416 | AND 청약일자 BETWEEN :sbcp_dt1 AND :sbcp_dt2
417 | AND 입력일자 >= trunc(sysdate - 3)
418 | AND 계약상태코드 IN (:ctr_stat_cd1, :ctr_stat_cd2, :ctr_stat_cd3)
419 | ORDER BY 청약일자, 입력자ID
420 | ```
421 |
422 | - 정답
423 |
424 | `취급지점ID + 청약일자 + 입력자ID`, 입력일자, 계약상태코드는 상황에 따라
425 |
426 | 이 조건을 만족하는 데이터가 적으면 인덱스에 추가하는 것이 좋다!(테이블 필터링 감소)
427 |
428 |
429 | ### IN 조건은 '='이 아니다
430 |
431 | 위의 쿼리에서 계약상태코드를 인덱스 앞에 두면 소트 연산이 생략될까?
432 |
433 | Nope! IN 조건은 '='이 아니다
434 |
435 | ```sql
436 | SELECT 고객번호, 고객명, 거주지역, 혈액형, 연령
437 | FROM 고객
438 | WHERE 거주지역 = '서울'
439 | AND 혈액형 IN ('A', 'O')
440 | ORDER BY 연령
441 | ```
442 |
443 | → 소트 연산을 생략하려면 IN-List Iterator 방식으로 풀려선 안됨
444 |
445 | ⇒ IN 조건절을 인덱스 액세스 조건X, 필터 조건 O
446 |
447 | ### 3.4.6 결합 인덱스 선택도
448 |
449 | - 선택도: 전체 레코드 중에서 조건절에 의해 선택되는 레코드 비율(선택도 * 총 레코드수 = 카디널리티)
450 | - 인덱스 생성 여부를 결정할 때 선택도가 충분히 낮은지가 중요한 판단 기준
451 | - 결합 인덱스 컬럼간 순서에 선택도가 중요할까? No
452 | - ex) WHERE 성별 =: gender AND 고객번호 =: cust_id
453 |
454 | → 인덱스 액세스 조건이니까 무관! 선택도 높은 걸 앞에 두는 노력은 의미없거나 손해일수도!
455 |
456 | - '항상 사용하는' 컬럼을 앞쪽에 두고 그중 '=' 조건을 앞쪽에 위치시키는 것이 중요
457 |
458 | ```sql
459 | WHERE 고객등급 =: V1
460 | AND 고객번호 =: V2
461 | AND 거래일자 >=: V3
462 |
463 | WHERE 고객등급 =: V1
464 | AND 고객번호 =: V2
465 | AND 거래일자 >=: V3
466 | AND 거래유형 =: V4
467 |
468 | WHERE 고객등급 =: V1
469 | AND 고객번호 =: V2
470 | AND 거래일자 >=: V3
471 | AND 상품번호 =: V5
472 |
473 | WHERE 고객등급 =: V1
474 | AND 고객번호 =: V2
475 | AND 거래일자 >=: V3
476 | AND 거래유형 =: V4
477 | AND 상품번호 =: V5
478 | ```
479 |
480 | - 필수) 고객번호(=), 고객 등급(=), 거래일자(between) + 옵션) 거래유형, 상품번호
481 | - 하지만, 고객변호는 필수인데 고객등급이 조건절에서 누락되거나 범위검색 조건일 수 있는 경우라면 고객 등급을 앞에 두는 것이 유리! → Index Skip Scan, In-List 조건 사용 가능
482 |
483 | ⇒ 인덱스 생성 여부에는 선택도가 중요하지만 컬럼 간 순서에는 각 컬럼의 선택도보다 필수 조건 여부, 연산자 형태가 더 중요한 판단 기준!
484 |
485 | ### 3.4.7 중복 인덱스 제거
486 |
487 | ### 완전 중복 인덱스
488 |
489 | X01: 계약ID + 청약일자
490 |
491 | X02: 계약ID + 청약일자 + 보험개시일자
492 |
493 | X03: 계약ID + 청약일자 + 보험개시일자 + 보험종료일자
494 |
495 | ### 불완전 중복 인덱스
496 |
497 | X01: 계약ID + 청약일자
498 |
499 | X02: 계약ID + 보험개시일자
500 |
501 | X03: 계약ID + 보험종료일자
502 |
503 | X04: 계약ID + 데이터생성일시
504 |
505 | → 계약ID 카디널리티가 매우 낮다면 사실상 중복
506 |
507 | ⇒ 계약ID + 청약일자 + 보험개시일자 + 보험종료일자 + 데이터생성일시
508 |
509 | ### 3.4.8 인덱스 설계도 작성
510 |
511 | 시스템 전체 효율을 고려해야하기 때문에 설계도 필요
512 |
513 | - 인덱스 설계 전에 파티션 설계를 먼저 진행하거나 병행해야 제대로 된 인덱스 전략을 수립할 수 있다
514 |
515 | 
516 |
517 | ### 질문
518 |
519 | - 216p 어떤 경우에는 OR Expantion 동작하는지?
520 | - 228p, 229p 함수 호출 횟수 왜,,?
521 | - 222P `where 고객ID = 고객ID`의 의미?
522 | - 인덱스 압축?
523 | - `where a =: a and b =: b` vs `where b =: b and a =: a` 다른가?
524 |
--------------------------------------------------------------------------------
/4장_조인_튜닝/.empty:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Meet-Coder-Study/nice-sql-tuning/6ebb1473a41c04d97351f3527a5c2311b4fc4973/4장_조인_튜닝/.empty
--------------------------------------------------------------------------------
/4장_조인_튜닝/4.1_NL_조인_김광훈.md:
--------------------------------------------------------------------------------
1 | # 4.1 NL 조인
2 | 조인 메소드으로는 NL 조인, 해쉬 조인, 소트 머지 조인 등이 존재한다.
3 |
4 | MySQL 은 버전에 따라 지원하는 조인 메소드가 다르다.
5 |
6 | 5.7 버전까지는 NL 조인만 지원하였고, 8.0 부터는 해쉬 조인까지 지원하게 되었다.
7 |
8 | https://dev.mysql.com/doc/refman/8.0/en/hash-joins.html
9 |
10 |
11 |
12 | ## 4.1.1 NL 조인 메커니즘
13 | NL 조인 (Nest Loop Join)은 이름 그대로 중첩 루프를 사용하는 조인이다.
14 |
15 |
16 |
17 | 조인 과정은 아래와 같다.
18 | - (1) Table A 에서 row 를 스캔한다.
19 | - 이때 Table A 를 Driving Table 혹은 Outer Table 이라고 한다.
20 |
21 |
22 | - (2) Outer Table 의 row 를 순회할 때 마다 Table B 를 스캔해서 Join 조건에 맞으면 리턴을 한다.
23 | - 이떄 Table B 를 Inner Table 이라고 한다.
24 |
25 |
26 | - (3) 위 1,2 과정을 계속 진행합니다.
27 |
28 |
29 |
30 | 예제로 한 번 더 살펴보자.
31 |
32 |
33 |
34 |
35 |
36 | ## 4.1.2 실행 게획
37 |
38 | MySQL 5.7 에서 두 테이블을 Join 하는 SQL 의 실행 계획을 확인해보자.
39 |
40 |
41 |
42 | 실행 계획을 확인해보면 NL 조인을 사용하는 것을 볼 수 있다.
43 |
44 | 또한 Index 를 사용하는 것도 볼 수 있다.
45 |
46 |
47 |
48 | ## 4.1.3 올바른 조인 메소드 선택
49 | (1) 1차적으로 NL 조인부터 고려하는 것이 좋다.
50 |
51 | (2) 그 이후에 성능이 좋지 않다면 NL 조인 튜닝 포인트에 따라 각 단계를 분석해서 과도한 랜덤 액세스가 발생하는 지점을 우선 파악한다.
52 |
53 | (3) 그 이후에도 나아지지 않는다면 소트 머지 조인이나 해시 조인을 검토한다.
54 |
55 |
56 | #### + 랜덤 엑세스 복습
57 | SSD 에서 랜덤 엑세스가 효율이 좋은 이유
58 |
59 |
60 |
61 | 랜덤 I/O vs 순차 I/O
62 |
63 |
64 |
65 |
66 | ## 4.1.4 NL 조인 특징 요약
67 | (1) 랜덤 엑세스 위주의 조인 방식이다. 따라서 대량의 데이터를 조인할 때에는 효율이 떨어진다.
68 |
69 | (2) 한 코레드씩 순차적으로 진행하다. 따라서 아무리 큰 테이블을 조인하더라도 매우 빠른 응답 속도를 낼 수 있다.
--------------------------------------------------------------------------------
/4장_조인_튜닝/6주_김민석.md:
--------------------------------------------------------------------------------
1 | # 4.4장 서브쿼리 조인
2 |
3 | ### 4.4.1 서브쿼리 변환이 필요한 이유
4 |
5 | - 서브쿼리의 종류
6 |
7 | 
8 |
9 | 1. 인라인 뷰 : FROM 절에 사용한 서브쿼리
10 | 2. 중첩된 서브쿼리 : 결과집합을 한정하기 위해 WHERE 절에 사용한 서브쿼리. 서브쿼리가 메인쿼리 컬럼을 참조할 때 상관관계 있는(Correlated) 서브쿼리라고 한다.
11 | 3. 스칼라 서브쿼리 : 한 레코드당 정확히 하나의 값을 반환하는 서브쿼리. 주로 SELECT 절에 사용하지만 컬럼이 올 수 있는 대부분 위치에 사용가능.
12 |
13 |
14 | 메인쿼리도 하나의 쿼리 블록이며 옵티마이저는 쿼리 블럭 단위로 최적화 수행.
15 |
16 | 
17 |
18 | 하지만, 서브쿼리별로 따로 최적화했다고 해서 전체 쿼리가 최적화 됐다고 할 수 없기 때문에 전체를 바라보는 관점에서 쿼리를 이해하려면 서브쿼리는 풀어내야 한다.
19 |
20 |
21 | ### 4.4.2 서브쿼리와 조인
22 |
23 | 메인쿼리와 서브쿼리간에는 부모와 자식같은 종속적이고 계층적인 관계가 형성된다. 서브가 메인에 종속되기 때문에 단독으로 실행할 수 없고 메인에서 값을 받아서 반복적으로 필터링하는 방식을 사용한다.
24 |
25 | #### 필터 오퍼레이션
26 |
27 | unnest 하지 않고 NL 조인 처럼 서브쿼리 하는 방식
28 |
29 | 
30 |
31 | - 필터방식으로 처리하기 위해 no_unnest 힌트 사용
32 | - no_unnest : unnest(풀어내지) 말고 그대로 수행하라는 뜻.
33 | - 필터 오퍼레이션은 NL 조인과 처리 루틴이 같다.
34 | - 다른 점은
35 | - 조인에 성공하는 순간(exist) 진행을 멈추고 다음 로우 진행
36 | - 필터가 각 서브쿼리 수행에 대해서 캐싱 한다.
37 |
38 | #### 서브쿼리 Unnesting
39 |
40 | unnest : 중첩된 상태를 풀어낸다
41 |
42 | 서브쿼리를 unnest 할 때는 unnest 힌트 사용
43 |
44 | 서브쿼리를 풀지 않고 쓰면 필터를 사용해야 하지만 unnest 하게 되면 일반 조인 처럼 다양한 최적화 기법을 사용할 수 있게 된다.
45 |
46 | - NL 세미조인
47 | - 필터처럼 조인에 성공하는 순간 메인 쿼리의 다음 로우로 넘어간다
48 | - 
49 | - leading
50 | - 필터를 쓰면 메인쿼리가 항상 드라이빙 집합이지만 unnest 하게되면 leading 힌트를 통해서 순서를 바꿀 수 있다.
51 | - 
52 | - 아래 쿼리로 변환된것과 같은 효과
53 | - 
54 | - hash 조인
55 | - unnest 하고 해쉬 조인 적용
56 | - 
57 |
58 | #### 서브쿼리 Pushing
59 |
60 | 필터방식에서 서브쿼리는 순서가 정해지고 맨 마지막 단계에 처리된다. 하지만, push_subq 힌트를 사용하여 서브쿼리 필터링을 먼저 처리하게 해서 처리량을 줄일 수 있다. 따라서, push는 필터링 상태에서만 적용이 가능하다.
61 |
62 | 
63 |
64 | ### 뷰와 조인
65 |
66 | 옵티마이저가 뷰 쿼리를 변환하지 않으면 뷰 쿼리 블록을 독립적으로 최적화 하기 때문에 전체 최적화가 안될 수 있다.
67 |
68 | 
69 |
70 | 전월 이후 가입 고객 필터 조건이 뷰 바깥에 있기 때문에 뷰 안에서는 전체 고객에 대한 데이터를 읽어야 한다.
71 |
72 | 이 문제는 merge 힌트를 사용하여 해결 할 수 있다.
73 |
74 | 
75 | 
76 |
77 |
78 | #### 조인 조건 Pushdown
79 |
80 | 메인 쿼리를 실행하면서 조인 조건절 값을 건건이 뷰 안으로 밀어 넣는 기능이다.
81 |
82 | 
83 | 
84 |
85 | 아래와 같이 최적화 되었다고 생각하면 됨(문법은 틀림)
86 |
87 | 
88 |
89 | 이렇게 되면 부분범위 처리 가능.
90 |
91 |
92 | ### 스칼라 서브쿼리 조인
93 |
94 | #### 스칼라 서브쿼리의 특징
95 |
96 | select 쿼리를 품은 사용자 함수가 있다면 쿼리 건수만큼 재귀적으로 반복 실행된다.
97 | 하지만 스칼라 서브쿼리는 정확히 하나의 값만 반환한다. 함수처럼 재귀적이지 않으면서 컨텍스트 스위칭도 발생하지 않는다.
98 |
99 | 
100 |
101 | #### 스칼라 서브쿼리 캐싱 효과
102 |
103 | 스칼라 서브쿼리로 조인할 때 입력값과 출력값은 캐싱된다.
104 |
105 | 입력값 : 서브쿼리에서 참조하는 메인 쿼리의 컬럼 값
106 |
107 | 
108 |
109 | 필터 서브쿼리 캐싱관 같은 기능.
110 | 쿼리를 시작할 때 PGA 메모리에 공간을 할당하고 쿼리가 끝나면 반환된다.
111 |
112 | 아래와 같이 캐싱을 사용하여 내장함수의 비효율성을 상쇄시킬 수 있다.
113 |
114 | 
115 |
116 | #### 스칼라 서브쿼리 캐싱 부작용
117 |
118 | 스칼라 서브쿼리 캐싱도 캐싱이 가지는 단점을 그대로 가지고 있다.
119 |
120 | - 입력 값의 종류가 적어서 해시 충돌 가능성이 적을 때 효과가 있다. 충돌이 많다면 당연히 성능이 낮아진다.
121 | - 좋은예) 거래구분코드가 20건이라서 입력값(t.거래구분코드)의 종류가 적다
122 | 
123 | - 나쁜예) 고객이 100만명이라서 입력값(t.고객번호)의 종류가 많다.
124 | 
125 |
126 | - 메인 쿼리 집합이 매우 작은 경우
127 | - 메인쿼리 집합이 클수록 재사용성이 높아서 캐싱 효율이 좋다.
128 | - 나쁜예) 고객당 계좌가 많지 않은 경우
129 | - 
130 |
131 |
132 | #### 두개 이상의 값 반환
133 |
134 | 
135 | 
136 |
137 |
138 | NL 조인 처럼 처리되기 때문에 부분범위 처리도 가능하고 캐싱의 효과를 쓸 수도 있어서 좋아보이지만 스칼라 서브쿼리는 두 개 이상의 값을 반환할 수가 없는 문제가 있다.
139 |
140 | 이런 경우 아래와 같은 방법을 사용한다.
141 |
142 | 
143 |
144 | 구하는 값들을 문자열로 결합하고 바깥쪽 쿼리에서 다시 분리한다.
145 |
146 |
147 | 다른 방법은 인라인 뷰를 사용한다.
148 |
149 | 
150 |
151 | 물론 앞에서 설명한 부분범위처리가 안되는 등의 인라인 뷰 머지의 문제가 있기 때문에 '조인 조건 pushdown' 기능을 통해서 문제를 해결할 수 있다.
152 |
153 | #### 스칼라 서브쿼리 Unnesting
154 |
155 | 스칼라 서브쿼리도 캐싱 효과가 미미하면 다른 조인 방식을 사용하기 위해 unnesting 해야 할 때가 있다.
156 | 쿼리가 길고 복잡하면 쉽지 않은데 다행히 오라클 12c부터 옵티마이저가 자동으로 쿼리를 변환해주는 기능이 생겼다.
157 |
158 | `_optimizer_unnest_scalar_sq = true'
159 |
160 | false로 설정하더라도 unnest 힌트로 유도 가능.
161 |
162 | 
163 |
164 | Unnesting 되었기 때문에 NL 조인이 아닌 해시조인 실행됨.
165 |
--------------------------------------------------------------------------------
/5장_소트_튜닝/.empty:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Meet-Coder-Study/nice-sql-tuning/6ebb1473a41c04d97351f3527a5c2311b4fc4973/5장_소트_튜닝/.empty
--------------------------------------------------------------------------------
/5장_소트_튜닝/5.1~5.1_내용_요약_김광훈.md:
--------------------------------------------------------------------------------
1 | # 5.1 소트 연산에 대한 이해
2 | ## 5.1.1 소트 수행 과정
3 | 소트는 기본적으로 PGA 에 할당한 Sort Area 에서 이루어진다.
4 |
5 | 메모리 공간인 Sort Area 가 다 차면, 디스크 Temp 테이블 스페이스를 활용한다.
6 |
7 | - 소트 유형
8 | - 메모리 소트
9 | - 디스크 소트
10 |
11 |
12 |
13 | # 5.2 소트가 발생하지 않도록 SQL 작성
14 | ## 5.2.1 Union vs Union ALl
15 | - Union
16 | - 옵티마이저는 상단과 하단 두 집합 간 중복을 제거하려고 소트 작업을 수행한다.
17 |
18 |
19 | - Union All
20 | - 중복을 확인하지 않고 두 집합을 단순히 결합
21 | - 소트 작업을 수행하지 않는다.
22 | - 두 집합이 상호 배타적일때 사용해도 된다.
23 |
24 | ## 5.2.2 Exist 활용
25 | - Distinct
26 | - 조건에 해당하는 데이터를 모두 읽어서 중복을 제거한다.
27 | - 따라서 많은 I/O 가 발생한다.
28 |
29 |
30 | - Exist
31 | - 데이터의 존재 여부만 확인하면 되기 떄문에 모든 데이터를 읽지 않는다.
32 | - [같이 보면 좋은 글](https://jojoldu.tistory.com/516)
33 |
34 |
35 |
36 |
37 | # 5.3 인덱스를 이용한 소트 연산 생략
38 | ## 5.3.1 Sort Order By 생략
39 | 모든 데이터를 다 읽어 정렬을 마치고 출력을 시작하면 OLTP 환경에서 요구되는 빠른 응답 속도를 내기 어렵다.
40 |
41 | 따라서 인덱스 선두 컬럼에 조건을 구성하여 쿼리에서 소트 연산을 생략하도록 하자.
42 |
43 | 인덱스는 항상 키 컬럼 순으로 정렬된 상태를 유지하기 때문에 적절하게 쿼리를 작성하면 소트 연산을 생략할 수 있다.
44 |
45 |
46 |
47 | ** 3-Tier 환경
48 |
49 | 클라이언트 - WAS - DB 로 구성된 구조
50 |
51 | 해당 아키텍처는 수 많은 클라이언트가 서버 리소스를 공유하는 구조이기 때문에 클라이언트가 특정 DB 커넥션을 독점할 수 없다.
52 |
53 | 독점을 하게 된다면 커넥션 풀이 말라 이슈가 발생할 것이다.
54 |
55 |
56 |
57 | # 5.4 Sort Area 를 적게 사용하도록 SQL 작성
58 | 소트 연산이 불가피하다면 메모리 내에서 처리를 완료할 수 있도록 노력해야 한다.
59 |
60 | 가장 중요한 것은 Sort Area 를 적게 사용할 방법부터 찾는 것이다.
61 |
62 | ## 5.4.1 소트 데이터 줄이기
63 | #### Case 1.
64 | - 일반 쿼리 / 서브 쿼리
65 | - 서브 쿼리를 사용하면 가공하지 않은 상태로 정렬을 완료하고 나서 최종 출력할 때 가공한다.
66 | - 따라서 Sort Area 를 훨씬 더 적게 사용한다.
67 |
68 | #### Case 2.
69 | - Select * / Select XX, TT
70 | - Select XX, TT 이 Sort Area 를 더 적게 사용한다.
71 | - 왜냐하면 XX, TT 두 필드만 저장하기 때문이다.
72 |
--------------------------------------------------------------------------------
/5장_소트_튜닝/5.1~5.4_황준호.md:
--------------------------------------------------------------------------------
1 | ## 5장. 소트 튜닝
2 |
3 | ### 5.1 소트 연산에 대한 이해
4 |
5 | - 소트는 기본적으로 Sort Area에서 이루어지지만 Sort Area가 다 차면 디스크 공간까지 사용할 수도 있다
6 |
7 | - Sort Area에서 작업을 완료할수 있는지에 따라 소트를 두가지 유형으로 나눈다
8 |
9 | 1. 메모리 소트 (=Internal Sort) : 전체 소트를 메모리 내에서 완료
10 | 2. 디스크 소트 (=External Sort) : 소트를 하는데에 디스크 공간까지 사용
11 |
12 | - 디스크 소트가 발생하는 순간 쿼리 성능은 나빠지므로 소트가 발생하지 않도록 SQL을 작성해야 하고, 소트가 불가피하다면 메모리 내에서 완료될 수 있도록 해야 한다
13 |
14 | - 소트를 발생시키는 오퍼레이션
15 |
16 | 1. Sort Aggregate
17 | - 전체 로우를 대상으로 집계를 수행할때 (sum, max, min, avg ...)
18 | - 실제로 데이터를 정렬하진 않고, Sort Area를 사용한다
19 | 2. Sort Oeder By
20 | - 데이터를 정렬할때 (order by ...)
21 | 3. Sort Group By
22 | - 소팅 알고리즘을 사용해 그룹별 집계를 수행할때 (group by ...)
23 | - 집계할 대상 레코드가 아무리 많아도 Temp 테이블스페이스를 쓰지 않는다
24 | 4. Sort Unique
25 | - 서브쿼리 Unnesting : 옵티마이저가 서브쿼리를 풀어 일반 조인문으로 변환하는 것
26 | - Unnesting된 서브쿼리가 M쪽 집합일때 메인쿼리와 조인하기 전에 중복 레코드부터 제거할때
27 | 5. Sort Join
28 | - 소트 머지 조인을 수행할때
29 | 6. Window Sort
30 | - 윈도우 함수(분석 함수)를 수행할때 (`avg(sal) over (partition by deptno)` ...)
31 |
32 | ### 5.2 소트가 발생하지 않도록 SQL 작성
33 |
34 | - 불필요한 소트가 발생하지 않도록 주의해야 한다
35 |
36 | - `Union`, `Minus`, `Distinct` 연산자는 중복 레코드를 제거하기 위한 소트 연산을 발생시키므로 필요할때만 사용해야 한다
37 |
38 | - `Union` VS `Union All`
39 |
40 | - 될 수 있으면 소트 작업을 수행하지 않는 `Union All`을 사용해야 한다
41 |
42 | - `Union`을 사용하는 쿼리를 `Union All`으로 변경하려면 `Union All`하는 두 집합이 상호 배타적이어야 (교집합이 없을때) 한다. 그래야 데이터 중복이 없다
43 |
44 | - `Union` -> `Union All` 변경 예시
45 |
46 | ```sql
47 | -- union 사용
48 | select *
49 | from 결제
50 | where 결제일자 = '20180316'
51 | UNION
52 | select *
53 | from 결제
54 | where 주문일자 = '20180316'
55 |
56 | -- union all 사용
57 | select *
58 | from 결제
59 | where 결제일자 = '20180316'
60 | UNION ALL
61 | select *
62 | from 결제
63 | where 주문일자 = '20180316'
64 | and 결제일자 <> '20180316'
65 | ```
66 |
67 | - Exists 활용
68 |
69 | - 조인 방식 변경
70 |
71 | ### 5.3 인덱스를 이용한 소트 연산 생략
72 |
73 | - 인덱스 선두 컬럼을 where절에 나오는 컬럼으로 구성하면 sort 연산을 생략할 수 있다
74 |
75 | - 부분범위 처리가 가능하도록 SQL를 작성하려면 아래를 만족해야 한다
76 |
77 | - 인덱스 사용 가능하도록 조건절을 구성한다
78 | - 조인은 NL조인 위주로 처리한다
79 | - Order by 절이 있어도 소트 연산을 생략할 수 있도록 인덱스를 구성해준다
80 |
81 | - 인덱스를 잘 구성하면 min, max를 구할때 전체 데이터를 읽지 않을 수 있다
82 |
83 | - 조건절 컬럼과 min, max함수 인자 컬럼이 모두 인덱스에 포함돼 있어야 한다
84 |
85 | - 예
86 |
87 | ```sql
88 | create index IDX_X1 on TABLE_NAME(a, b, c);
89 |
90 | select max(c)
91 | from TABLE_NAME
92 | where a = 30
93 | and b = 70;
94 | -- a, b 두 조건을 만족하는 레코드 중 가장 오른쪽 레코드를 읽는다
95 | ```
96 |
97 | ```sql
98 | create index IDX_X1 on TABLE_NAME(a, b, c);
99 |
100 | select max(b)
101 | from TABLE_NAME
102 | where a = 30
103 | and c = 70;
104 | -- a 조건을 만족하는 레코드 중 오른쪽부터 스캔하면서 c조건에 맞는 레코드를 찾으면 멈춘다
105 | ```
106 |
107 | ```sql
108 | create index IDX_X1 on TABLE_NAME(a, b, c);
109 |
110 | select max(a)
111 | from TABLE_NAME
112 | where b = 30
113 | and c = 70;
114 | -- 전체 레코드 중 오른쪽부터 스캔하면서 b, c조건에 맞는 레코드를 찾으면 멈춘다
115 | ```
116 |
117 | - 인덱스 선두 컬럼을 group by절에 사용한다면 sort group by 연산을 생략할 수 있다
--------------------------------------------------------------------------------
/5장_소트_튜닝/5.3_인덱스를_이용한_소트연산_생략_유효정.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Meet-Coder-Study/nice-sql-tuning/6ebb1473a41c04d97351f3527a5c2311b4fc4973/5장_소트_튜닝/5.3_인덱스를_이용한_소트연산_생략_유효정.pdf
--------------------------------------------------------------------------------
/5장_소트_튜닝/5장_김민석.md:
--------------------------------------------------------------------------------
1 | # 5장 소트 튜닝
2 |
3 | ## 5.1 소트 연산에 대한 이해
4 |
5 | ### 소트 수행 과정
6 |
7 | - 소트 연산은 메모리와 CPU를 많이 쓴다.
8 | - 처리할 양이 많을 때는 디스크 I/O를 사용하기도 한다.
9 | - 부분범위 처리가 불가해서 OLTP 환경에서 앱 성능을 저하시킨다.
10 | - 따라서, 되도록 소트를 안하도록 SQL를 작성하고 불가피할경우 메모리내에서 수행이 완료되도록 한다.
11 |
12 | ### 소트 오퍼레이션
13 |
14 | - Sort Aggregate
15 | - 실제 데이터를 정렬하진 않고 Sort Area 사용
16 | - SUM / MAX / MIN / COUNT
17 | - Sort Order By
18 | - 실제 소트 실행
19 | - Sort Group By
20 | - 소팅 알고리즘을 사용해 그룹별 집계 수행
21 | - Sort Unique
22 | - Unnesting된 서브쿼리가 M쪽 집합이면 메인 쿼리와 조인하기 전에 중복 레코드 제거
23 | - Sort Join
24 | - 소트 머지 조인
25 | - Windows Sort
26 | - 분석 함수 수행
27 |
28 | ## 5.2 소트가 발생하지 않고록 SQL 작성
29 |
30 | ### Union vs. Union All
31 |
32 | Union All은 소트 안함. 중복 가능성 없을 경우 Union All 사용하자.
33 |
34 | ### Exists 활용
35 |
36 | 중복 레코드를 제거할 목적으로 Distinct 사용하면 부분범위 처리도 불가할뿐더러 디스크 I/O도 발생한다.
37 |
38 | Exists 서브쿼리는 데이터 존재 여부만 확인하면 되기 때문에 조건절을 만족하는 데이터를 모두 읽지 않는다.
39 |
40 | Distinct, Minus 연산자를 사용한 쿼리는 대부분 Exists 서브쿼리로 변환 가능하다.
41 |
42 | ### 조인 방식 변경
43 |
44 | 조인 방식을 변경하는 것만으로 소트 연산 생략이 가능하다.
45 |
46 |
47 | ## 5.3 인덱스를 이용한 소트 연산 생략
48 |
49 | ### Sort Order By 생략
50 |
51 | 조건절을 고려한 인덱스 생성으로 소트 연산 생략 가능
52 |
53 | ### Top N 쿼리
54 |
55 | 소트가 되어 있는 상황에서 Top N은 필요한 만큼만 연산을 수행한다.
56 |
57 | 대표적으로 페이징이 있다.(물론 뒤쪽 페이지를 select하면 그만큼 많이 읽는다.)
58 |
59 | ### 최소값/최대값 구하기
60 |
61 | 인덱스를 사용하면 바로 읽어올 수 있다.
62 | 이때, 조건절 컬럼과 MIN/MAX 함수 인자 컬럼이 모두 인덱스에 포함돼 있어야 한다.
63 |
64 | ### 이력 조회
65 |
66 |
67 | ### Sort Group By 생략
68 |
69 | 인덱스를 사용하여 Sort Group By 연산 생략이 가능하다. 이때 부분범위 처리도 가능하다.
70 |
71 |
72 | ## 5.4 Sort Area를 적게 사용하도록 SQL 작성
73 |
74 | ### 소트 데이터 줄이기
75 |
76 | 트레이스를 하면서 소트의 데이터량을 직접 비교해 봐야한다.
77 |
78 | ### Top N 쿼리의 소트 부하 경감 원리
79 |
80 | Top N 소트 알고리즘을 사용하여 공간 절약 가능.
81 |
82 | ### 분석함수에서의 Top N 소트
83 |
84 | 윈도우 함수 중 rank / row_number 함수는 max 함수보다 소트 부하가 적다. Top N 소트 알고리즘이 작동하기 때문이다.
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/6장_DML_튜닝/6.1_기본DML튜닝_김광훈.md:
--------------------------------------------------------------------------------
1 | # 6.1 기본 DML 튜닝
2 | ## 6.1.1 DML 성능에 영향을 미치는 요소
3 | #### (1) 인덱스
4 | #### (2) 무결성 제약
5 | #### (3) 조건절
6 | #### (4) 서브쿼리
7 | #### (5) Redo 로깅
8 | #### (6) Undo 로깅
9 | #### (7) Lock
10 | #### (8) 커밋
11 |
12 |
13 |
14 | ### 인덱스와 DML 성능
15 | #### (1) 입력
16 | 테이블에 레코드를 입력하면, 인덱스에도 입력해야 한다.
17 |
18 | 인덱스는 정렬된 자료구조이므로, 수직적 탐색을 통해 입력할 블록을 찾아야 한다.
19 |
20 |
21 |
22 |
23 | #### (2) DELETE
24 | 테이블에서 레코드 하나를 삭제하면, 인덱스 레코드를 모두 찾아서 삭제해야한다.
25 |
26 | #### (3) UPDATE
27 | 변경된 컬럼을 참조하는 인덱스만 찾아서 변경
28 |
29 | 그대신 테이블에서 한 건 변경할 때마다 인덱스에는 두 개의 오퍼레이션 발생
30 |
31 | Example) A --> K 로 변경, 저장 위치가 바뀜
32 |
33 |
34 |
35 |
36 | ### 무결정 제약과 DML 성능
37 | PK, FK, Check, Not Null 같은 제약은 애플리케이션에서 구현할 수 있지만 DBMS 에서 설정한다면 더 완벽하게 데이터 무결성을 보장한다.
38 |
39 | 하지만 성능과 트레이드 오프하는 조건이라는 점은 명심해야한다
40 |
41 | PK, FK 를 검증하기 위해서는 실제 데이터를 조회해 봐야 하기 때문이다.
42 |
43 |
44 |
45 |
46 | ### Redo 로깅과 DML 성능
47 | #### Redo 로그
48 | 오라클은 데이터파일과 컨트롤 파일??에서 발생하는 모든 변경사항을 Redo 로그에 기록
49 |
50 | Redo 로그는 트랜잭션 데이터가 유실됐을 때, 트랜잭션을 재현함으로써 유실 이전 상태로 복구하는 데 사용
51 |
52 | DML 수행을 할때마다 Redo 로그를 생성해야 하므로 성능에 영향을 미친다.
53 |
54 | INSERT 작업을 할 때 Redo 로깅을 생략할 수 있다.
55 |
56 |
57 |
58 | #### Redo 로그 목적
59 | (1) Database Recovery
60 |
61 | 물리적으로 디스크가 깨지는 등의 media fail 발생 시 복구를 위해 사용
62 |
63 | (2) Cache Recovery
64 |
65 | 버퍼캐시는 I/O 성능을 높히기 위해 사용되지만 휘발성이다.
66 |
67 | 인스턴스가 종료되면 버퍼캐시는 휘발되므로 이를 복구하기 위해 사용된다.
68 |
69 | (3) Fast Commit
70 |
71 | 변경사항을 우선 Append 방식으로 빠르게 로그 파일에 기록하고,
72 |
73 | 변경된 메모리 버퍼 블록과 데이터파일 블록 간 동기화는 적절한 수단?? (DBWR, Checkpoint) 을 이용해 추후 배치 방식으로 일괄 수행
74 |
75 | *** 변경된 메모리 버퍼블록을 디스크 상의 데이터 블록에 반영하는 작업 --> 랜덤 엑세스 --> 매우 느림 --> 그래서 Append 방식으로
76 |
77 |
78 |
79 | ### Undo 로깅과 DML 성능
80 | Undo 는 트랜잭션을 롤백함으로써 현재를 과거 상태로 되돌리는 데 사용한다.
81 |
82 |
83 |
84 |
85 | 오라클은 데이터를 입력, 수정, 삭제할 때마다 Undo 세그먼트에 기록을 남긴다.
86 |
87 |
88 |
89 | #### Undo 로그 목적
90 | (1) Transaction RollBack
91 |
92 | 트랜잭션에 의한 변경사항을 최종 커밋하지 않고 롤백하고자 할 때 사용
93 |
94 | (2) Transaction Recovery
95 |
96 | Redo 를 이용해 roll forward 단계가 완료되면 최종 커밋되지 않은 변경사항까지 모두 복구 된다.
97 |
98 | 따라서 셧다운 시점에 아직 커밋되지 않았던 트랜잭션들을 모두 롤백해야 하는데 이때 사용한다.
99 |
100 |
101 | (3) Read Consistency
102 |
103 | 데이터 읽기 일관성을 위해 사용
104 |
105 |
106 |
107 | #### MVCC 모델 (Multi-Version Concurrency Control Model)
108 | (1) Current 모드
109 |
110 | 디스크에서 캐시로 적재된 원본(current) 블록을 현재 상태 그대로 읽는 방식
111 |
112 |
113 | (2) Consistence 모드
114 |
115 | 다른 트랜잭션에 의해 변경된 블록을 만나면 원본 블록으로부터 복사본 블록을 만들고, 거기에 Undo 데이터를 적용
116 |
117 |
118 |
119 |
120 |
121 | ** SCN (System Commit Number)
122 |
123 | 마지막 커밋이 발생한 시점정보를 SCN Global 변수 값을 관리
124 |
125 | ** 블록 SCN
126 |
127 | 각 블록이 마지막으로 변경된 시점을 관리하기 위해 모든 블록 헤더에 SCN 을 기록
128 |
129 | ** 쿼리 SCN
130 |
131 | 모든 쿼리는 Global 변수인 SCN 값을 먼저 확인하고 읽기 작업 시작 이를 쿼리 SCN 이라고 함
132 |
133 | ** 다시 정리
134 |
135 | Consistence 모드는 쿼리 SCN 과 블록 SCN 을 비교함으로서 쿼리 수행 도중에 블록이 변경되었는지 확인하면서 데이터를 읽는 방식
136 |
137 | 데이터를 읽다가 블록 SCN 이 쿼리 SCN 보다 더 큰 블록을 만나면 복사본 블록을 만들고 Undo 적용
138 |
139 |
140 |
141 |
142 | 다시 정리 !!
143 |
144 |
145 |
146 |
147 | MySQL InnoDB 예제 !!
148 |
149 | 기본 세팅
150 |
151 |
152 |
153 | 데이터 UPDATE
154 |
155 |
156 |
157 | 트랜잭션 격리 수준에 따라 읽는 장소가 다르다 !!
158 |
159 | - READ_UNCOMMITTED: InnoDB 버퍼 풀이나 데이터 파일로부터 읽음 --> 데이터 커밋 상관 없이 읽음
160 | - 그 이상: Undo 영역을 읽음
161 |
162 |
163 | ** 예제 요약
164 |
165 | UPDATE 쿼리를 실행하면 InnoDB 버퍼 풀은 즉시 새로운 데이터로 변경되고, 기존 데이터는 Undo 영역으로 복사
166 |
167 | 여기서 COMMIT 명령을 실행하면 InnoDB 는 더 이상 변경 작업 없이 지금의 상태를 영구 데이터로 변환
168 |
169 | 하지만 롤백이 발생한다면 Undo 영역에 있는 백업된 데이터를 InnoDB 버퍼 풀로 다시 복구 그리고 Undo 영역 내용 삭제
170 |
171 |
172 |
173 |
174 |
175 | ### Redo vs Undo
176 | #### 개념 비교
177 |
178 |
179 | #### checkpoint
180 |
181 |
182 | 각 트랜잭션 이해하기
183 |
184 |
185 |
186 |
187 | #### ** 참고
188 | http://blog.skby.net/%EB%8B%A4%EC%A4%91-%EB%B2%84%EC%A0%84-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4-mvcc-multi-version-concurrency-control/
189 |
190 | https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=dbmsexpert&logNo=220441276804
191 |
192 | https://mangkyu.tistory.com/53
--------------------------------------------------------------------------------
/6장_DML_튜닝/6.3_최락준.md:
--------------------------------------------------------------------------------
1 | # 6.3 파티션을 활용한 DML 튜닝
2 |
3 |
4 | ### 파티션이란
5 | - 하나의 테이블 또는 인덱스를 파티션 키에 따라 별도 세그먼트에 저장하는 것
6 | - 논리적으로는 하나의 테이블이지만 실제로 물리적으로는 여러 개의 테이블의 효과
7 |
8 | ### 파티션이 필요한 이유
9 | - 관리적 측면 : 파티션 단위의 작업 -> 같은 테이블이라도 서로 다른 파티션에 영향을 미치지 않음 -> 가용성 향상
10 | - 성능적 측면 : 경합 또는 부하 분산
11 |
12 | ## 6.3.1 테이블 파티션
13 |
14 | ### (1). 파티션 테이블의 성능 향상 원리
15 | * 파티션 테이블의 성능 향상 원리는 파티션 pruning을 통해 sql파싱이나 실행 시점에 조건절을 분석하여 **다른 파티션 세그먼트를 액세스 대상에서 제외**하는 것에 있다.
16 | * 예를 들어 파티션이 적용되지 않은 테이블에 1000만건의 데이터가 있다면 1000만 건 중 조건에 따른 데이터를 필터링 하지만,
17 | * 파티션이 적용된 경우 1000만건 중 파티션닝이 된 테이블만 액세스 하여 필터링 하기 때문에 성능상 이점이 있다.
18 |
19 | ### (2). range 파티션
20 | * 날짜 컬럼과 같이 범위 값을 기준으로 파티셔닝
21 | * 보관 주기 정책에 따라 관리가 달라지는 경우 효율적 (ex. 1년만 보관하고 백업하는 로그 테이블)
22 |
23 | ### (3) 해시 파티션
24 | * 파티션 개수만 사용자가 결정하고 데이터를 분산하는 알고리즘은 오라클 내부 해시함수가 결정
25 | * 해시 알고리즘 특성상 등치 조건 또는 In-List 조건에서만 파티션 Pruning이 작동
26 |
27 | ### (4) 리스트 파티션
28 |
29 | * 해시 파티션은 오라클 내부의 해시 알고리즘에 따라 임의로 분할하는 반면, 리스트 파티션은 사용자가 정의한 논리적인 그룹에 따라 분할
30 | * 예를 들어 사용자가 지역별로 파티션 키를 설정하면, '서울', '경기', '부산' 등과 같은 논리적 기준에 따라 파티셔닝 된다.
31 |
32 | ### 인덱스 파티션
33 |
34 | ### (1). 로컬 파티션 인덱스
35 | * 각 테이블 파티션과 1:1로 대응 되는 인덱스 파티션.
36 | * 예를 들어 테이블이 월별로 파티셔닝 되어 있다면, 각각의 파티셔닝 테이블에 마찬가지로 인덱스가 만들어 진다.
37 | * 인덱스 파티션은 테이블 파티션의 속성을 그대로 상속 받는다. 따라서 `테이블 파티션 키 == 인덱스 파티션 키`가 된다.
38 | * 파티션 테이블이 변경 되면 로컬 파티션 인덱스도 이에 맞추어 오라클이 자동으로 변경해 준다.
39 |
40 | > quiz
41 | ```
42 | 파티셔닝 되지 않은 테이블의 경우 로컬 파티션 인덱스가 존재 할까요?
43 | ```
44 |
45 | ### (2). 글로벌 파티션 인덱스
46 |
47 | * 파티션을 테이블과 다르게 구성한 인덱스. 로컬 인덱스가 아니면 **모두 글로벌 파티션 인덱스**이다.
48 | * 파티션 유형이 다르거나, 파티션 키가 다르거나, 파티션 기준값 정의가 다른 경우
49 | * 테이블 파티션 구성을 변경하는 경우 Unusable 상태로 바뀌므로 재생성해야 한다.
50 |
51 | ### (3). 비파티션 인덱스
52 |
53 | * 파틱셔닝 되지 않은 인덱스
54 | * 비파티션 인덱스와 파티션 테이블과 조합될 때, 테이블에 변경이 있으면 인덱스를 재생성해야 한다.
55 |
56 | 
57 |
58 |
59 | ## 6.3.3 파티션을 활용한 대량 update 튜닝
60 |
61 | 10억 건의 데이터가 저장된 테이블에서 5%(5000만 건)의 데이터 변경이 있을 경우
62 |
63 | ### 1) 5000만 건을 update
64 | -> 5000 만 건에 대한 select/update, 그리고 index 수정에 대한 부담 발생
65 |
66 | ### 2). index 를 drop 하고 5000만 건을 update
67 | -> 10억 건에 대한 index 재생성 부담 발생
68 |
69 | ### 3). 파티션 exchange를 이용
70 | * 테이블이 파티셔닝이 되어 있고 인덱스도 로컬 파티션으로 구성되어 있을 경우 가능
71 | ```sql
72 | (1). 먼저 임시 테이블을 만들고
73 | (2). 임시 테이블에 update 될 데이터를 insert
74 | (3). 임시 테이블에 인덱스를 생성
75 | (4). 기존 파티션 테이블과 임시 테이블을 exchange
76 | (5). 임시 테이블 drop
77 | ```
78 |
79 | 
80 | ## 6.3.4 파티션을 활용한 대량 delete 튜닝
81 |
82 | update 는 변경 대상 컬럼을 포함하는 인덱스만 재생성하면 되지만, delete는 모든 인덱스를 재생성 해야 하기 때문에
83 | 인덱스 재생성 비용이 더 크다.
84 |
85 | ### 1) 파티션 drop을 이용한 대량 데이터 삭제
86 |
87 | * 삭제 조건절 컬럼 기준으로 파티셔닝 되어 있고, 로컬 인덱스일 경우 간단히 삭제 가능
88 | * 예를 들어 월별로 파티셔닝 되어 있고, 1월 파티션을 통째로 삭제하는 경우
89 | ```sql
90 | alter table 거래 drop partition p201412;
91 | ```
92 |
93 | ### 2) 파타션 truncate를 이용한 대량 데이터 삭제
94 | * 위와 같이 파티셔닝 기준으로 일괄 삭제하지 않고 또 다른 삭제 조건이 있는 경우
95 | * 지워야할 데이터가 대다수라면, 데이터를 지우는 것 보다 전부 삭제 후 남길 데이터를 insert 하는 것이 낫다.
96 | ```sql
97 | (1). 임시 테이블 생성, 남길 데이터만 insert
98 | (2). 삭제 대상 테이블 파티션을 truncate
99 | (3). 임시 테이블에 복제한 데이터를 원본 테이블에 insert (insert select 사용)
100 | (4). 임시 테이블 drop
101 | ```
102 |
103 | ## 6.3.5 파티션을 활용한 대량 insert 튜닝
104 |
105 | ### 1) 비파티션 테이블일 때
106 | * 비파티션 테이블에서 대량 데이터를 insert 할 때, 인덱스를 unusable 시킨 후 재생성하는 방식이 더 빠를 수 있다.
107 |
108 | ### 2) 파티션 테이블일 때
109 | 테이블이 파티셔닝돼 있고, 로컬 인덱스라면 파티션 단위로 인덱스를 재생성 할 수 있다.
110 |
111 | 
112 |
113 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/6장_DML_튜닝/6.4_Lock과_트랜잭션_동시성_제어_황준호.md:
--------------------------------------------------------------------------------
1 | ### 6.4 Lock과 트랜잭션 동시성 제어
2 |
3 | - 자신이 사용하는 데이터베이스의 Lock 매커니즘과 트랜잭션 동시성 제어에 대한 학습은 필수다!
4 |
5 | - 오라클 Lock 종류
6 |
7 | 1. DML Lock
8 | - 어플리케이션 개발 측면에서 가장 중요하게 다뤄야 할 Lock
9 | - 다중 트랜잭션이 동시에 액세스하는 사용자 데이터의 무결성을 보호해준다
10 | - DML Lock엔 테이블 Lock과 로우 Lock이 있다
11 | 2. DDL Lock
12 | 3. 래치
13 | - SGA에 공유된 각종 자료구조를 보호하기 위해 사용
14 | 4. 버퍼 Lock
15 | - 버퍼 블록에 대한 엑세스를 직렬화하기 위해 사용
16 | 5. 라이브러리 캐시 Lock/Pin
17 | - 라이브러리 캐시에 공유된 SQL 커서와 PL/SQL 프로그램을 보호하기 위해 사용
18 |
19 | - DML 로우 Lock
20 |
21 | - 두 개의 동시 트랜잭션이 같은 로우를 변경하는 것을 방지함
22 | - 같은 로우를 UPDATE/DELETE/INSERT를 시도할때
23 | - UPDATE, DELETE : 진행중인(아직 커밋하지 않은) 로우를 다른 트랜잭션이 수정할 수 없다
24 | - INSERT : unique인덱스가 있고 같은 값을 입력하려고 할때 블로킹이 발생한다.
25 | 1. 선행 트랜잭션이 커밋할 동안 후행 트랜잭션은 기다린다
26 | 2. 선행 트랜잭션이 커밋하면 후행 트랜잭션은 실패하고, 선행 트랜잭션이 롤백하면 후행 트랜잭션은 성공한다
27 | - DML 로우 Lock에 의한 성능 저하를 방지하려면 Lock을 너무 오래 유지하지 않도록 커밋 시점을 조절해야 한다
28 |
29 | - DML 테이블 Lock (= TM Lock)
30 |
31 | - 현재 트랜잭션이 갱신 중인 테이블 구조를 다른 트랜잭션이 변경하지 못하게 하기 위해 로우 Lock 이전에 테이블 Lock을 설정한다
32 |
33 | - 로우 Lock은 1가지 Lock 모드(배타적 모드)만 사용하는 반면 테이블 Lock에는 여러 Lock 모드(RS, RX, S, SRX, X)가 있다
34 |
35 | - 테이블 Lock을 테이블 전체에 걸리는 Lock이라고 오해하기 쉬운데, 그게 아니라 테이블 Lock을 설정한 트랜잭션이 해당 테이블에서 어떤 작업을 수행중인지 알리는 일종의 푯말이라고 이해하면 좋다 (후행 트랜잭션은 그 푯말을 보고 기다릴지, 포기할지 결정된다)
36 |
37 | - Lock이 해제될때까지 기다린다 or 일정 시간만 기다리다 포기한다 or 기다리지 않고 포기한다
38 |
39 | - 예) DDL을 이용하여 테이블 구조를 변경하려 할때
40 |
41 | - 테이블 구조를 변경하려는 트랜잭션은 해당 테이블에 TM Lock이 설정돼있는지 확인한다
42 |
43 | - RX모드로 설정한 트랜잭션이 하나라도 있다면 작업을 멈춘다
44 | - DDL이 먼저 수행중이라면 DML을 수행하려는 트랜잭션이 기다린다
45 |
46 | - 블로킹 vs 교착상태
47 |
48 | - 블로킹 : 선행 트랜잭션이 설정한 Lock 때문에 후행 트랜잭션이 기다리는 상태
49 | - 교착상태 : 두 트랜잭션이 각각 특정 리소스에 Lock을 설정한 상태에서 두 트랜잭션 모두 맞은편 트랜잭션의 리소스에 Lock을 설정하려고 하는 상황
50 | - 오라클에서는 교착상태가 발생하면 이를 먼저 인지한 트랜잭션이 문장 수준 롤백을 진행한 후 에러 메시지를 던진다
51 |
52 | - 트랜잭션 동시성 제어는 비관적 동시성 제어와 낙관적 동시성 제어로 나뉜다
53 |
54 | 1. 비관적 동시성 제어
55 | - 사용자들이 같은 데이터를 동시에 수정할 것으로 가정하고, 한 사용자가 데이터를 읽는 시점에 Lock을 걸고 조회/갱신이 완료될때까지 이를 유지한다
56 | - `select ... for update`로 비관적 락을 걸 수 있다
57 | - 동시성을 심각하게 떨어뜨릴 우려가 있지만, `for update wait ...`, `for update nowait` 옵션으로 동시성을 증가시킬 수 있다
58 | 2. 낙관적 동시성 제어
59 | - 사용자들이 같은 데이터를 동시에 수정하지 않을 것으로 가정하고, 데이터를 읽을때 Lock을 설정하지 않는다. 읽는 시점에 변경된 데이터인지 반드시 검사해야 한다
60 | - 모든 컬럼에 대해 변경이 됐는지 확인하거나, 수정시간에 대한 컬럼을 따로 만들어서 수정시간이 변경됐는지 확인한다
61 |
62 | - 데이터 품질과 동시성 향상을 위해선
63 |
64 | - for update 사용을 두려워하지 말아야 한다
65 | - for update가 필요한 상황이면 정확히 사용하고, 번거롭더라도 wait 또는 nowait 옵션을 활용하여 예외처리에 주의를 기울어야 한다
66 | - 불필요하게 Lock을 오래 유지하지 않고, 트랜잭션의 원자성을 보장하는 범위 내에서 가급적 빨리 커밋해야 한다
67 |
68 | - 채번 방식
69 |
70 | - 채번 : 신규 데이터를 입력할때 PK 중복을 방지하기 위한 과정
71 |
72 | - 채번 방식 종류
73 |
74 | 1. 채번 테이블
75 |
76 | - 채번하기 위한 별도의 테이블을 관리하는 방식
77 | - 채번 레코드를 읽어서 1을 더한 값으로 변경하고, 그 값을 새로운 레코드를 입력하는데 사용한다
78 | - 두 트랜잭션이 중복 값을 채번할 가능성을 원천적으로 방지해준다
79 | - 장점
80 | - 범용성이 좋다
81 | - 중복 레코드 발생에 대비하지 않아도 된다
82 | - INSERT 과정에 결번을 방지할 수 있다
83 | - PK가 복합컬럼일때도 사용할 수 있다
84 | - 단점
85 | - 채번 레코드를 변경하기 위한 로우 Lock 경합 때문에 성능이 안좋다 (그래서 동시 INSERT가 아주 많으면 사용하기 힘들다)
86 | 2. 시퀀스 오브젝트
87 |
88 | - 시퀀스 오브젝트? 오라클 내부에서 관리하는 채번 테이블
89 |
90 | - 장점
91 | - 성능이 빠르다
92 | - 중복 레코드 발생에 대비하지 않아도 된다
93 |
94 | - 단점
95 | - 테이블 별로 시퀀스 오브젝트를 생성하고 관리하는 부담이 있다
96 | - 시퀀스 채번 과정에서 Lock에 의한 성능 이슈가 있다 (단, 캐시 사이즈를 적절히 설정하면 빠른 성능을 제공한다)
97 | - PK가 단일컬럼일때만 사용할 수 있다
98 | - 결번이 생길 수 있다
99 | 3. MAX + 1 조회
100 | - 예 : `select max(거래일련번호) + 1 from 거래`의 값을 새로 insert할 row의 pk로 사용
101 | - 장점
102 | - 시퀀스나 채번 테이블을 관리하지 않아도 된다
103 | - 동시 트랜잭션에 의한 충돌이 많지 않으면 성능이 매우 좋다
104 | - PK가 복합컬럼이어도 사용할 수 있다
105 | - 단점
106 | - 레코드 중복에 대비한 세밀한 예외처리가 필요하다
107 | - 다중 트랜잭션에 의한 동시 채번이 심하면 시퀀스보다 성능이 많이 나빠질 수 있다
--------------------------------------------------------------------------------
/6장_DML_튜닝/6장-1_김민석.md:
--------------------------------------------------------------------------------
1 | # 6장 소트 튜닝
2 |
3 | ## 6.1 기본 DML 튜닝
4 |
5 | ### DML 성능에 영향을 미치는 요소
6 |
7 | #### 인덱스
8 |
9 | 인덱스가 늘어나면 Insert / Delete / Update 시 부하가 늘어난다. 특히 Update 시에는 변경과 인덱스 수조 내에서 정렬을 다시해야 하는 이중적인 부하가 발생한다.
10 |
11 | #### 무결성 제약
12 |
13 | 무결성 제약 중 PK/FK 제약은 Check / Not Null 보다 성능에 더 큰 영향이 있다. 실제 데이터를 조회하기 때문이다.
14 |
15 | #### 조건절
16 |
17 | 2/3장에서 학습한 인덱스 튜닝 원리 참고.
18 |
19 | #### 서브쿼리
20 |
21 | 4장에서 학습한 조인 튜닝 원리 참고.
22 |
23 | #### Redo 로깅
24 |
25 | DML 수행 시마다 Redo 로그 생성하기 때문에 DML에 성능에 영향을 끼친다.
26 |
27 | #### Undo 로깅
28 |
29 | DML 수행 시마다 Undo 로그 생성하기 때문에 DML에 성능에 영향을 끼친다.
30 |
31 | #### Lock
32 |
33 | Lock은 DML 성능에 직접적인 영향을 끼친다. 성능과 데이터품질은 트레이드오프 관계.
34 |
35 | #### 커밋
36 |
37 | - DB 버퍼캐시
38 | - 서버 프로세스는 버퍼캐시를 통해 데이터를 일괄 처리한다.
39 | - Redo 로그버퍼
40 | - Redo 성능을 위해 Redo 파일 중간에 버퍼 위치.
41 | - 트랜잭션 데이터 저장 과정
42 | - DML 문을 실행하면 Redo 로그버퍼에 변경사항 기록
43 | - 버퍼블록에서 데이터 변경
44 | - 커밋
45 | - Redo 로그버퍼 파일에 일괄 저장
46 | - 버퍼블록을 데이터파일에 일괄 저장
47 | - 커밋
48 | - 커밋을 오래동안 안하는 것도 안좋지만 너무 자주하는 것도 IO를 발생시키기 때문에 좋지 않다
49 |
50 | ### 데이터베이스 Call과 성능
51 |
52 | - SQL Call 세 단계
53 | - Parse call: SQL 파싱과 최적화 수행
54 | - Execute call: SQL 실행
55 | - Fetch call: 데이터를 읽어서 사용자에게 전송
56 |
57 | - 발생 시점에 따른 Call 분류
58 | - User call: DBMS 외부로부터 인입되는 call
59 | - Recursive call: DBMS 내부에서 발생하는 call
60 |
61 | - 절차적 루프 처리
62 | - 절차적으로 반복되는 call일 경우 그나마 recursive 콜일 경우는 낫다. user call은 느려질 수밖에 없다.
63 | - One SQL의 중요성
64 | - 로직이 복잡하지 않으면 되도록 하나의 SQL로 끝나는 게 성능에 좋다.
65 |
66 | ### Array Processing 활용
67 |
68 | 복잡한 업무 로직을 One SQL로 구현하는 것은 어렵다. 이럴 때 Array Processing을 활용한다.
69 |
70 | ### 인덱스 및 제약 해제를 통한 대량 DML 튜닝
71 |
72 | 동시 트랜잭션이 없는 배치 환경에서는 PK 및 인덱스 제약 헤제를 통하여 성능을 향상 시킬 수 있다.
73 |
74 | ### 수정가능 조인 뷰
75 |
76 | 인라인 뷰가 포함된 update 쿼리문은 튜닝을 통하여 속도 향상이 가능하다.
77 |
78 | ### Merge 문 활용
79 |
80 | 데이터웨어하우스에서는 두 시스템 간 데이터 동기화가 가장 흔히 발생하는 작업이다.
81 | 이때 오라클9i에서 도입된 MERGE 문을 활용할 수 있다.
82 |
83 | ## 6.2 Direct Path I/O
84 |
85 | 정보계 시스템(DW/OLAP)이나 배치작업에서는 버퍼캐시 경유하지 않는 것이 더 이득이다. 버퍼캐시를 경유하지 않고 바로 데이터 블록을 읽고 쓰는 것을 Direct Path I/O 라고 한다.
86 |
87 | ### Direct Path I/O
88 |
89 | 아래 경우에 사용한다.
90 |
91 | 1. 병렬 쿼리로 Full Scan울 수행 할 때
92 | 2. 병렬 DML을 수행할 때
93 | 3. Direct Path Insert를 수행할 떄
94 | 4. Temp 세그먼트 블록들을 읽고 쓸 때
95 | 5. direct 옵션을 지정하고 export를 수행할 때
96 | 6. nocache 옵션을 지정한 LOB 컬럼을 읽을 때
97 |
98 | ### Direct Path Insert
99 |
100 | Insert 작업 시 Direct Path Insert 를 사용하면 아래와 같은 이점이 있다
101 |
102 | - Freelist를 참조하지 않는다
103 | - 블록을 버퍼캐시에서 탐색하지 않는다
104 | - 버퍼캐시를 사용하지 않는다.
105 | - Undo 로깅을 안한다
106 | - Redo 로깅을 안하게 할 수 있다.
107 |
108 | ### 병렬 DML
109 |
110 | UPDATE / DELETE 는 기본적으로 Direct Path Write가 불가능하기 때문에 병렬 DML 로 유도한다.
111 |
112 |
113 |
--------------------------------------------------------------------------------
/6장_DML_튜닝/images/partition1.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Meet-Coder-Study/nice-sql-tuning/6ebb1473a41c04d97351f3527a5c2311b4fc4973/6장_DML_튜닝/images/partition1.jpeg
--------------------------------------------------------------------------------
/6장_DML_튜닝/images/partition2.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Meet-Coder-Study/nice-sql-tuning/6ebb1473a41c04d97351f3527a5c2311b4fc4973/6장_DML_튜닝/images/partition2.jpeg
--------------------------------------------------------------------------------
/6장_DML_튜닝/images/partition3.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Meet-Coder-Study/nice-sql-tuning/6ebb1473a41c04d97351f3527a5c2311b4fc4973/6장_DML_튜닝/images/partition3.jpeg
--------------------------------------------------------------------------------
/6장_DML_튜닝/조수진.md:
--------------------------------------------------------------------------------
1 | # Direct Path I/O 활용
2 | 일반적으로 블록 I/O는 버퍼캐시를 경유
3 |
4 | - 온라인 트랜잭션은 기준성 데이터, 특정 고객, 특정 상품, 최근 거래 등을 반복적으로 읽기 때문에 유용
5 | - 정보계 시스템 혹은 배치성 프로그램에서는 주로 대량 데이터를 처리하기 때문에 버퍼캐시를 경우하는 것이 오히려 성능을 떨어뜨림
6 |
7 | ### Direct Path I/O
8 |
9 | - 버퍼 캐시 목적: 자주 쓰는 블록에 대한 I/O call을 줄여서 시스템 전반의 성능을 높이기 위함
10 | - 대량 데이터를 읽고 쓸 때는 캐시 히트율이 낮아서 효율이 떨어짐
11 | - 읽을 때도, 건건이 디스크에서 버퍼캐시에 적재하고 읽어야하므로 효율 낮음
12 |
13 | → Direct Path I/O는 버퍼캐시를 경유하지 않고 곧바로 데이터를 읽고 쓸 수 있는 기능
14 |
15 | ### Direct Path I/O 작동
16 |
17 | 1. 병렬 쿼리로 Full Scan 수행 시
18 | 2. 병렬 DML을 수행 시
19 | 3. Direct Path Insert를 수행 시
20 | 4. Temp 세그먼트 블록들을 읽고 쓸 때
21 | 5. direct 옵션을 지정하고 export를 수행할 때
22 | 6. nocache 옵션을 지정한 LOB 컬럼을 읽을 때
23 |
24 | ### 일반적인 Insert 과정
25 |
26 | - 데이터를 입력할 수 있는 블록을 freelist에서 찾는다
27 | - freelist에 할당받은 블록을 버퍼캐시에서 찾는다
28 | - 버퍼캐시에 없으면 데이터 파일에서 읽어 버퍼캐시에 적재
29 | - Insert 내용을 undo 세그먼트에 기록
30 | - Insert 내용을 redo 세그먼트에 기록
31 |
32 | ### Direct Path Insert
33 |
34 | - freelist 참조하지 않고 HWM 바깥 영역에 데이터를 순차적으로 입력
35 | - 버퍼캐시에서 블록 탐색하지 X
36 | - 버퍼캐시에 적재하지 않고 데이터파일에 직접 기록
37 | - Undo 로깅 최소화할 수 있음
38 | - Redo 로깅을 최소화할 수 있음(데이터 딕셔너리 변경만 로깅)
39 | - table을 nologging 모드로 전환하고 Direct path insert 실행
40 |
41 | `alter table t NOLOGGING;`
42 |
43 |
44 | ### Direct Path Insert 사용 방법
45 |
46 | - Insert ... select 문에 append 힌트 사용
47 | - parallel 힌트를 이용해 병렬 모드로 INSERT
48 | - direct 옵션을 지정하고 SQL Loader(sqlldr)로 데이터 적재
49 | - CTAS(create table ... as select) 문 수행
50 |
51 | ### Direct Path Insert 사용시 주의점
52 |
53 | - Exclusive 모드 TM Lock이 걸린다
54 |
55 | → 커밋전에 다른 트랜잭션이 해당 테이블에 DML을 수행하지 못한다
56 |
57 | → 트랜잭션이 빈번한 주간에 이 옵션을 사용하는 것은 절대 금물
58 |
59 | - Freelist를 조회하지 않고 HWM 영역에 입력하므로 테이블의 여유공간을 재활용하지 않는다
60 |
61 | → Range 파티션 테이블이면 과거 데이터를 DELETE가 아닌 파티션 DROP 방식으로 지워야 공간 반환이 제대로 이루어진다
62 |
63 | → 비파티션 테이블이면 주기적으로 Reorg 작업을 수행해줘야 한다
64 |
65 |
66 | ### 병렬 DML
67 |
68 | 병렬 처리는 대용량 데이터가 전제이므로 오라클은 병렬 DML에 항상 Direct Path Write 방식 사용
69 |
70 | Insert와 달리 Update, delete는 기본적으로 Direct Path Write을 위해 병렬 DML로 처리하는 방법이 유일
71 |
72 | - append 힌트 사용 불가능
73 |
74 | ```sql
75 | alter session enable paralle dml; // 병렬 DML 활성화
76 |
77 | insert /*+ parallel(c 4) */ into 고객 c
78 | select /*+ fulll(o) paralle(o 4) */ * from 외부가입고객 o;
79 |
80 | update /*+ full(c) paralle(c 4) */ 고객 c set 고객상태코드 = 'WD'
81 | where 최종거래일시 < '20100101';
82 |
83 | delete /*+ full(c) paralle(c 4) */ from 고객 c
84 | where 탈퇴일시 < '20100101';
85 | ```
86 |
87 | → 대상 레코드 찾는 작업(select, wherer 절) & 데이터 추가/변경/삭제 모두 병렬로 진행
88 |
89 | - parallel 힌트는 사용했지만 병렬 DML을 활성화하지 않으면 레코드 검색은 병렬로 진행, 추가/변경/삭제는 QC가 혼자 담당해서 병목이 생긴다
90 | - QC(Query Coordinator): 최초로 DB에 접속해서 SQL을 수행한 프로세스
91 | - 병렬 Insert는 append 힌트 지정하지 않아도 Direct path insert로 동작하지만, 병렬 DML이 동작하지 않을 경우를 대비해 append도 같이 써주는 것을 추천
92 |
93 | ```sql
94 | insert /*+ append parallel(c 4) */ into 고객 c
95 | select /*+ fulll(o) paralle(o 4) */ * from 외부가입고객 o;
96 | ```
97 |
98 | - 오라클 12c 부터 `enable_paralle_dml` 힌트 지원
99 |
100 | ```sql
101 | insert /*+ enable_parallel_dml parallel(c 4) */ into 고객 c
102 | select /*+ full(o) parallel(o 4) */ * from 외부가입고객 o;
103 | ```
104 |
105 | ### 병렬 DML이 잘 동작하는지 확인하는 방법
106 |
107 | - update(또는 delete/insert)가 ‘PX COORDINATOR’아래쪽에 나타나면 Update를 각 병렬 프로세스가 처리
108 | - 반대로 update가 ‘PX COORDINATOR’보다 위쪽에 나타나면 update를 QC가 처리
109 |
110 | 
--------------------------------------------------------------------------------
/7장_SQL_옵티마이저/.empty:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Meet-Coder-Study/nice-sql-tuning/6ebb1473a41c04d97351f3527a5c2311b4fc4973/7장_SQL_옵티마이저/.empty
--------------------------------------------------------------------------------
/7장_SQL_옵티마이저/7.1_김민석.md:
--------------------------------------------------------------------------------
1 | # 7.1 통계정보와 비용 계산 원리
2 |
3 | ## 7.1.1 선택도와 카디널리티
4 |
5 |
6 | 선택도 : 전체 레코드 중 조건절에 의해 선택되는 레코드 비율
7 |
8 |
9 | '=' 조건으로 검색 할 때 경우
10 |
11 | `선택도 = 1 / NDV`
12 |
13 | NDV : 컬럼값 종류 개수(성별이라면 남/여 2)
14 |
15 |
16 | 카디널리티 : 전체 레코드 중에서 조건절에 의해 선택되는 레코드 개수
17 |
18 | `카디널리티 = 총 로우 수 * 선택도 = 총 로우 수 / NDV`
19 |
20 | *옵티마이저는 카디널리티 값을 통하여 비용을 산출하고 비용을 통하여 옵티마이징 방식을 결정한다. 따라서 카디널리티값의 기초가 되는 선택도 값의 계산이 매우 중요하다.
21 |
22 |
23 | ## 7.1.2 통계정보
24 |
25 | ### 오브젝트 통계
26 |
27 | #### 테이블 통계
28 |
29 | 테이블 통계 조회
30 | ```sql
31 | select
32 | num_rows -- 총 레코드 계수
33 | ,blocks -- 테이블 블록 수 = '사용된' 익스텐트에 속한 총 블록 수
34 | ,avg_row_len -- 레코드당 평균 길이
35 | ,sample_size -- 샘플링한 레코드 수
36 | ,last_analyzed -- 통계정보 수집일시
37 | from all_tables
38 | where owner='SCOTT'
39 | and table_name = 'EMP'
40 | ;
41 | ```
42 |
43 | #### 인덱스 통계
44 |
45 |
46 | 인덱스 통계 조회
47 | ```sql
48 | select blevel -- 브랜치 레벨. 인덱스 루트에서 블록에 도달하기 직전까지 읽게되는 블록 수
49 | ,leaf_blocks -- 인덱스 리프 블록 총 개수
50 | ,num_rows -- 인덱스에 저장된 레코드 개수
51 | ,distnct_keys -- 인덱스 키값의 조합으로 만들어지는 값의 종류 개수. c1(3) * c2(4) = 12. 선택도 계산에 사용.
52 | ,avg_leaf_blocks_per_key -- 인덱스 키값을 모두 '=' 조건으로 조회할 때 읽게 될 리프 블록 개수
53 | ,avg_data_blocks_per_key -- 인덱스 키값을 모두 '=' 조건으로 조회할 때 읽게 될 테이블 블록 개수
54 | ,clustering_factor -- 인덱스 키값 기준으로 테이블 데이터가 모여 있는 정도. 인덱스를 스캔하면서 테이블 레코드를 찾을 때 읽게 될 테이블 블록 개수를 미리 계산 한 수치
55 | ,sample_size
56 | ,last_analyzed
57 | from all_indexes
58 | where owner = 'SCOTT'
59 | and table_name = 'EMP'
60 | and index_name = 'EMP_X01'
61 | ;
62 |
63 |
64 | ```
65 |
66 | #### 컬럼 통계
67 |
68 | 컬럼 통계 조회
69 |
70 | ```sql
71 | select num_distinct -- 컬럼 값의 종류 개수(NDV)
72 | , density -- '=' 조건일 때 선택도. 1/NDV
73 | , avg_col_len -- 컬럼 평균 길이(bytes)
74 | , low_value -- 최소값
75 | , high_value -- 최대값
76 | , num_nulls -- null 인 레코드 수
77 | , last_analyzed
78 | , sample_size
79 | from all_tab_columns
80 | where owner = 'SCOTT'
81 | and table_name = 'EMP'
82 | and column_name = 'DEPTNO'
83 | ;
84 | ```
85 |
86 | 컬럼 히스토그램
87 |
88 | '=' 조건 선택도는 1/NDV 나 Density 값을 활용.
89 |
90 | 하지만 데이터 분포가 균열하지 않다면 적용하기 쉽지 않기 때문에 이런 경우 옵티마이저는 통계 외에 히스토그램 사용.
91 |
92 | 히스토그램 : 컬럼 값 별로 데이터 비중이나 빈도를 미리 계산해 놓은 통계정보. 실제 테이터를 읽어서 계산해 둔 값.
93 |
94 | - 히스토그램 유형
95 | - 도수분포 : 값별로 빈도수 저장
96 | - 높이균형 : 각 버킷의 높이가 동일하도록 데이터 분포 관리
97 | - 상위도수분포 : 많은 레코드를 가진 상위 n개 값에 대한 빈도수 저장
98 | - 하이브리드 : 도수분포와 높이균형 특성 결합
99 |
100 |
101 | 히스토그램 조회
102 |
103 | ```sql
104 | select endpoint_value, endpoint_number
105 | from all_histograms
106 | where owner = 'SCOTT'
107 | and table_name = 'EMP'
108 | and column_name = 'DEPTNO'
109 | order by endpoint_value
110 | ;
111 | ```
112 |
113 | ### 시스템 통계
114 |
115 | 시스템 통계 : 애플리케이션 및 하드웨어 성능 특성의 측정값
116 |
117 | - CPU 속도
118 | - 평균 Single Block I/O 속도
119 | - 평균 Multiblock I/O 속도
120 | - 평균 Multiblock I/O 개수
121 | - I/O 서브시스템의 최대 처리량
122 | - 병렬 Slave의 평균적인 처리량
123 |
124 | ## 7.1.3 비용 계산 원리
125 |
126 | - 단일 테이블을 인덱스로 액세스할 때의 비용 계산 원리
127 |
128 | '=' 조건 검색 시
129 |
130 | ```
131 | 비용 = BLEVEL -- 인덱스 수직적 탐색 비용
132 | + AVG_LEAF_BLOCKS_PER_KEY -- 인덱스 수평적 탐색 비용
133 | + AVG_DATA_BLOCKS_PER_KEY -- 테이블 랜덤 액세스 비용
134 | ```
135 |
136 | - blevel : 브랜치 레벨. 인덱스 루트에서 블록에 도달하기 직전까지 읽게되는 블록 수
137 | - avg_leaf_blocks_per_key : 인덱스 키값을 모두 '=' 조건으로 조회할 때 읽게 될 리프 블록 개수
138 | - avg_data_blocks_per_key : 인덱스 키값을 모두 '=' 조건으로 조회할 때 읽게 될 테이블 블록 개수
139 |
140 |
141 | '=' 조건 검색 아닐 시(아래 컬럼 통계까지 활용)
142 |
143 | ```
144 | 비용 = BLEVEL -- 인덱스 수직적 탐색 비용
145 | + LEAF_BLOCKS * 유효 인덱스 선택도 -- 인덱스 수평적 탐색 비용
146 | + CLUSERING_FACTOR * 유효 테이블 선택도 -- 테이블 랜덤 액세스 비용
147 | ```
148 |
149 | - leaf_blocks : 인덱스 리프 블록 총 개수
150 | - clustering_factor : 인덱스 키값 기준으로 테이블 데이터가 모여 있는 정도. 인덱스를 스캔하면서 테이블 레코드를 찾을 때 읽게 될 테이블 블록 개수를 미리 계산 한 수치
151 | - 유효 인덱스 선택도 : 전체 인덱스 레코드 중 액세스 조건에 의해 선택될 것으로 예상되는 레코드 비중
152 | - 유효 테이블 선택도 : 전체 인덱스 레코드 중 인덱스 컬럼에 대한 모든 조건절에 의해 선택 될 것으로 예상되는 레코드 비중
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 친절한 SQL 튜닝 스터디
2 |
3 | https://www.notion.so/SQL-2b8c5a039c204901ad8afd109b34ffcd
4 |
--------------------------------------------------------------------------------
/img/pic3-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Meet-Coder-Study/nice-sql-tuning/6ebb1473a41c04d97351f3527a5c2311b4fc4973/img/pic3-3.png
--------------------------------------------------------------------------------
/img/pic3-47.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Meet-Coder-Study/nice-sql-tuning/6ebb1473a41c04d97351f3527a5c2311b4fc4973/img/pic3-47.png
--------------------------------------------------------------------------------
/img/pic_1-12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Meet-Coder-Study/nice-sql-tuning/6ebb1473a41c04d97351f3527a5c2311b4fc4973/img/pic_1-12.png
--------------------------------------------------------------------------------
/img/pic_1-27.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Meet-Coder-Study/nice-sql-tuning/6ebb1473a41c04d97351f3527a5c2311b4fc4973/img/pic_1-27.png
--------------------------------------------------------------------------------
/img/pic_1-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Meet-Coder-Study/nice-sql-tuning/6ebb1473a41c04d97351f3527a5c2311b4fc4973/img/pic_1-4.png
--------------------------------------------------------------------------------
/img/table3-8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Meet-Coder-Study/nice-sql-tuning/6ebb1473a41c04d97351f3527a5c2311b4fc4973/img/table3-8.png
--------------------------------------------------------------------------------