Effective SQL(SQL 코딩의 기술 발췌: 개인적인 comment 추가)

 

Better way 11 인덱스와 데이터 스캔을 최소화하도록 인덱스는 신중히 만들자

흔히 성능 문제는 인덱스가 부족하거나 올바르지 않은 인덱스를 만들어서 발생하기에 데이터베이스 엔진은 쿼리 조건을 만족하는 레코드를 찾는 데 필요 이상으로 많은 데이터를 처리할 가능성이 크다. 이런 프로세스를 인덱스 스캔 or 테이블 스캔으로 나눌 수 있다

데이터베이스 엔진이 적합한 레코드를 찾으려고 인덱스와 데이터 페이지를 스캔하며 데이터가 많을수록 인덱스 스캔을 완료하는 데 걸리는 시간은 길다.

à 데이터베이스 엔진 즉 옵티마이저는 데이터를 스캔하는 녀석이며 처리 건수에 성능 영향을 받는다.

인덱스는 데이터의 추출 문제를 해결해 주는 만능 해결사라고 여기는 것은 위험한 생각이다. 많은 인덱스가 데이터를 빠르게 가져오지도 않고 데이터 갱신 속도만 느리게 한다. 인덱스를 갱신하는 작업은 종종 테이블을 갱신하는 작업보다 비용이 많이 든다.

à 처음에 인덱스 만들 때 막 만들지 말아라

데이블 스캔보다 비클러스터 인덱스 스캔이 더 나은 성능을 발휘하는지 여부는 테이블 크기, 로우의 저장패턴, 로우의 길이, 쿼리가 반환하는 로우의 비율에 따라 다르다 흔히 전체 로우 중 최소 10%이상의 로우가 반환 될 때 테이블 스캔이 비클러스터 인덱스보다 나은 성능을 보이기 시작한다. 클러스터 인덱스는 보통 반환되는 로우의 비율이 높을수록 테이블 스캔보다 나은 성능을 발휘 한다.

à 검증이 필요하다 특히 반환되는 로우의 비율이라는 말은 잘 공감이 안 된다. 인덱스는 기본적으로 범위검색에 강하다 그렇다면 반환되는 비율이 높다는 것은 걸러지는 행이 별로 없다는 말로 이해된다… (경험으로 극복필요)

컬러의 카디널리티가 낮으면(인덱스 값의 다수가 같은 값일 때)인덱스의 효과가 미미하다. 인덱스를 사용했지만 테이블에서 최소 비율 이하의 데이터만 읽게 된다면 데이터베이스 엔진은 인덱스를 사용하지 않을 것이다. 게다가 인덱스는 테이블이 클 때만 사용하는 것이 좋다. 데이터베이스 엔진은 대부분 테이블이 작으면 그 데이터를 메모리에 올려놓는다. 데이터가 일단 메모리에 올라오면 무슨 작업을 하든 빠르게 데이터를 탐색한다. 여기서 작다는 기준은 로우의 개수, 개별 로우의 크기, 페이지에 로드되는 방식과 데이터베이스 서버의 가용 메모리 용량에 의존함을 의미한다.

à 인덱스로 사용하는 컬럼은 중복이 없으면 좋다. 그리고 메모리에 올리기 위해서는 가능한 작은 집합적으로 연산이 되야 한다.

 

Better way 12 인덱스를 단순 필터링 이상의 목적으로 사용하자

SQ문에서 검색 조건은 Where 절에 기술하는데 여기서 데이터를 빠르게 찾는다는 인덱스의 핵심 목적이 진가를 발휘하다. 느린 쿼리의 첫 번째 범인은 제대로 작성되지 않은 Where 절이다.

à 조건 검색에 사용할 데이터가 뭔지 미리 알자

컬럼을 인덱스로 만들었는지 여부는 테이블 간 조인이 얼마나 효율적으로 수행되는지에 영향을 미친다. 요컨대 조인을 이용하면 정규화된 모델의 데이터를 특정 처리에 적합한 역정규화된 형태로 변환할 수 있다.(비즈니스 모델 조인은 여러 테이블에 분산된 데이터를 결합하므로 여러 페이지에 있는 데이터를 더 많이 읽느라 디스크 탐색 대기 시간에 민감하다. 따라서 적절하게 인덱스를 만드는 것은 조인이 응답하는 시간에 큰 영향을 준다.

쿼리를 수행할 때 사용하는 조인은 한번에 두 테이블만 접근한다. SQL이 더 많은 테이블을 조인하는 경우 더욱더 많은 단계가 필요하다 먼저 두 테이블을 조인해 중간 결과 집합을 만든 후 이 결과 집합과 다음 테이블을 조인하는 식으로 처리한다.

à 조인의 경우 인덱스에 영향을 받고 조인으로 비즈니스 모델을 만들 수 있고 여러 테이블을 조인하는 경우 한번에 두 테이블만 처리가능 하므로 조인 순서에 영향을 받는다.

중접 루프 조인은 가장 기본적인 조인 알고리즘이다. 선행 쿼리가 한 테이블에서 결과 집합을 가져오고 두번째 쿼리는 선행 쿼리 결과집합의 각 로우에 대응하는 데이터를 다른 테이블에서 가져온다. 따라서 중첩 루프조인은 조인 조건에 참여하는 컬럼을 인덱스로 만들었을 때 가장 효과가 좋다. 선행 쿼리가 작은 결과 집합을 반환할 때 중첩 루프 조인은 좋은 성능을 보인다. 그렇지 않으면 옵티마이저는 다른 조인 알고리즘을 선택한다.

à NL 조인은 조인조건에 참여(ON, Select까지) 하는 컬럼을 인덱스로 만들고 선행테이블은 작은 결과 집합이 나오는 것으로 하자 결과집합이기 때문에 단순히 작은 테이블이 아니라 조건에 의해 걸리진 작은 결과 집합이다.

해시 조인은 참여하는 한쪽 테이블데이터를 해시 테이블로 만든 후 다른 쪽 테이블의 각 로우를 매우 빠르게 탐색할 수 있다. 해시 조인은 해시 테이블을 사용하므로 조인되는 컬럼을 인덱스로 만들 필요가 없다 해시 조인의 성능을 향상할 수 있는 인덱스는 WhereON 절에 사용되는 컬럼에 대한 인덱스다 현실적으로 해시 조인의 성능은 수평적(좀 더 적은 로우) 수직적(좀 더 적은 컬럼)으로 해시 테이블을 줄이면 성능이 좋다.

à 해시조인의 인덱스는 Select 절 컬럼보다는 On이나 Where절에 우선해서 만들고 선행 테이블의 결과 집합이 적으면 메모리에서 처리가 가능해 성능이 좋고 대량의 데이터를 처리할 때 알고리즘 복잡도가 O(1)이기 때문에 선호된다.

소트머지 조인은 조인 조건에 따라 두 테이블을 각각 정렬한 후 지퍼처럼 정렬된 두 항목을 결합하는 식으로 수행된다. 소트머지 조인은 테이블의 조인 순서가 별 의미가 없고 성능에 미치는 영향이 작다 동시에 두 테이블에 접근한다고 보면 된다. 일단 데이터가 정렬되면 소트머지 조인은 좋은 성능을 발휘하지만 양쪽 데이터를 정렬하는 비용이 크기 때문에 잘 사용하지 않는다. 반면에 해시조인은 한쪽만 미리 처리하면 되므로 양이 많으면 해시조인이 훨씬 낫다.

à 정렬된 상태와 범위 검색을 할 수 있는 컬럼의 인덱스가 필요하다 다른 조인과 가장 큰 차이는 조인 순서가 크게 영향이 없다는 부분이다. 해시조인이 부담이 될 때 고려해 볼 수 있겠다.

 

Better way 46 실행 계획의 작동 원리를 이해하자

SQL 데이터베이스를 사용하는 사람이라면 누구나 SQL 쿼리를 최적화하거나 특히 인덱스나 모델 설계에서 필요한 스키마 변경을 수행 할 수 있도록 실행 계획을 읽는 방법과 실행 계획의 의미를 이해해야 한다.

데이터를 효율적인 방식으로 가져오려고 물리적인 단계를 기술하는 단조로운 작업에서 개발자를 해방하는 것이 SQL의 목표라는 점을 일깨우고 싶다. 즉 얻으려는 데이터를 선언적으로 기술하고 데이터를 가져오는 최상의 방법은 옵티마이저에게 일임하는 것이다. 실행 계획과 물리적인 구현 내용을 설명할 때 SQL이 제공하는 추상화는 배제할 것이다.  

à 어렵게 말한 것 같다 내가 이해하기로는 모델링과 같은 추상화 적인 부분과 DBMS가 해석하는 영역은 구분되어 있다는 말인 것 같다

컴퓨터이기 때문에 작업을 수행하는 방식이 완전히 다를 것이라고 가정하지만 그렇지 않다 동일한 작업을 처리할 때 거치는 물리적인 단계는 사람이 수행하는 것과 다르지 않다 결론적으로 실행 계획을 읽을 때 쿼리에 대해 데이터베이스 엔진이 수행하는 물리적인 단계를 파악하는 것이 좋다

à 물리적인 처리 단계를 이해하면 실행계획과 쿼리를 보고 내부적으로 어떻게 처리하는 지 알 수 있다는 말   즉 디버깅 같은 과정을 하지 않아도 쿼리가 어떻게 실행되는지 해석하는데 도움이 되는 것

인덱스 시스템은 여러분이 작성할 쿼리의 종류에 매우 크게 의존한다는 점이다. (관련 예제 설명은 인덱스를 생성할 때 컬럼의 순서나 INCLUDE 절로 Lookup을 제거할 수 있다는 것을 설명한다)

à 좋은 인덱스 생성은 좋은 모델 혹은 좋은 설계부터 시작된다. (책의 첫 인덱스는 클러스터 인덱스로 볼 수 있고 뒷장의 인덱스는 비클러스터드 인덱스로 볼 수 있고 룩업이라는 것은 비클러스터드 인덱스에서 페이지 번호를 보고 실제 책에서 내용을 찾는 것을 말한다 이런 DBMS의 물리적인 동작을 머릿속에서 그릴 수 있다

데이블을 스캔하는 실행 계획을 보는데 인덱스가 있음에도 실행 계획에 사용되지 않는다면 어떤 이유인지 찾고 분석해서 개선하거나 수정할 수 있어야 한다. (인덱스는 만능도 아니고 비용이다)

à 실행 계획을 보고 인덱스 사용여부를 파악하라는 말

데이터베이스 엔진은 쿼리를 수행하는 더 나은 방법을 찾고 이에 맞게 실행 계획을 다시 세울 정도로 똑똑하게 처리했다. 이 특성은 데이터베이스 엔진 자체가 사용자가 질의한 쿼리로 제한된다는 점을 강조하는 것이다. 잘 작성되지 않은 쿼리를 보낸다면 데이터베이스 엔진도 어쩔 수 없이 나쁜 실행 계획을 생성할 것이다.

à 쿼리작성과 모델링을 잘하자

물리적인 동작을 처리하는 순서가 꽤 많다. 하지만 어떤 순서로 처리해야 좀 더 효율적인지는 데이터 분포에 의존한다. 따라서 매개변수화 된 쿼리(저장 프로시저)는 특정 값에서 월등한 성능으로 수행되지만 다른 값에서는 현격히 느리게 수행될 수 있다. 데이터베이스 엔진은 매개변수화 된 쿼리에 대응하는 실행 계획을 저장해 놓는다.

à Ad-Hoc 쿼리와 차이점 저장 프로시저는 실행 계획을 캐싱한다 라는 의미로 보면 될 것 같다 즉 저장 프로시저는 캐싱된 실행계획을 사용하지만 그 데이터 분포가 변하는 것에 캐싱된 정보로 인해 잘 못 판단할 수 있다는 말이다. 즉 노후화된 시스템은 인덱스 통계정보를 갱신하거나 인덱스를 수정해서 DBMS에서 다시 실행계획을 캐싱하도록 검토하고 확인할 필요가 있다. (유지보수하면서 나올 문제를 말하는 것 같은데.. 다만 SP를 만들면서 이와 같은 경우를 예측할 수 있을까?? 일단 경험을 쌓자)

실행계획의 단계를 보고 효율적인지 판단한다. 효율성은 데이터 분포의 영향을 받는다 결론적으로 나쁜 연산은 없다. 다만 쿼리에 적합한 연산이 무엇인지 분석한다.

à 실행계획을 볼 줄 알자

좋은 실행 계획을 얻으려면 한 쿼리에 국한되지 않는 인덱스를 추가한다. 데이터베이스의 전반적인 가용성을 고려해 추가한 인덱스가 가능한 많은 쿼리에 적용되는지 확인하고 저장프로시저의 경우 시간이 흘러도 최적화를 잘하는지 확인하자 (DBMS에서 재컴파일 옵션이 어떻게 동작하는지도 확인해 보자)

à 인덱스 통계(분포와 관련)를 주기적으로 갱신해 주고 필요하면 기존 캐싱을 지우고 실행계획을 다시 생성하도록 하자

인덱스와 통계적 상관관계

 

개요

인덱스와 카디널리티와 선택도에 대한 관계를 알아보고자 테스트를 하고자 한다.

 

요인(factor)

- 선택도 (X1)

- 카디널리티 (X2)

- 검색 범위 (X3)

- 인덱스 개수 (X4)

 

테스트 가설

n  Y(수행시간) = aX1 + bX2 + cX3 + dX4

단 계수에 대한 가중치나 차등을 둘 것이 아니면 1로 생각한다.

예를 들면 아래 규칙에 따라 가중치를 계산한다면 

1.     선택도에 따라

A.     1 / 0.5 / 0.3 / 0.1 / 0.01

2.     카디널리티에 따라

A.     10000 / 100000 / 1000000

3.     검색 범위에 따라

A.     90% / 70% / 50% / 30% / 10% / 1%

4.     인덱스 개수

A.     1, 2, 3, 4

경우에 따라 결과에 영향을 미치는 차이가 있다는 설계를 하지 않으면 계수에 대한 영향은 각각 같다

모든 경우를 테스트 하는 것은 아니고 카디널리티에 대해서 구분하고자 한다.

 

테스트 Case

1.     카디널리티를 낮은 -> 높은 순서로 인덱스를 구성

2.     카디널리티를 높은 -> 낮은 순서로 인덱스를 구성

테이블 형태

전체 Row는 약 1700만건으로 생성하고 각각의 카디널리티를 구해 본다

인덱스를 2가지 형태로 생성

첫 번째 인덱스는 is_bonus, from_date, group_no순으로 카디널리티가 낮은 순에서 높은 순 (중복도가 높은 순에서 낮은순으로) 으로, 두번째 인덱스는 group_no, from_date, is_bonus순으로 카디널리티가 높은 순에서 낮은 순 (중복도가 낮은 순에서 높은순으로) 으로 생성

select SQL_NO_CACHE *               -- 쿼리 Cache 사용하지 않음

from salaries

use index (IDX_SALARIES_INCREASE)  -- 옵티마이저에서 인덱스 사용을 변경하지 않도록 강제로 지정

where from_date = '1998-03-30'

and group_no in ('abcdefghijklmn10494','abcdefghijklmn3968', 'abcdefghijklmn11322', 'abcdefghijklmn13902', 'abcdefghijklmn100', 'abcdefghijklmn10406')

and is_bonus = true;

 

select SQL_NO_CACHE *

from salaries

use index (IDX_SALARIES_DECREASE)

where from_date = '1998-03-30'

and group_no in ('abcdefghijklmn10494','abcdefghijklmn3968', 'abcdefghijklmn11322', 'abcdefghijklmn13902', 'abcdefghijklmn100', 'abcdefghijklmn10406')

and is_bonus = true;

 

테스트 방법

2가지 쿼리에 대해 각각 10회씩 진행

 

테스트 결과

IDX_SALARIES_INCREASE

IDX_SALARIES_DECREASE

1

110ms

46.9ms

2

89.5ms

24.6ms

3

95.4ms

38.1ms

4

85.6ms

29.3ms

5

83.6ms

29.3ms

6

85.2ms

38.2ms

7

59.4ms

26.1ms

8

64.2ms

29.4ms

9

93.7ms

25.7ms

10

102ms

35.4ms

평균

86.86ms

32.3ms

 

결과 요약

성능 수치로 봤을 때 카디널리티가 높은 순에서 낮은 순으로 구성하는 게 성능이 더 뛰어나다 

'Computer Science > 데이터베이스' 카테고리의 다른 글

Effective SQL과의 대화  (0) 2018.07.24
안녕 옵티마이저  (0) 2018.07.24
친절한 SQL 튜닝 1. SQL 처리과정과 I/O  (0) 2018.07.04
옵티마이저  (0) 2018.04.13
트랜잭션 매니저와 카운트 그리고 Biz  (0) 2018.03.25

옵티마이저와 물리 조인 수행 원리

서론

- DBMS의 엔진이 동작하는 원리

- 용어 정리

l  옵티마이저

-      사용자가 질의한 SQL문에 대해 최적의 실행 방법을 결정하는 DBMS의 엔진

-      사용자가 질의한 SQL문에 대해 최적의 실행 방법을 결정하는 역할을 수행

-      옵티마이저가 선택한 실행 방법의 적절성 여부에 따라 수행속도에 영향을 미침

옵티마이저를 이해하는 것이 튜닝의 시작이고 옵티마이저를 제어하는 것이 튜닝의 기본

 

l  옵티마이저에 의해 발생하는 조인 3가지

NL조인 , Sort Merge 조인, Hash 조인이 있다이것은 물리적인 조인이기 때문에 개발자가 직접 사용하게 되는 조인은 아니다

개발자가 직접 제어하는 조인은 내부조인이나 외부조인과 같은 논리적인 조인을 선언해서 만든다.

Ludy의 경우 Merge Into 구문도 조인이 있다 

(조인 성능에 대해 검색을 해보면 UPSERT로 동작하기 때문에 구문하나에 UPDATE, INSERT를 수행하는 것에 대해 UPDATE INSERT를 따로 하는 것에 비해 성능적 이점을 말하고 있다 단 당연한 것이지만 데이터가 많아 질 때 약 200만건 이상인 데이터를 Merge 구문으로 처리할 때는 느려질 수 있음을 주의시키고 있다즉 기존의 UPDATE시행 이후에 @@ROWCOUNT를 확인한 후 변경된 내용이 없으면 INSERT를 해야 하는 세밀한 트랜잭션처리를 필요로 하지 않기 때문에 단일 구문의 이점은 있다 개인적인 견해지만 대량의 데이터는 적정수준을 정해 나누어서 UPSERT하는 경우가 더 나을 수 있어 보인다.)

 

l  옵티마이저 역할

논리적인 조인을 옵티마이저라는 녀석이 DBMS 내부에서 물리적인 조인으로 표현하고 만드는 것

보통 CBO라는 비용기반 옵티마이저가 사용되며 최적의 실행 방법 결정이라는 것은 어떤 방법으로 처리한 것이 최소 일 량(비용)으로 동일한 일을 처리할 수 있을 지 결정하는 것이다.

개발자는 옵티마이저가 최적의 실행 방법을 결정하는 것을 도와줄 수 있다. (인공지능은 아님)

단순히 조인종류를 아는 것보다 이 옵티마이저가 어떻게 물리적인 조인으로 변경시키는 지 알자

           

l  실행 계획옵티마이저에 의한 최적의 실행 방법

구성하는 요소 

조인순서참조하는 테이블의 순서

조인기법: NL, Hash, Sort Merge

액세스 기법테이블 스캔인덱스 스캔

최적화 정보비용(cost) 주어진 조건을 만족한 결과 집합 수

조인 조건을 만족한 건(Cardinality) 

Bytes(결과집합의 메모리 양)

연산조인기법액세스기법 등 여러 가지 조작을 통해서 원하는 결과를 얻어내는 작업

Ex)

 

l  옵티마이저가 연산으로서 Full Table Scan을 선택하는 경우

1.     SQL 문에 조건이 존재하지 않는 경우

2.     SQL 문의 주어진 조건에 사용 가능한 인덱스가 존재하지 않는 경우

3.      조건을 만족하는 데이터가 많은 경우 옵티마이저 자체적으로 판단할 때 Index scan가 아닌 Full Scan 고려

4.     많은 데이터를 엔진 내부에서 병렬처리 방식으로 처리하는 경우나 SELECT * 같은 전체를 가져올 때

 

l  옵티마이저가 이용하는 통계정보

통계정보를 사용하는 이유

일단 옵티마이저는 만능이 아니다옵티마이저도 사람이 만든 SW이다.

실제로 SQL문을 처리해 보지 않은 상태에서 결정해야 하는 어려움이 있다

-      DBMS 별로 옵티마이저의 성능을 비교해 볼 수 있는 부분이라고 볼 수 있겠다

기본적으로 통계정보를 이용한다. (http://www.dbguide.net/db.db?cmd=view&boardUid=148218&boardConfigUid=9&categoryUid=216&boardIdx=139&boardStep=1)

1. 선택도  예상되는 ROW 비율

2. 카디널리티  예상 되는 ROW 건 수

3. 히스토그램(데이터의 분포 평균 분포에도 영향을 받음

 

l  옵티마이저 힌트

SQL 서버에서의 힌트는 3가지 이다.

1. 테이블 힌트  테이블 명 다음에 WITH를 통해 지정한다(fastfirstrow, holdlock, nolock)

2. 조인 힌트  FROM 절에 지정하며 두 테이블간 조인 전략에 영향을 미친다. (Loop, hash, merge, remote)

3. 쿼리 힌트  쿼리당 맨 마지막에 한번만 지정할 수 있는 쿼리 힌트는 OPTION 절을 이용한다

 

l  카디널리티

- Cardinality: 사전적 의미로는 집합원의 개수 그렇다면 원소의 개수

- 카디널리티가 낮은 경우에서 속성의 예를 들면 성별부서지역이 있다.

- 성별의 경우 남자여자 두 가지 경우만 가능하므로 매우 낮다고 할 수 있다

- 주민번호사원번호와 같은 경우 조직원이 많을수록 카디널리티가 높다

- 간단히 생각하면 중복을 제외하고 고유한 속성을 뽑았을 때 발생할 수 있는 경우의 수이다.

- 데이터베이스로 한정해보면 테이블에서 Primary Key 고유한 값인 카디널리티로서 높은 경우의 수를 가지고 있다.

 

l  카디널리티 설명 예제

학급이라는 엔터티를 만들어 학년이라는 성적표를 만든다면 반 번호가 기본 키가 되고 그 학급의 학생의 수가

카디널리티가 될 수 있다 (학급(1) : 학생(N)) 학생 한 명 한 명은 고유한 값이다.

또는 특정 쿼리문을 실행시켜서 나오는 결과 값(Row)를 카디널리티라고 한다.

즉 학교라는 개념에서는 학급이 카디널리티일 수 도 있고 학급이라는 개념에서는 학생이 카디널리티일 수 있다.

다른 예로 사원테이블의 전체 레코드 수가 1000개 일 때 WHERE 부서 = ‘인사팀’ 이면 그 중 인사 팀 사원이 10명이되면 10/1000 = 0.01 이 선택도가 되고 출력되는 Rows의 개수 1000 * 0.01 = 10 

카디널리티는 10이다.

 

l  카디널리티를 이해해야 하는 이유

DB의 옵티마이저에서 특정 값을 찾는 항목으로 인덱스를 사용하게 된다없으면 인덱스를 사용하지 않고 Full Scan 이 되어서라도 찾아준다중요한 건 현재 접근하는 컬럼을 조건으로 접근할 때 얼마나 많이 걸러내지는 지 이해가 되는가이다.

선택도가 확률이라고 하면 해당 카디널리티 즉 경우의 수(확률과 전체를 곱했을 때 나타나는 기대값)는 전체 데이터의 개수에 영향을 받게 된다데이터가 많을 때는 해당 조건으로 얼마나 많이 걸러지는 지 즉 중복이 최소한일수록 해당 조건으로 많이 걸러지게 된다당연히 중복이 있는 컬럼을 인덱스로 사용 안 한다고 생각하겠지만 Non Clustered 인덱스는 꼭 중복이 없는 컬럼만 사용된다고 보장할 수 없다. WHERE 절의 조건에 의해 검색되는 경우도 마찬가지이다.

중복된 data를 검색하면 중복이기 때문에 동일한 값의 범위에 해당하게 되고 인덱스 컬럼이 여러 개 이어서 순서대로 후행에 대한 조건도 같이 검색해야 될 경우 한행 한행 하나씩 Scan하면서 찾아야 할 수 있다검색으로 찾아야 하는 data의 중복이 많거나 또는 DB 인덱스의 Leaf 블록에 동일한 것이 많을수록 블록 전체를 Scan 해야 하기 때문에 모든 조건을 만족하는 원하는 데이터를 찾는 경우 범위를 좁혀가는 검색에서는 중복을 고려하는 시간이 더 걸릴 수 있다동일한 항목을 제낀다?? 제한다?? 라는 표현이 맞을 지 모르겠지만 인덱스 블록에 Access를 하고 순차적으로 선형 검색을 하면 순서대로 전체를 스캔 하게 되고 인덱스 블록에 중복이 없어 정렬된 상태에서 인덱스 스캔을 하면 범위 단위로 검색 대상을 좁힐 수 있는 경우 성능이 더 좋다고 할 수 있다.

랜덤 IO와 순차 IO의 비교http://12bme.tistory.com/138

알고리즘 적으로는 선형 O(N) > 이진트리 O(logN) 이기 때문에 성능이 더 좋다고 한다

이렇게 집합에서 원소가 어떻게 분포되었는지에 따라 스캔속도가 차이가 나므로 옵티마이저는 인덱스를 어떤 녀석으로 하느냐에 따라 성능에 영향을 받는 다는 것이다.

개발자는 물리적인 조인에 대해 직접 관여는 하지 않지만 인덱스 설정에 따라 영향은 줄 수 있다는 말이기도 하다일반적으로 옵티마이저는 여러 인덱스가 있을 때 선택도가 높은 즉중복발생확률이 낮은 인덱스를 사용한다고 한다.

더 멀리 나가면 DB 설계를 할 때 어떤 속성들을 컬럼으로 쓰냐에 따라 장기적인 인덱스 관리나 검색성능에 영향을 미치게 되므로 집합의 분포를 알고 어떤 속성을 모델에 넣을지 정하는 것도 모델링의 중요한 포인트라는 것이기도 하다.

 

l  선택도와 카디널리티의 이해

확률이 예를 들기 좋은 것 같다.

카디널리티라는 주머니를 만들고 거기에 빨주노초파남보 의 무지개 색 공을 집어넣으면 이 경우 선택도는 7/7 = 1 이다모두 유니크 한 색을 가지고 있기 때문이다.

그러면 빨간공 3개 노란공 2개 보라색공 2개를 넣으면 이 경우 선택도는 몇 일 까?? 빨간 공의 확률은 3/7 노란공은 2/7 보라색공은 2/7 이다이건 단순히 시행(반복)이라는 시도와 맞물려서 말할 수 있는 확률 인 것이고 공이라는 개념으로 접근하면 3개의 색만을 가지므로 그 공이라는 엔터티 테이블에서 색깔이라는 속성은 선택도가 3/7   0.44 이다앞의 무지개색인 경우에 비해 선택도가 작아졌다

그리고 그 빨주노초파남보 이면서 각각 1~7까지의 숫자가 매겨져 있는 상황이라면

 |  |  |  |  |  | 

1   | 2  |  3  |  4 |  5  | 6   | 7

이런 상황에서 숫자와 색깔이라는 속성의 선택도는 어떻게 될까??

확률을 얘기 했으니 숫자로 하면 7/7 색으로 하면 7/7 이다이것은 시행에 의해 획득할 수 있는 한번의 공과 같지만 유일성이 확보가 되니 선택도는 1이고 특정 색이나 숫자가 검색되는 카디널리티는 1 이다.

근데 예제를 바꾸면

 |  |  |  |  |  | 

1  |  2  |  2 |  2  |  3 |  4  |  4

이런 상황에서는 선택도가 변하나색으로는 7/7이고 숫자로는 4/7 이다.

여기서 어떤 속성을 기준으로 하느냐에 따라 확률이 달라졌고 그에 따라 선택도(카디널리티)도 달라졌다.

여기서 속성을 색과 숫자로 나눈 이유는 인덱스를 어디다 거는 기준이 뭐냐?? 라는 말을 하고 싶어서이다.

최소한의 기준은 앞에서 선택도가 높고 를 말했다.

한번 비교해 보자

색깔선택도 - 7/7  빨간공 하나 카디널리티 7 * 1 = 7이다.

숫자선택도  4/7  4번 공 하나 카디널리티 7 * (4/7) = 4이다.

숫자 일 때 선택도가 크고 카디널리티가 크다 이런 경우 어떤 속성을 인덱스로 하는 것이 더 나을까??

 

이걸 옵티마이저에서 증명하고자 한다그래서 아래 내용은 가설적인 내용이다. (팩트는 아닐 수 있다)

확률 공식을 보면

 n은 테이블 전체 로우 수 p는 선택도 E(X)는 카디널리티 집합 수

즉 카디널리티는 일정한 기댓값 즉 평균의 속성을 가지고 있다.

카디널리티가 비용에서 고려되는 이유는 결과집합의 수를 알기 위해서이다비율이 작아 선택도가 1% 밖에 되지 않더라도 그 집합의 전체 개수의 영향을 받는다. 100개중의 1% 10000개 중의 1%은 결과 집합 수가 다르다옵티마이저는 선택도에 대한 통계를 이용하기 때문에 인덱스를 사용할지 안 할지를 통계정보에서 얻을 수 있다카디널리티는 검색되는 대상 집합이 되기 때문에 인덱스나 조건에 의해 어떻게 걸러지냐에 따라 속도와 관계 있으므로 카디널리티의 결과 집합의 수에 따라 조인 테이블의 순서나 인덱스 컬럼의 순서를 확인 해야 할 수도 있다.

 

사실 위의 내용을 고민하지 않아도 인덱스를 만들거나 하는 것에는 크게 상관이 없다보통 자연속성의 Id가 없으면 인조키를 만들어서 라도 유일키를 만들기 때문에 적어도 PK의 중복을 제거하는 인덱스를 만드려고 개발상에 에로사항을 겪을 일이 적다다만 과거 히스토리를 조건 검색하거나 통계쿼리를 작성해야 하거나 기존의 쿼리를 튜닝 해야 하는 경우는 옵티마이저를 이해해 한 튜닝을 해야 할 수 있다.

조인을 만드는 것도 옵티마이저에게 성능판단을 위임하는 것이기 때문에 개발자가 DBMS를 어느 정도 이해해야 하나라는 의문을 가지면 SELECT가 되기 위해 입력하는 조건과 출력되는 컬럼을 빨리 가져오는 쿼리를 만들 줄 아는 것이라고 말할 수 있다. 그리고 옵티마이저는 분명 만능이 아니고 최적화에 실패할 수 있다벤더사 별로 인덱스를 가지고 검색을 할 때 동작방법이나 원리는 DBMS마다 다르기도 하다. MySQLMerge Hash 조인이 없다. DBMS 마다 다른 특성이 있기 때문에 하나의 DB 환경만 공부할 것이 아니면 어느 정도 내부적인 특성도 알아야 한다.

위에 내용에 대해 궁금하게 접근한 이유는 이미 누구나 알고 있는 인덱스의 동작원리보다는 어떤 녀석이 인덱스가 되어야 하지?? 넌 어떤 점이 좋으니 인덱스가 되고 넌 어떤 점이 안 좋으니 인덱스가 될 수 없다 하는 기준을 세우고 그것을 수치화 하거나 테스트를 해보고 싶어서 이다어떤 녀석을 인덱스로 고민하고 또 그것을 활용하는 옵티마이저는 이 인덱스의 어떤 점에 끌려 자기만의 인공지능적인 최적화를 수행 할 수 있을까라는 물음이다.

즉 가능하면 개발자가 데이터베이스에서 인덱스를 만들어야 한다면 적어도 그 테이블의 속성이나 데이터의 분포를 보고 기준이 되는 부분을 한번 생각해보고 정하고자 한다물론 옵티마이저에서 위의 선택도나 카디널리티나 기댓값이나 이런 부분이 통계수치를 이용한다고만 써있고 아직 내부를 직접 구현해 보거나 정확한 계산 수치는 더 연구해 봐야 한다. 다만 위키에 넣을 테스트 내용을 검증할 때 어느 부분을 고민했고 검증하려고 했는지 그 근거로 인덱스를 고려할 컬럼을 정한다면 (PK 말고 다른 컬럼을 조건 검색할 때통계적인 수치 부분이나 그 속성의 Unique 특성을 고려해서 해당 컬럼에 인덱스를 걸고 그 인덱스를 옵티마이저가 판단했을 때 (개발자가)내가 제어 가능한 수준으로 기준을 산정하기 위해 옵티마이저와 인덱스와 통계적 근거를 고려 했다.  

  

인덱스는 따로 위키 설명이 있으니 생략하려고 했으나 언급을 안 할 수가 없을 것 같아 정리한다.

l  인덱스

인덱스는 검색을 빨리 하기 위한 용도로 사용

인덱스를 여러 개 사용할 때는 넌클러스터 인덱스를 사용.

인덱스를 여러 개 사용할수록 옵티마이저가 잘못된 인덱스를 선택할 확률이 높아짐

인덱스를 하나의 컬럼에만 걸어야 한다면 선택도가 가장 큰 경우 즉 중복을 제거 했을 때 집합 원소의 개수가 가장 많은 속성을 지정(보통 PK ID 속성들이 당연하게 쓰임 이걸 고려하는 것은 WHERE 절이나 INCUDE 절에 쓰이는 추가적인 Index 사용이다)

예를 들면 성별을 인덱스로 지정하면 카디널리티가 2밖에 되지 않으므로 인덱스로 걸러지는(Range(범위)로 걸러진다고 할 때경우가 50% 밖에 걸러지지가 않는다. 100 개 중에 유일한 하나일 경우라면 1%를 찾는 Unique Index가 되고 한번에 99%가 걸리지는 경우이다.

 

위의 내용을 보고 다시 궁금증을 가지면 인덱스로 걸러낸다는 개념은 무엇일까???

그리고 우리가 사용하는 테이블은 기본적으로 어떻게 설계하길래 성능이슈가 있게 된 것일까??

확실히 말할 수 있는 것은 사원번호나 주민번호와 같은 속성의 값을 대표 값으로 할 수 있는 것은 현재 구성원 Member 라는 집합(SET)에서 유일성과 대표성을 설명할 수 있는 속성이 된다(모델링을 한다면)

또는 10 이하의 자연수의 집합을 표현 하면 S = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} 일 때 원하는 숫자를 찾으려면 중복이 없기 때문에 유일한 값 하나를 찾을 수 있다 이렇게 유일한 속성이라는 것은 이 값 하나만 찾으면 내가 찾을 수 있는 최소한의 리소스로 나머지 전체를 알 수 있는 이유와 같다 즉, DB용어로는 함수종속이라고 하는데

사원번호 ß 사원정보들 |

사원에 대한 정보들이 사원번호에 함수종속 하므로 사원번호만 알면 그 사원의 정보를 모두 알 수 있다 즉 최소한의 정보만 알아도 관련 정보를 다 알 수 있으니 최소한의 리소스로 원하는 정보를 찾을 수 있는 것이다성능관점은 이것으로 이해하면 걸러진다는 개념은 내가 찾는 원소의 최소한의 속성 하나로 나머지 비상관 원소들을 모두 걸러버릴 수 있으므로 필터율이 매우 높은 필터라고 할 수 있다 즉 필터 성능이 좋으면 인덱스의 성능도 좋다 모든 경우라고 할 수는 없고 = 검색에 탁월 해진다(범위는 지정하기 나름)

위의 설명에서 인덱스의 유일성의 필요에 대해서 성능과 관련 지어 봤다면 조건상 모든 인덱스가 유일하게 쓰이지 않을 수도 있다는 말을 했다그렇다면 그 컬럼이 Null도 들어갈 수 있다는 말이다. Null에 대한 처리는 오라클과 MS Sql이 다르기 때문에 해당되는DBMS 별로 알아야 한다.

기본적으로 데이터베이스의 엔터티릴레이션혹은 테이블이라고 하는 이 복잡한 개념은 해당 컬럼이 정규테이블이라면 다른 컬럼들과 원자적 혹은 독립적인 속성이어야 한다고 한다즉 본인이 변경되었다고 다른 컬럼의 변경에 영향을 미치면 안 된다는 것이다 (PK 제외만약 변경이 된다면 정규적이지 않다고 하게 된다그리고 튜플이라고 하는 이 Row 값 중에 다른 Row와 구별되는 대표값이 Null이 있다면 일단 다른 Row와 구별이 되지 않게 되고 해당 Row를 대표하는 경우도 못하게 된다 즉 고아행이 되어서 영영 지워지거나 이용되지 않은 채로 테이블에 남을 수도 있다대표값이 구별되면서 Not Null이어야 대표값만 가지고 관련 정보들을 찾을 수 있다 인덱스가 이런 용도이고 이런 용도로 인덱스를 사용하려면 원천적인 모델링도 잘 해놔야 2개이상의 조건 검색에 대응하기 좋은 모델이라고도 할 수 있다인덱스는 데이터의 얼굴 같은 느낌이다얼굴만 보고 그 사람을 알고 얼굴을 보고 그 사람을 찾아야 한다. (굳이 예를 들자면)

 

인덱스 스캔

-      인덱스의 순서에 따라 (A,B)로 지정되면 A에 대해 정렬이 되고 A가 동일할 경우 B로 정렬한다.

인덱스 유일 스캔

-      유일 인덱스를 사용하여 단 하나의 데이터를 추출하는 방식

-      중복을 허락하지 않는 인덱스 스캔이며 수평적 탐색이 없이 수직적 탐색이 이루어짐

인덱스 범위 스캔

-       인덱스를 이용해서 한 건 이상의 데이터를 추출하는 방식

-       Leaf 블록을 필요한 범위만 스캔하는 방식

-       인덱스를 구성하는 선두 컬럼이 조건절에 사용되어야 한다.

-       필요한 범위만 스캔

인덱스 역순 범위 스캔

-       인덱스의 리프 페이지를 내림차순으로 검색하는 방식 최대값을 쉽게 찾는 것과 같은 이치

인덱스 전체 스캔

-       Leaf 블록 전체를 스캔 하는 방식 데이터 검색을 위한 최적의 인덱스가 없을 때 차선으로 사용된다.

-       가능하면 인덱스가 있는 경우가 낫고 전체 데이터에서 극히 일부를 추출하는 경우라면 Full Table Scan보다 낫지만 Range Scan보다는 떨어진다.

-       인덱스 컬럼에서 1순위가 아닌 컬럼을 이용해 검색할 때 발생한다.

-    Index를 정상적으로 사용하기 보다는 전체를 검색

SQL Server의 클러스터형 인덱스

-       인덱스의 Leaf 페이지가 곧 데이터 페이지이며 인덱스 키 컬럼을 Leaf 페이지에 같이 저장하기 때문에 테이블을 랜덤 엑세스하지 않는다(Lockup과정X)

-       Leaf 페이지를 찾으면 바로 모든 컬럼의 값을 얻을 수 있으며 인덱스로 정렬이 되어 있다모든 데이터가 인덱스 키 컬럼 순으로 물리적으로 정렬

전체 데이터 중에 일부의 데이터를 찾는다면 인덱스를 이용해 원하는 데이터를 쉽게 찾지만 테이블의 데이터를 찾을 때는 하나의 블록씩 읽는 인덱스 스캔 방식보다는 여러 블록씩 읽은 전체 테이블 방식이 유리할 수도 있다.

 

l  조인

두 개 이상의 테이블을 하나의 집합으로 만드는 연산이다. FROM 절에 두 개 이상의 테이블이 나열이 되면 테이블 순서에 따라 조인이 이루어 진다예를 들어 A, B, C 3개의 테이블을 조인하게 되면 순서에 따라 A B를 먼저 조인해서 그 결과와 C를 조인한다. A, C, B 순서일 경우는 A C를 먼저하고 B를 나중에 한다.

 

l  DB의 조인

논리적내부조인(Natural, Inner) 외부조인(Outer), 크로스조인

물리적: NL 조인, Sort merge Join, Hash 조인(그 밖에 더 있으나 여기까지만)

실행계획을 이해했고 조인의 기본적인 처리 절차를 파악해둠으로써 나중에 조인을 깊이 이해 하기 위한 사전준비일 뿐이다…(책에서)

1. 내포조인

2. 정렬병합

3. 해시조인

4. 세미조인(다루지 않음)

5. 카티전 조인(다루지 않음)

6. 아우터 조인(다루지 않음)

7. 인덱스 조인(다루지 않음)

기본적으로 조인의 원리를 생각하려면 SQL 즉 코드로 논리적인 조인을 만들 것이고 그 조인을 구성하는 Table 즉 대상으로부터 data를 취득할 때 DBMS가 어떻게 데이터를 취합하고 어떻게 데이터를 저장하고 그 저장된 데이터에서 원하는 조건절에 맞게 가져오는 지 궁금증을 가지는 데에서부터 시작한다.

즉 어떻게 내부조인이나 외부조인 같은 방법을 사용해서 쿼리(질의)를 하고 그 결과는 DBMS에서 출력해 주며 디스크 or 메모리 상에서 어떠한 연산(알고리즘에 의해 빠른 속도 or 느린 속도로 결과를 만들어주게 되는 것인가?

여기서 어떠한 연산?? 이 부분을 아는 것이 물리적인 조인의 수행 원리를 아는 목표이다.

기본적으로 생각 해야 하는 것은 적어도 우리는 이미 설계가 완료된 테이블로 구성된 DB에서 작업이 일어날 것이며 그렇기 때문에 정규테이블간의 관계에 따라 필요한 데이터를 얻기 위해 조인을 하게 되고 그때 그 테이블을 필터하는 범위나 컬럼의 선택도나 사용하고 있는 인덱스에 의해 쿼리의 성능이 정해진다

 

자 그렇다면 1. 내포조인 2. 정렬병합 3. 해시조인에 대해 간략히 언급하고 넘어간다

 일단 기본은 내포조인이다즉 중첩된 루프를 돌면서 Outer table에서 Inner Table로 조건이 일치되는 data를 찾는 것이 가장 보편적으로 사용되는 조인이다. NL 조인은 중첩된 반복문과 유사하다

FOR 선행 테이블 읽음 --> 외부 테이블(Outer Table)

     FOR 후행 테이블 읽음 --> 내부 테이블(Inner Table)

          (선행 테이블과 후행 테이블 조인)

여기서 기본적으로 사용되는 자료구조는 B+Tree 이며 Root 부터 Branch를 지나 Leaf를 찾는 것이 알고리즘의 핵심이다. 이 알고리즘을 사용할 때 정렬되냐 정렬이 안되냐에 따른 차이는 있지만 기본적으로 NL 조인은 랜덤엑세스를 해서 찾아간다 그래서 (Index)Table Scan을 하면 안 되고 Index Seek or Range Scan을 해서 범위로 접근해야 기본적인 성능이 좋다고 본다

그 한번의 랜덤액세스가 중첩된 Roof를 돌면서 반복되므로 Scan이 아닌 Seek or Range를 가지는 부분이 성능의 핵심이다.

대량의 데이터 조인시 랜덤 액세스가 많이 발생되므로 인덱스 유무에 따라 성능 차이가 많이 난다 주로 소량의 데이터나 부분범위 처리에 적합하다. NL조인의 경우 인덱스가 있어도 드라이빙 집합(Target)의 개수가 많아 시작단계에서 랜덤 액세스가 많으면 성능상 좋지 않을 수 있다.

 

Sort Merge의 경우는 위의 Case에 정렬된 자료구조를 가지고 있다고 생각하면 된다 즉 내부적으로 정렬 프로세스를 거치니 초기 작업은 오래 걸려도 실제로 Merge 하는 작업은 적게 걸릴 수 있다

NL의 단점을 극복하기 위해 생겼다고 한다 각각 조인하는 대상으로부터 결과 집합을 정렬하는 과정을 거치고 양쪽을 개별적으로 읽어들 인 다음 Merge하는 것으로 NL 조인보다 인덱스의 영향을 적게 받는다

 

Hash의 경우는 Sort Merge가 정렬프로세스를 가지고 있어서 정렬의 단점을 극복하려고 생겼다고 한다 즉 이 부분은 알고리즘적으로 접근해야 하는데 O(logn)도 충분히 훌륭한 것이지만 해시 맵을 만들면 해시 알고리즘은 O(N)이기 때문에 정렬을 하지 않고 검색이 가능하다고 한다단 해시결과가 유일한 지 검증하는 부분이 추가되므로 꼭 O(N)은 아니라고 한다 어쨌든 데이터가 무지막지하게 많아서 알고리즘 적으로 해시 맵을 만들어서 검색하는 것이 더 탁월해 사용하는 조인방법이다조인 컬럼에 적당한 인덱스가 없어 NL조인이 비효율적이고 소트부하가 심할 때 고려된다.

 

NL 조인은

랜덤액세스 기반이어서 대량의 데이터 조인시 불리

인덱스 유뮤에 따라 성능 차이가 많이 남

선행 테이블의 결과 집합에 따라 속도에 영향을 많이 받음

소량의 데이터나 부분범위 처리에 적합

 

소트 머지 조인은

집합을 정렬하는 과정을 거침

양쪽을 개별적으로 읽어 들여서 한번에 두 테이블을 연결하는 NL조인에 비해 인덱스의 영향을 적게 받음

 

해시조인은

선행 테이블의 조인 키를 기준으로 해쉬 함수를 적용

조인컬럼에 적당한 인덱스가 없어 NL 조인이 비효율적일 때

인덱스가 있어도 드라이빙 집합의 개수가 매우 많아 랜덤액세스가 많을 때

소트 부하가 심할 때

수행 빈도가 낮고 쿼리 수행 시간이 오래 걸리는 대용량 테이블을 조인할 때

하드웨어 자원이 좋을 때(메모리에 올려서 빠른 처리가 가능)

 

조인은 기본적으로 복수의 대상간 각각의 내부 구현방법으로 조건이 맞는 경우를 찾는 것이고 NL의 경우 B+Tree 구조상에 랜덤액세스의 부담 때문에 그 부담을 줄이고자 정렬된 상태에서 액세스를 하는 Sort Merge 조인이 생겨났고 정렬을 미리 해야 하는 전 처리에 대한 부담으로 유일한 해시값을 생성해서 그 해시를 이용해 조인을 하는 것이 해시 조인이다

여기서 중요한 것은 인덱스 설계를 잘하는 것이다 인덱스에 따라 옵티마이저가 보다 좋은 액세스경로를 찾을 수 있게 하는 근본적인 방법이다인덱스는 현재 발생할 수 있는 상황과 데이터의 분포도나 결합도 선택도 등을 고려해서 설계되어야 한다

 

 

 

 

본론 à PPT or Test Case (따로 작성)

 

위에서 언급한 내용으로 확인 한 순서

물리적인(DBMS) 조인 수행 원리 à DBMS 내부에서 SW 스스로 동작하는 방식

옵티마이저 à 조인을 제어하는 DBMS의 엔진

카디널리티(선택도) à 옵티마이저가 실행계획 작성에 활용하는 통계 data

확률 à 선택도의 개념이 사건이 발생하는 확률의 개념과 비슷

인덱스 à 실행계획을 변경시킬 수 있는 Input

조인 방법 à 논리적 조인 / 물리적 조인의 구분

자료구조(B+Tree) à 인덱스 검색의 기본 방식

알고리즘 à 해시 조인의 복잡도가 O(1)이기 때문에(자료의 수와 관계X) 대량 검색에 탁월

Sort, Loop의 부담이 없음

인덱스 설계 à 선두 컬럼의 사용, 유일 인덱스 여부 고려, 검색조건 확인, 출력 값 확인, 조인 컬럼 확인

성능(튜닝) à 아직 도달하지 못한 부분 튜닝 전에 트랜잭션이나 동시성제어(Lock)에 대한 선행 필요

 

통계적 테스트 확인

1.     카디널리티가 큰 집합의 경우 큰 à 작은 순과 작은 à 큰 순으로 인덱스 생성 후 비교

     범위를 제한다 제낀다?? 라는 부분이 성능에 영향이 있는지 가설과 확인

카디널리티에 대한 성능 테스트

출처http://jojoldu.tistory.com/243

 

 

SQL 처리 과정과 I/O 


1. SQL 파싱과 최적화 

2. SQL 공유 및 재사용

3. 데이터저장 구조 및 I/O 매커니즘


1. SQL 파싱과 최적화 

SQL은 Structured Query Language의 줄임말이다. 말 그대로 구조적 질의 언어다. 

SQL은 기본적으로 구조적이고 집합적이고 선언적인 질의 언어다. 원하는 결과집합을 구조적, 집합적으로 선언하지만 그 겨롸집합을 만드는 과정은 절차적이다 그렇기 때문에 프로시저가 필요하며 그 프로시저를 만들어 내는 DBMS 내부엔진이 바로 옵티마이저이다. 이 옵티마이저가 프로그래밍을 대신해 주는 셈이다. 

DBMS 내부에서 프로시저를 작성하고 컴파일해서 실행 가능한 상태로 만드는 전 과정을 SQL 최적화라고 한다. 


SQL 최적화 

SQL 파싱 : 사용자로 부터 SQL을 전달 받으면 가장 먼저 SQL 파서가 파싱을 진행한다. 

   파싱트리를 생성하고 문법검사 -> 의미검사(?) -> 권한검사와 같은 것을 한다 

SQL  최적화 : 옵티마이저가 역할을 맡고 미리 수집한 시스템 및 오브젝트 통계정보를 바탕으로 다양한 실행경로를 생성해서 비교한 후 가장 효율적인 

                  하나를 선택한다. 데이터베이스의 성능을 결정하는 가장 핵심적인 엔진이다.

로우 소스 생성 : 로우(Row??) 소스 생성기가 그 역할을 맡고 실제 실행 가능한 코드 또는 프로시저 형태로 포맷팅하는 단계이다.


SQL 옵티마이저 

SQL 옵티마이저는 사용자가 원하는 작업을 가장 효율적으로 수행할 수 있는 최적의 데이터엑세스 경로를 선택해 주는 DBMS 핵심 엔진이다. 

1. 사용자로부터 전달받은 쿼리를 수행하는 데 후보군이 될만한 실행 계획들을 찾아낸다. 

2. 데이터 딕셔너리에 미리 수집해 둔 오브젝트 통계 및 시스템 통계정보를 이용해 각 실행계획의 예상비용을 산정한다. 

3. 최저 비용을 나타내는 실행 계획을 선택한다. 

옵티마이저는 네비게이션에서 길을 찾아주는 역할을 하며 비용을 근거로 인덱스 사용 유무와 쿼리 수행방법을 결정한다. 


SQL을 최적화 할 때 옵티마이저가 사용하는 정보 

- 테이블, 컬럼, 인덱스 구조에 관한 정보

- 오브젝트 통계 : 테이블 통계, 인덱스 통계, 히스토그램을 포함한 컬럼 통계

- 시스템 통계 : CPU 속도 Single Block I/O 속도, Multi Block I/O 속도 

- 옵티마이저 관련 파라미터 



2. SQL 공유 및 재사용

라이브러리 캐시 : 내부 프로시저를 반복 재사용할 수 있도록 캐싱해 두는 메모리 공간 

SGA(System Global Area) : 서버 프로세스와 백그라운드 프로세스가 공통으로 엑세스하는 데이터와 제어 구조를 캐싱하는 메모리 공간이다. 

소프트 파싱 : SQL을 캐시에서 찾아 곧바로 실행단계로 넘어가는 것

하드 파싱 : 캐시에서 찾는 데 실패해서 최적화 및 로우소스생성 단계까지 모두 거치는 것 


라이브러리 캐시가 필요한 이유 

데이터 베이스에서 이루어지는 처리과정의 대부분은 I/O 과정에 집중되는 반면 하드 파싱은 CPU를 많이 소비하는 몇 안되는 작업 중 하나 

이렇게 어렵게 작업을 거처 생성한 내부 프로시저를 한 번만 사용하고 버린다면 비효율적이다. 

이름없는 SQL 문제 

사용자 정의 함수/프로시저, 트리거, 패키지 등은 생성할 때부터 이름을 갖는다. 컴파일한 상태로 딕셔너리에 저장되며 실행할 때 라이브러리 캐시에 적재함으로써 여러 사용자가 공유하면서 재사용한다. 

DBMS에서 수행되는 SQL이 모두 완성된 SQL이 아니며 개발과정에서 수시로 변경이 일어날 수 있다 일회성(Ad-hoc) SQL도 있고 무효화된 SQL 까지 모두 저장해서 쓰려면 많은 공간이 필요하게 된다. 그만큼 SQL을 찾는 속도도 느려지므로 오라클, SQL Server 같은 DBMS가 SQL을 영구 저장하지 않는 쪽을 택했다. 


프로시저에서 파라미터를 전달 받아 실행하는 경우가 추천되며 이런 경우 바인드 변수만 변경되며 프로시저 하나를 공유하면서 재사용할 수 있다. 



3. 데이터저장 구조 및 I/O 매커니즘

프로세스의 생명주기 

프로세스는 실행 중인 프로그램이며 생성 이후 종료 전까지 준비와 실행 대기를 반복한다. 실행 중인 프로세스는 interrupt에 의해 수시로 실행 준비상태로 전환 했다가 다시 실행 상태로 전환한다 

열심히 일하던 프로세스도 디스크에서 데이터를 읽어야 할 땐 CPU를 OS에 반환하고 잠시 waiting 상태에서 I/O가 완료되기를 기다린다. 이렇게 기다리는 시간이 생기게 되면 SQL이 느려지게 된다. 이 때를 디스크 I/O 라고 하고 이것은 SQL의 성능에 큰 영향을 끼친다. 


데이터베이스의 저장 구조 

가장 큰 개념으로 테이블 스페이스가 있으며 테이블 스페이스는 여러개의 데이터파일로 구성된다. 또 테이블 스페이스는 세그먼트들을 포함한다. 세그먼트는 테이블, 인덱스 처럼 데이터 저장공간이 필요한 오브젝트다. 테이블, 인덱스를 생성할 때 데이터를 어떤 테이블스페이스에 저장할지를 지정한다. 

세그먼트는 여러 익스텐트들로 구성된다. 테이블도 하나의 세그먼트이며 인덱스(테이블??)도 하나의 세그먼트이다. 

익스텐트는 연속된 블록들의 집합이기도 하다. 사용자가 입력한 레코드를 실제로 저장하는 공간은 데이터 블록이다. SQL 서버는 블록대신 페이지 라는 용어를 사용한다. 

블록(페이지) : 데이터를 읽고 쓰는 단위

익스텐트 : 공간을 확장하는 단위, 연속된 블록 집합

세그먼트 : 데이터 저장공간이 필요한 오브젝트(테이블, 인덱스, 파티션, LOB등)

테이블스페이스 : 세그먼트를 담는 콘테이너

데이터파일 : 디스크 상의 물리적인 OS 파일

블록이 DBMS가 데이터를 읽고 쓰는 단위다. 

??? 데이터 I/O 단위가 블록이므로 특정 레코드 하나를 읽고 싶어도 해당 블록을 통째로 읽는다. 

테이블 뿐만 아니라 인덱스도 블록 단위로 데이터를 읽고 쓴다


시퀀셜 액세스 : 논리적 또는 물리적으로 연결된 순서에 따라 차례대로 블록을 읽는 방식. 인덱스 리프 블록은 앞뒤를 가리키는 주소값을 통해 논리적으로

                    서로 연결되어 있다. 이 주소 값에 따라 앞 또는 뒤로 순차적으로 스캔하는 방식이다. 

   각 익스텐트의 첫 번째 블록뒤에 연속해서 저장된 블록을 순서대로 읽으면 그것이 Full Table Scan 이다. 

랜던 액세스 : 논리적, 물리적인 순서를 따르지 않고 레코드 하나를 읽기 위해 한 블록씩 접근하는 방식


DB 버퍼 캐시 : 앞의 라이브러리 캐시가 프로시저 실행 계획과 같은 코드 캐시라고 한다면 DB 버퍼 캐시는 데이터 캐시라고 할 수 있다. 

                   디스크에서 어렵게 읽은 데이터 블록을 캐싱해 둠으로써 같은 블록에 대한 반복적인 I/O Call을 줄이는데 목적이 있다. 

                   서버 프로세스와 데이터 파일 사이에 버퍼캐시가 있으므로 데이터 블록을 읽을 땐 항상 버퍼캐시부터 탐색한다. 


논리적 I/O : SQL을 처리하는 과정에 메모리 버퍼캐시에서 발생한 총 블록 I/O를 말함 

                논리적 I/O와 메모리 I/O가 같은 으미는 아니지만 같다고 생각해도 무방하다. 

                아무리 여러번 실행해도 매번 읽는 블록 수는 같으며 SQL을 수생하면서 읽은 총 블록 I/O가 논리적 I/O이다. 


물리적 I/O : 디스크에서 발생한 총 블록 I/O를 말한다. DB 버퍼캐시에서 블록을 찾지 못해 디스크에서 읽은 블록 I/O가 물리적 I/O이다. 


버퍼캐시 히트율 (BCHR)

- 온라인 트랜잭션을 주로 처리하는 애플리케이션이라면 시스템 레벨에서 평균 99% 히트율을 달성해야 한다. 

- 실제 SQL 성능을 향상하려면 물리적 I/O(분자)가 아닌 논리적 I/O(분모)를 줄여야 한다.

- SQL을 튜닝해서 읽는 총 블록 개수를 줄이면 되며 논리적 I/O는 항상 일정하게 발생하지는 통제가능한 내생변수다. 

- 논리적 I/O를 줄임으로써 물리적 I/O를 줄이는 것이 곧 SQL 튜닝이다. 

버퍼캐시 히트율의 주의할 점 

- BCHR이 SQL의 성능을 좌우하지만 BCHR이 높다고 해서 효율적인 SQL을 의미하지는 않는다. 같은 블록을 비효율적으로 반복해서 읽는 것도 BCHR이 높아진다. 


Single Block I/O

- 한 번에 한 블록씩 요청해서 메모리에 적재하는 방식 

- 인덱스를 이용할 때는 기본적으로 인덱스와 테이블 블록모 Single Block I/O 방식 사용 

- 인덱스 루트 블록을 읽을 때 

- 인덱스 루트 블록에서 얻은 주소 정보로 브랜치 블록을 읽을 때 

- 인덱스 브랜치 블록에서 얻은 주소 정보로 리프 블록을 읽을 때 

- 인덱스 리프 블록에서 얻은 정보로 테이블 블록을 읽을 때 


Multi Block I/O 

- 한 번에 여러 블록씩 요청해서 메모리에 적재하는 방식 

- 많은 데이터 블록을 읽을 때 효율적이며 인덱스를 이용하지 않고 테이블 전체를 스캔할 때 이 방식 사용 


Table Full Scan

- 시퀀설 엑세스와 Multiblock I/O 방식으로 디스크 블록을 읽어 들인다. 

- 한 블록에 속한 레코드를 한번에 읽어 들이고 캐시에서 못 찾으면 한번의 수면(I/O Call)을 통해 인접한 수십~수백 개 블록을 한꺼번에 I/O하는 방식 


Index Range Scan 

- 큰 테이블에서 데이터를 소량 검색할 때는 인덱스를 꼭 이용해야 한다.  

- 랜덤 액세스와 Single Block I/O 방식으로 디스크 블록을 읽는다.

- 캐시에서 블록을 못 찾으면 레코드 하나를 읽기 위해 매번 프로세스 waiting에서 대기하게 된다. 하나의 레코드를 읽기 위해 블록을 계속 읽게되는 것은 성능 저하의 우려가 있다. 

- 많은 데이터를 읽을 때는 Table Full Scan이 유리하다.  


성능문제는 인덱스로 해결하지 말고 데이터 결과 집합이 많으면 Full Table Scan이 Index Scan 보다 유리하다. 


캐시 탐색 매커니즘 

버퍼 캐시 탐색 과정을 거치는 경우 

- 인덱스 루트 블록을 읽을 때 

- 인덱스 루트 블록에서 얻은 주소 정보로 브랜치 블록을 읽을 때 

- 인덱스 브랜치 블록에서 얻은 주소 정보로 리프 블록을 읽을 때 

- 인덱스 리프 블록에서 얻은 주소 정보로 테이블 블록을 읽을 때

- 테이블 블록을 Full Scan 할 때 

내부구조 

- 버퍼캐시를 해시 구조로 관리한다. 

메모리 공유자원에 대한 액세스 직렬화 

- 버퍼블록은 공유자원이다. 즉 모두에서 권한이 있기 때문에 누구나 접근할 수 있다. 문제는 두 개 이상의 프로세스가 동시에 접근하려고 할 때 발생한다. 

- 공유자원이라도 내부에서는 한 프로세스식 접근하도록 직렬화 매커니즘이 필요하다. 이런 직렬화를 실현하기 위해 DBMS는 동시성 제어를 위한 Lock을 제공한다. 

- 프로세스가 줄을 서서 기다리는 것을 가능하도록 지워너하는 매커니즘이 래치(Latch)이다. 

- 오라클은 버퍼 헤더에 버퍼 Lock을 설정함으로써 버퍼 블록 자체에 대한 직렬화 문제를 해결하고 있다 (SQL Server 는??) 

용어 정리

 

DB의 조인

-       논리적: 내부조인(자연조인, Inner), 외부조인(Outer), 크로스 조인(크로스 프로덕트)

-       물리적: Nested Roof, Sort Merge, Hash 조인(그 밖에 더 있으나 여기까지만)

 

조인 수행 원리

PPT(애니메이션 가능)

 

옵티마이저

-       옵티마이저를 이해하는 것이 튜닝의 시작이고 옵티마이저를 제어하는 것이 튜닝의 기본이다.

-       옵티마이저에 의해 발생하는 조인 3가지: NL조인 , Sort Merge 조인, Hash 조인 이 있다 이것은 물리적인 조인이기 때문에 개발자가 직접 사용하게 되는 조인은 아니다

-       개발자가 직접 제어하는 조인은 내부조인이나 외부조인과 같은 논리적인 조인을 선언해서 만든다.

n  Ludy의 경우 Merge Into 구문도 조인이 있다  

-       즉 이런 논리적인 조인을 옵티마이저라는 녀석이 DBMS 내부에서 물리적인 조인으로 표현하고 만드는 것이다

-       옵티마이저는 사용자가 질의한 SQL문에 대해 최적의 실행 방법을 결정하는 역할을 수행

-       옵티마이저가 정한 최적의 실행 방법을 실행 계획이라고 함

-       다양한 실행 방법들 중에서 최적의 실행 방법을 결정하는 것이 옵티마이저의 역할

-       실제로 SQL문을 처리해 보지 않은 상태에서 결정해야 하는 어려움이 있다

n  DBMS 별로 옵티마이저의 성능을 비교해 볼 수 있는 부분이라고 볼 수 있겠다

-       보통 CBO(비용기반 옵티마이저를) 생각하면 된다

-       최적의 실행방법 결정이라는 것은 어떤 방법으로 처리한 것이 최소 일량(비용)으로 동일한 일을 처리할 수 있을 지 결정하는 것이다

-       즉 옵티마이저 최적의 실행 방법을 결정하는 것을 개발자는 도와주어야 한다

-       단순히 조인의 원리를 아는 것보다는 이 옵티마이저가 어떻게 물리적인 조인으로 변경시키는지를 알아야 한다

 

 

카디널리티

-       Cardinality: 사전적 의미로는 집합원의 개수

-       카디널리티가 낮은 경우에서 속성의 예를 들면 성별, 부서, 지역이 있다.

n  성별의 경우 남자, 여자 두 가지 경우만 가능하므로 매우 낮다고 할 수 있다

n  주민번호, 사원번호와 같은 경우 조직원이 많을수록 카디널리티가 높다

-       간단히 생각하면 한 개를 뽑는 뽑기를 했을 때 그 뽑는 모집단의 수가 많으면 높다고 할 수 있다

-       데이터베이스로 한정해보면 테이블에서 Primary Key에 해당 되는 경우의 수 라고도 볼 수 있겠다

 

카디널리티 설명 예제

PPT

예를 들면 학급 엔터티로 학년의 성적표를 만든다면 반 번호가 기본키가 되면 그 학급의 학생의 수가 카디널리티가 될 수 있다 (학급(1) : 학생(N))

다른형태로는 특정 쿼리문을 실행시켜서 나오는 결과 값(Row)를 카디널리티라고 한다

즉 학교라는 개념에서는 학급이 카디널리티일 수 도 있고 학급이라는 개념에서는 학생이 카디널리티일 수도 있다

예를 들면 사원테이블의 전체 레코드 수가 1000개일 때

WHERE 부서 = ‘인사팀이면 그 중 인사팀 사원이 10명이면 선택도는 10/1000 = 0.01이 됩니다. 즉 전체 rows * 선택도 = 결과수, 즉 카디널리티 가 결과 수라면 10이 카티널리티 입니다.

 

카디널리티를 이해해야 하는 이유

DB의 옵티마이저에서 조인을 맺어주는 항목으로 인덱스를 사용하게 됩니다. 물론 없으면 따로 사용하지 않고 Full Scan 이 되겠지요. 중요한 건 현재 접근하는 컬럼을 조건으로 접근할 때 얼마나 많이 걸러지느냐 입니다.

선택도가 확률이라고 하면 해당 카디널리티 즉 경우의 수는 전체 데이터의 개수에 영향을 받게 됩니다. 데이터가 많을 때는 해당 조건으로 얼마나 많이 걸러지는 지 즉 중복이 최소한일수록 해당 조건으로 많이 걸러지게 됩니다. 중복된 data를 검색 즉 중복이기 때문에 해당 data 전체를 Scan 해야 하기 때문에 원하는 하나를 찾는 경우에 범위를 좁혀가는 검색에서는 시간이 더 걸리게 됩니다.

순차적인 선형 검색일 경우 순서대로 전체를 보게 되지만 범위를 좁혀가는 이진검색이 성능이 더 좋다고 합니다.

이렇게 집합에서 원소가 어떻게 분포되었냐에 따라 스캔속도가 차이가 나므로 옵티마이저는 인덱스의 설계가 어떻게 되었냐에 따라 영향을 받게 됩니다. 개발자는 물리적인 조인에 대해 직접 관여는 하지 않지만 인덱스 설정에 따라 영향은 줄 수 있다는 말입니다.

일반적으로 옵티마이저는 여러 인덱스가 있을 때 선택도가 낮은 즉 중복발생확률이 낮은 인덱스를 사용합니다.

인덱스

인덱스는 검색을 빨리 하기 위한 용도로 사용합니다.

인덱스를 여러 개 사용할 때는 넌클러스터 인덱스를 사용합니다.

인덱스를 여러 개 사용할수록 옵티마이저가 잘못된 인덱스를 선택할 확률이 높아집니다.

인텍스를 하나의 컬럼에만 걸어야 한다면 카디널리티가 가장 높은 경우 즉 중복을 제거 했을 때 집합 원소의 개수가 가장 많은 속성을 지정합니다.

예를 들면 성별을 인덱스로 지정하면 카디널리티가 2밖에 되지 않으므로 인덱스로 걸러지는(Range로 걸러진다고 할 때) 경우가 50% 밖에 걸리지지가 않습니다.

100 개 중에 유일한 하나일 경우라면 1%이나 99%가 걸리지는 경우이겠습니다.

인덱스로 걸러낸다는 개념은 무엇일까요???

그리고 우리가 사용하는 테이블은 기본적으로 어떻게 설계하길래 성능이슈가 있게 된 것일 까요??

어려워 지는데 하나 확실히 아는 것은 사원번호나 주민번호와 같은 속성의 값을 대표값으로 할 수 있는 것은 현재 구성원을 구성하는 큰 집합에서 유일한 속성으로 취급할 수 있는 것입니다. 10이하의 자연수의 집합일 경우라면 {1,2,3,4,5,6,7,8,9,10} 원하는 숫자를 찾을 때 중복이 없으므로 유일한 값 하나를 찾습니다. 이렇게 유일한 속성의 인덱스는 내가 찾는 요소 하나로 최대한으로 나머지 원소를 거를 수 있습니다. 이런 개념에서 카디널리티를 이해하고 넘어가고자 합니다.

위의 개념에서 인덱스의 유일성이 필요에 대해서 이해가 되었으면(모든 인덱스가 유일성을 가지는 것은 아닙니다)

낫널에 대해서 이야기 해 볼까요? 기본적으로 데이터베이스의 엔터티 혹은 릴레이션 혹은 테이블이라고 하는 개체는 해당 컬럼이 동일 선상에서 이미 다른 컬럼들과 원자적인 구조로 만들어져야 합니다. 즉 본인이 변경되었다고 다른 컬럼의 값도 변경이 된다는 것은 정규적이지 않은 관계입니다. 그리고 data의 중복을 최소화 한다는 것은 하나의 Row, 튜플 이런 값이 다른 Row, 튜플과 구별되는 대표값이 있어야 한다는 것입니다. 그렇다면 대표값이 널이 되어버리면 일단 다른 Row들과 구별되거나 해당 Row를 대표할 수 없습니다.

유일한 Row이고 그 값이 비어있지 않으며 속성들끼리 원자적인 테이블에서 대표값만 가지고 그 Row, 튜플을 찾거나 맵핑 할 수 있습니다. 대표값만 가지고 데이터를 찾는다.. 인덱스가 그런 용도의 느낌입니다.

 

확률

확률이 예를 들기 좋은 것 같습니다.

카디널리티라는 주머니를 만들고 거기에 빨주노초파남보 의 무지개 색 공을 집어넣으면 이 경우 선택도는 1/7 입니다.

그러면 빨간공 3개 노란공 2개 보라색공 4개를 넣으면 이 경우 선택도는 몇 일 까요?? 빨간공의 확률은 1/3 노란공은 2/9 보라색공은 4/9 입니다 이건 단순히 시행(반복)이라는 시도와 맞물려서 말할 수 있는 확률 인 것이고

공이라는 개념으로 접근하면 3개의 색만을 가지므로 그 공이라는 릴레이션은 엔터티는 테이블의 색깔이라는 속성은 선택도가 1/3 0.33 입니다.

 

그리고 그 빨주노초파남보 이면서 각각 1~7까지의 숫자가 매겨저 있는 상황이라면

빨 주 노 초 파 남 보

1  2  3  4  5  6  7

 

이런 상황에서 숫자와 색깔이라는 속성의 선택도는 어떻게 될 까요??

제가 확률을 얘기 했으니 숫자로 하면 1/7 색으로 하면 1/7입니다. 단 이것은 전에 말했듯이 시행에 의해 획득할 수 있는 한번의 공입니다 다만 유일성이 확보가 되니 선택도는 1/전체 이고 뭘로하든 카디널리티는 1 입니다.

근데 예제를 바꾸면

빨 주 노 초 파 남 보

1  2  2  2  3  4  4

 

이런 상황에서는 선택도가 변하겠죠? 색으로는 1/7이고 숫자로는 1/4 입니다.

여기서 어떤 속성을 기준으로 하느냐에 따라 확률이 달라졌고 그에 따라 선택도(카디널리티)도 달라졌습니다.

여기서 속성을 색과 숫자로 나눈 이유는 인덱스를 어디다 거는 기준이 뭐냐?? 라는 말을 하고 싶어서 입니다.

 

최소한의 기준은 앞에서 선택도가 낮고 카디널리티가 높은 경우를 말했습니다. 그리고 여기서 선택도라는 확률에서 분모 즉 유일성이 보장되는 모집단의 개수입니다. 경우의 수라고도 보이겠네요

이게 더 큰 경우로 색깔이 나오기 때문에 공이라는 릴레이션 엔터티 테이블은 인덱스를 색깔로 잡아야 숫자보다 효율이 좋습니다.

즉 숫자로 해야 걸러지는 케이스가 많아진다는 말입니다.

확률 공식을 보면

 즉 카디널리티는 일정한 기댓값 즉 평균의 속성을 가지고 있습니다.

위의 내용은 확률 분포 내용으로 기댓값이란

사실 위의 내용을 고민하지 않아도 인덱스를 만들거나 하는 것에는 크게 상관이 없습니다. 조인을 만드는 것도 또 옵티마이저에게 성능판단을 위임하는 것도 개발자가 DBMS를 이해해야 하나? 라는 의문을 가지면 딱히 할말은 없지만 옵티마이저는 분명 만능이 아니고 최적화에 실패할 수 있습니다. 그리고 인덱스를 가지고 검색을 할 때 동작하는 거나 원리는 DBMS마다 조금씩 다를 수 있습니다. 모든게 같으면 DB가격이나 성능이 같아야 할 테니까요 위에 내용에 대해 궁금하게 접근한 이유는 이미 누구나 알고 있는 인덱스의 동작원리보다는 어떤 녀석이 인덱스가 되어야 하지?? 넌 어떤 점이 좋으니 인덱스가 되고 넌 어떤 점이 안좋으니 인덱스가 될 수 없다 하는 어떤 녀석이 인덱스로 고민하고 또 그것을 활용하는 옵티마이저는 이 인덱스의 어떤 점에 끌려 자기만의 인공지능적인 최적화를 수행 할 수 있을까? 라는 부분에서의 물음입니다.

즉 가능하면 내부적으로 개발자가 직접 인덱스를 만든다면 적어도 그 테이블이나 데이터를 보고 그 기준이 되는 부분을 찾아보고 싶기 때문입니다. 물론 옵티마이저에서 제가 생각한 선택도나 카디널리티나 기댓값이나 이런 부분에 영향을 미쳐서 판단한다고 증명된 것은 없습니다. 다만 저의 위키의 내용은 제가 인덱스를 고려할 컬럼을 정한다면 (PK 외적으로) 저런 수치적인 부분이나 그 속성의 고유한 특성을 고려해서 해당 컬럼에 인덱스를 걸고 그 인덱스를 옵티마이저가 제가 원하는 기준(작은 수량은 NL조인, 대량의 데이터는 Sort Merge 조인, 메모리 성능도 좋고 양도 많으면 Hash 조인으로 동작하기를 희망하기 때문에 인덱스를 재물로 삼았습니다. )

인덱스가 아니라면 일단 옵티마이저에게 어떻게 말을 걸고 접근할지 막연하기 때문에 인덱스로 그리고 그것이 조건이 되었을 때 옵티마이저 안녕? 이것을 하려고 합니다.

 

위의 내용까지가 서론이고 이제 본론에서는 실제 데이터의 수량을 달리하면서 실행 계획에서 어떻게 성능적으로 달라진는지 확인해 보겠습니다.

 

조인을 깊이 이해하는 것은 어렵고 동일한 결과를 얻을 수 있는 조인 방법은 많이 있지만 주어진 환경에 따라 효율성이 차이가 난다

즉 어떻게 사용하느냐에 따라 결과가 천지만별이라는 것이다.

실행계획을 이해했고 조인의 기본적인 처리 절차를 파악해둠으로서 나중에 조인을 깊이 이해 하기 위한 사전준비일 뿐이다…(책에서)

1.         내포조인

2.         정렬병합

3.         해시조인

4.         세미조인

5.         카티전 조인

6.         아우터 조인

7.         인덱스 조인

엔코아 책에서는 저렇게 만 다룬다고 하니 더 있나보다

 

간단히 말하면 일단..

기본적으로 조인의 원리를 생각하려면 SQL 즉 코드로 인해 조인을 만들 것이고 그 조인을 구성하는 Table 즉 이 대상으로부터 data를 취득할 때 DBMS가 어떻게 데이터를 취합하고 어떻게 데이터를 저장하고 그 저장된 데이터에서 원하는 조건에 맞게 가져오는 지 궁금증을 가지는 데에서부터 시작한다.

 

즉 내부조인이나 외부조인 같은 방법을 사용해서 쿼리(질의)를 하고 그 결과는 DBMS에서 반환되며 디스크 or 메모리 상에서 어떠한 연산??(알고리즘) 에 의해 빠른 속도 or 느린 속도로 결과를 만들어주게 된다.

여기서 어떠한 연산?? 이 부분을 아는 것이 일단 지금 단계서의 목표이다.

기본적으로 생각 해야 하는 것은 적어도 우리는 최대한의 정규테이블로 구성된 DB에서 작업이 일어날 것이며 그렇기 때문에 정규테이블간의 관계에 따라 필요한 데이터를 얻기 위해 조인을 하게 되고

그 때 그 테이블을 필터하는 범위나 컬럼의 선택도나 사용하고 있는 인덱스에 의해 쿼리의 성능이 정해진다

 

자 그렇다면 1.내포조인 2. 정렬병합 3. 해시조인에 대해 간략히 언급하고 넘어간다

 

일단 기본은 내포조인이다. 즉 중첩된 루프를 돌면서 Outer table에서 Inner Table로 조건이 일치되는 data를 찾는 것이 가장 보편적으로 사용되는 조인이다

여기서 기본적으로 사용되는 자료구조는 B+Tree 이며 Root 부터 Branch를 지나 Leaf를 찾는 것이 알고리즘의 핵심이다

이 알고리즘을 사용할 때 정렬되냐 정렬이 안되냐에 따른 차이는 있지만 기본적으로 NL 조인은 랜덤엑세스를 해서 찾아간다 그래서 (Index)Table Scan을 하면 안 되고 Index Seek or Range Scan을 해서 범위로 접근해야 기본적인 성능이 좋다고 본다

그 한번의 랜덤엑세스가 중첩된 Roof를 돌면서 반복되므로 Scan이 아닌 Seek or Range를 가지는 부분이 성능의 핵심이다.

Sort Merge의 경우는 위의 Case에 정렬된 자료구조를 가지고 있다고 생각하면 된다 즉 내부적으로 정렬 프로세스를 거치니 초기 작업은 오래 걸려도 실제 찾는 프로세스는 적게 걸릴 수 있다 NL의 단점을 극복하기 위해 생겼다고 한다

Hash 의 경우는 Sort Merge가 정렬프로세스를 가지고 있다면 단점을 극복하려고 생겼다고 한다 즉 이 부분은 알고리즘적으로 접근해야 하는데 O(logn)도 충분히 훌륭한 것이지만 해시 맵을 만들면 해시 알고리즘은 O(1)이기 때문에 정렬을 하지않아고 검색이 가능하다고 한다 단 해시결과가 유일한 지 검증하는 부분이 추가되므로 꼭 O(1)은 아니라고 한다 어쨌든 알고리즘 적으로 해시 맵을 만들어서 검색하는 것이 더 탁월해 사용하는 조인방법이다.

 

즉 조인은 기본적으로 복수의 대상간 중첩된 루프를 돌며 조건이 맞는 경우를 찾는 것이고 NL의 경우 B+Tree 구조상에 랜덤엑세스의 부담 때문에 그 부담을 줄이고자 정렬된 상태에서 엑세스를 하는 Sort Merge 조인이 생겨났고 정렬을 미리 해야 하는 전처리에 대한 부담으로 아예 유일한 해시값을 생성해서 그 해시를 이용해 조인을 하는 것이 해시 조인이다라고 까지 생각하고 더 붙이자.

 

자 여기서 중요한 것은 인덱스 설계를 잘하는 것이다 인덱스에 따라 옵티마이저가 보다 좋은 엑세스경로를 찾을 수있게하는 근본적인 방법이다.

인덱스는 현재 발생할 수 있는 상황과 데이터의 분포도나 결합도 선택도등을 고려해서 설계되어야 한다

 

아래 내용은 트랜잭션 2탄이다. 

다만 코드로 나타내보고 싶은에 일단 글로 남겨본다 구현의 문제는 좀 더 나중에 고민 


관련된 책은 계속 읽어 봐야 겠다 

지금 총 5권이 있는데 난이도로는 


초급

Head First SQL 


중급 이상

SQL Server(개발편)

SQL 전문가 가이드 

대용량 데이터베이스 솔루션 Vol1(엔코아)

웹 프로그래머를 위한 데이터베이스를 지탱하는 기술 


이렇게 다 봐야 할 것 같다 


지금 정리할 내용은 중첩 트랜잭션 즉 트랜잭션이 이중 For문 이상 처럼 중첩되서 시작될 때 과연 트랜잭션 개수나 명시적 혹은 묵시적 이나 Auto Commit이면 어떻게 되는 것인가에 대한 고민이다. 


일단 몇가지 주의?? 사항이 있다 


1. 트랜잭션 선언 방법 

명시적

묵시적(오라클 기본)

Auto(SQL Server 기본)


2. 트랜잭션의 관리주체

로컬

분산(or Global)


3 중첩 영역 (트랜잭션 허용범위?)

Code(.Net)

Stored Procedure(DBMS)


총 3가지 요소로 나누고 사용하는 방법을 생각해보자 

현재 내가 다루는 부분에서는 어플리케이션 계층 즉 Biz 영역을 가지고 있다 그래서 기본은 Biz에서 트랜잭션을 처리하는 것이 기본이다. 


이걸 가지고 가설(또는 주장)을 세우자 

가설 : app에 Biz영역이 있으면 어플리케이션 계층에서 트랜잭션 처리를 한다!

라는 가설을 세우고 그것에 대한 타당성 검증을 하자 


그러면 고려되는 부분이 Code에서 트랜잭션 처리를 해야 한다는 것이다. 이것에 대한 기술은 .Net의 TrasactionScope를 사용하거나 IDbTransaction을 사용해서 트랜잭션 단위를 묶어서 처리할 수 있다 

기술적으로는 문제가 없다 


그렇다면 Biz영역을 왜 만들었나에 대한 질문을 할 수 있는데 나의 답은

DAO(Database Access Object)를 통해 DB를 변경하기 전에 로직에 따라 데이터를 수정하고 원하는 타입(예를 들면 Table Value Parameter)와 같이 변경해서 사용하거나 필요에 의한 가공을 하고 여러 Table(Entity)애 접근 할 수 있으니 그건 업무에 따라 달라지는 부분을 묶어서 업무 로직을 트랜잭션으로 처리하기 위해서 라고 길게 말하고 DB에 반영하기 이전에 데이터나 모델의 수정을 한 곳에서 일괄적인 작업단위로 분리하기 위해서 라고 간단히 말 할 수 있다 


그러면 SP에서 트랜잭션이 걸리면 어떻게 할거냐?? 라는 반론이 생길 수 있는데 

이때 중첩 트랜잭션이 생기고 그것에 따라 트랜잭션 개수가 복수로 생길 수 있다 

즉 Code에서 Begin Tran을 하면 하나의 트랜잭션이지만 SP에서도 Begin Tran을 하면 2개이상의 트랜잭션이 생긴다는 것이다. 

이렇게 되면 2번의 Commit이나 Rollback에 의한 초기화가 되야 짝이 맞아진다 

Scope가 중요해지는 부분이 생기는데 이렇게 되면 Code나 SP에서 둘 다 관리포인트가 생기니 일련의 과정을 모두 확인해봐야 하는 번거로움이 생긴다 


이때 유효하게 사용할 수 있는 부분이 TM이다 즉 트랜잭션 매니저가 있어서 Commit과 Rollback을 제어해 줄 수 있고 관리를 위임할 수 있으면 개발자 입장에서는 편해진다. 

그러니 Code에서 트랜잭션 처리를 할거면 TM역할을 하는 TransactionScope나 IDbTransaction을 이용해서 Work 단위를 만들면 중첩된 부분을 커버해주지는 않더라도 한 곳에서 트랜잭션을 시작하고 끝낼 수 있다 


한 곳에서 트랜잭션을 시작하고 끝낸다는 것에 대한 고찰 

사실 답은 억지로 끌어낸 감이 없잖아 있지만 중첩트랜잭션이 필요한가?? 에 대한 경우가 일관되게 적용하기가 아직 경험이 없다 그렇게 되니 일단 한곳에서 집중하고 필요한 경우 SP에서 중첩되게 사용할 수 있다고 말하고 싶다 

트랜잭션은 성능과 연관될 수 있다 트랜잭션이 길어지면 선행 처리가 길어진다는 것이고 그러면 후행처리는 그만 큼 느려지니 타임아웃에 의한 실패나 시스템 전체에 Sleep이 걸린 것 처럼 처리속도에 영향을 줄 수 있다 

그렇기 때문에 트랜잭션은 짧게 가져가되 성능감소는 최소화 하고 처리하는 Data는 무결성을 지켜주는 그런 식의 보완을 개발자가 제어해줘야 한다고 생각한다. 


간단히 의사 코드로 표현 해 보면 

TransactionScope Start 

{

Begin Tran()                // Transation Count +1 증가


Excute SP(insert Model)


Commit()


Excute SP(insert Model)

{

// 아래 내용은 SP의 내용

// insert into Table (Columns) values (Model)

Insert(Model) 

}


위와 같이 하면 Transaction Count가 1인 것이고 

아래와 같이 SP에 트랜잭션을 다시 걸면


TransactionScope Start 

{

Begin Tran()                // Transation Count +1 증가


Excute SP(insert Model)


Commit()


Excute SP(insert Model)

{

// 아래 내용은 SP의 내용

// Begin Tran            // Transaction Count +1 증가 

//     if(@@Transaction Count = 2)

//        insert into Table (Columns) values (Model)

// Commit Tran

Insert(Model) 

}


위 구문은 트랜잭션 카운트가 2가 되어서 Table에 Insert가 된다 
물론 위의 코드가 정확하다고 할 수 없다 Test Code를 작성할 때 Transaction Count를 사용할 때 생각지 못한 error 가 발생 했었기 때문에 
(@@는 Count를 반환하는 함수정도로 생각하면 될 것 같다 : http://forums.codeguru.com/showthread.php?493155-What-does-mean-in-MS-Sql-Server)
Test Case를 모두 Code로 해보려는 것은 실패했다 

일단 Code에서 시작한 Begin Tran이 SP의 Script 에서 @@TranCount를 만났을 때 호환이 안되는 것 처럼 보인다. 위의 SP가 실행되려면 상단에 Rollback Tran을 넣어주면 0으로 초기화가 되어서 Transaction Count가 2가 되지 않아 위의 SP는 Insert가 되지 않는다 (Rollback은.. 사실 강제로 SP에서 TranCount를 세기 위한 꼼수 같은 것이다.)
아마도 Code와 SP 모두 Begin Tran이 발생했을 때 그것을 받아들이는 DBMS에서 그 차이를 인지하지 못해서 문제가 발생될 수 있다고 해석된다. 

2개 이상의 중첩 트랜잭션의 필요여부 
꼭 그런것은 아니지만 로직을 짤 때 이중For문 이상 삼중 사중의 For은 피해야 하는 권장사항을 들은 적이 있다 즉 필요하면 써먹어야 겠지만 중첩 구조가 복잡한 것을 일부러 할 필요는 없다 그렇다면 비슷한 형태의 트랜잭션도 굳이 로직 사이사이에 필수로 라도 부분 Commit으로 처리해야하는 경우가 있는 것이 아니라면 Data의 수정을 All or Nothing 으로 하면 될 것으로 보인다 
그러면 부분적으로 SP에서 Begin Tran을 하는 경우는 부분 Commit이 있는 경우에 한정지어서 생각할 수 있다 만약 처리해야하는 부분이 많은데 부분 실패가 났을 때 완전 Rollback이 아니라 부분적으로 반영하고 재시도에서는 기존 Commit을 거치지 않는 그런 식으로 Retry인 경우에 이점이 있는 로직에서 처리할 수 있다 아직 그런 Case를 해본적이 없어서 가정일 뿐이다. 

위의 상황은 로컬 트랜잭션에 대한 과정이라는 것
분산트랜잭션을 경험해 보지 못해서인지 막연한 점이 있지만 하나 기준을 잡자면 어느 Resource 대상인지와 물리적인 대상이 변경되는지 2가지로 압축하고자 한다 위의 Insert는 특정 DB의 Table이다 그러니 하나의 DB만 수정하므로 분산된 Resource를 건드리는 것이 아니라 그리고 물리적으로 여러개의 DB를 건드리거나 통신적으로 (예를 들면 TCP / HTTP 를 사용해서 물리적인 Server가 달리지거나) 달라지는 것이 없고 하나의 Connection으로 단일 DB를 처리하므로 이런 case에는 단일 지역 트랜잭션 즉 로컬 트랜잭션으로 본다 
분산 트랜잭션을 구현하게 되면 여러개의 다른 Resource DB나 물리적으로 다른 Server 혹은 App에서 통신 구간이 변경되어 각각의 Data가 제대로 요청과 응답이 있었는지의 확인 과정을 거쳐야 해서 새로운 개념의 Scope의 Begin Tran이나 Commit or Rollback으로 묶어서 처리하는 부분이 있는 경우로 고려해 볼 수 있다. 

관련 내용 링크
https://ko.wikipedia.org/wiki/%EB%B6%84%EC%82%B0_%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98
https://technet.microsoft.com/ko-kr/library/ms191440(v=sql.105).aspx
http://krids.tistory.com/96

즉 분산된 트랜잭션을 쓰는 경우는 하나의 작업에서 다른 Server 접근 다른 DB 접근과 같이 상태가 변하는 경우로 간단히 볼 수 있다 
나중에 2PC(begin -> end -> prepare  -> commit)도 검증해보도록 하자 글로벌 트랜잭션이 이루어지는 과정이라고 한다 

MS Tech 쪽 설명을 보다보면 아래와 같은 내용이 나오는데

분산 트랜잭션은 리소스 관리자라고 하는 둘 이상의 서버에 분산됩니다. 트랜잭션 관리는 트랜잭션 관리자라고 하는 서버 구성 요소에 의해 리소스 관리자 간에 조정되어야 합니다. MS DTC(SQL Server 데이터베이스 엔진은 Microsoft Distributed Transaction Coordinator) 등의 트랜잭션 관리자 또는 분산 트랜잭션 처리용 Open Group XA 사양을 지원하는 기타 트랜잭션 관리자에 의해 조정되는 분산 트랜잭션에서 리소스 관리자 역할을 합니다. 자세한 내용은 MS DTC 설명서를 참조하십시오.  

트랜잭션 관리자라는 용어가 나오는데 아마도 위의 TM이라고 한 TransactionScope나 IDbTransaction 과 연관되어 있을 것으로 보인다
위키 백과에서는 

일반적으로 시스템은 트랜잭션 리소스(transaction resource)의 역할을 하고, 트랜잭션 매니저(transaction manager)는 이러한 리소스에 관련된 모든 동작에 대해 트랜잭션의 생성 및 관리를 담당한다

이 시스템이란 말은 데이터베이스로 대입해도 될 듯 하다 그렇다면 데이터베이스라는 리소스에 접근하는 부분에서 데이터베이스 엔진이라거나 MS DTC 이런 녀석들이 트랜잭션을 관리할 수 있고 Code에서는 위의 TransactionScope나 IDbTransaction 를 통해 관리자의 역할을 수행한다고 보인다. 

왜 마지막에 트랜잭션 매니저가 나왔냐 하면 위의 3가지 트랜잭션 선언방법 중 

명시적

묵시적(오라클 기본)

Auto(SQL Server 기본)

Auto Commit 이라는 것이 있다 이 녀석이 나에게 혼란을 주었다 


하나의 의사코드로 예를 들자면 

Begin Tran 

insert A

insert B

insert C

Commit Tran


insert A (commit)

insert B (commit)

insert C (commit)


위 2개의 방식에서 차이는 명시적 트랜잭션과 Auto Commit이다. 
아래의 경우에서 Auto Commit이 이루어 질때는 트랜잭션 개수가 3개인가?? 의문이 생겨버렸다. 
물론 하나의 작업으로 생각하면 3번의 트랜잭션이 있었다고 볼 수 있고 각각 한줄을 지날 때마다 영구적인 data의 변화를 줬기 때문에 트랜잭션의 영구성 원칙에 따라 트랜잭션 3번으로 보인다. 
그러면 트랜잭션으로 묶을 때와 아닐 때에 대해 트랜잭션에 대해 혼란이 가중된다. 뭐 일일이 트랜잭션 개수를 고려하는 것이 중요한 것은 아니겠지만 auto commit이 일어나는 것에 대해 어떻게 대처할 것인가에 대해 고려해줘야 한다.
이 때 정확한 Count는 알 수 없었지만 MS DTC가 되었든 DBMS가 되었든 단순 insert가 여러번 발생하면 그 하나에도 트랜잭션이 Begin ~ Commit이 일어났다고 봐야 할 것 같다 다만 사용자가 제어할 수 있는 트랜잭션이 아니기 때문에 Count를 확인하기 전에 트랜잭션이 끝나버렸다 
SQL Profiler로 확인 해 본 결과 TM이 아닌 각각의 BEGIN TRAN ~ COMMIT TRAN으로 잡혔기 때문에 트랜잭션 3개로 보는 게 옳은 것으로 보인다. 
TM이 관여한 트랜잭션도 아닌 것으로 로그가 남았다 명시적으로 BEGIN TRAN ~ COMMIT TRAN 을 사용하지 않았는데 해당 구문을 추적하는 이벤트에 남은 것으로 보아 명시적으로 선언하지 않으면 DBMS에서 DML 구문 하나가 끝날 때 Auto COMMIT TRAN을 만들어 주는 것으로 예상된다 

어쨌든 그렇다면 트랜잭션 개수는 카운팅되어야 하는 것으로 보인다. 

자 마지막 결론을 내린다. 
위의 일련의 과정은 결국 트랜잭션 처리는 어플리케이션에서 Biz 계층이 있다는 경우면 단순하게 DAO 영역에서 데이터베이스에 접근해서 CRUD를 하는 것이 아니라 필요한 로직처리가 있는 것이고 그 로직에서 Code에서 트랜잭션을 처리하기 때문에 기술적으로 TransactionScope나 IDbTransaction 을 통한 트랜잭션 매니저를 이용하게 되고 그 트랜잭션 매니저에서는 일단 하나의 트랜잭션 Count를 만들고 내부에서 호출한 SP에 BEGIN TRAN이 있으면 또 새로운 트랜잭션 Count가 생기는 것이다. 다만 이렇게 사용해야 하는 경우가 있다면 중첩된 For문을 쓰는 것과 같이 중첩된 트랜잭션이기 때문에 가급적 부분처리를 해야하는 것이 아니면 Code에서 일관되게 트랜잭션의 원자성과 격리성을 지켜주어서 All or Nothing 을 지켜준다
그러니 Biz를 어떻게 나누냐에 따라 트랜잭션을 고려한 개발을 하는 것이라고 할 수 있다는 것이다 
그리고 이것은 필수 사항은 아닌 것이다 왜냐하면 Code에서 하지 않으면 SP에서 BEGIN TRAN ~ COMMIT TRAN or ROLLBACK TRAN을 하는 경우이면 SP에게 트랜잭션 처리를 위임하게 되므로 DB에서 SP 내부처리로 할지 Code에서 Biz로 처리하는 것을 구분할 줄 알고 Biz에서 처리할 때는 일관된 DB Connection이 유지되어서 DB로의 접근을 가장 단순하게 해야 하는 이유가 생기는 것이다. 헷갈리지 말자 

만약 Biz 가 없고 정말 단순히 DB의 data를 변경하는 경우라면 SP에서 트랜잭션을 시작해서 BEGIN TRAN ~ COMMIT TRAN or ROLLBACK TRAN 로 처리 할 수도 있다는 것이다. 
그리고 트랜잭션 매니저의 내부처리에 대해서 궁금할 수도 있지만 단순히 각각의 SP를 큐에 모아놨다가 Commit이 일어날 때 큐에 모인 SP 전체를 실행해서 비우는 것처럼 은유적으로만 생각하고 깊게 생각하지 말자 트랜잭션 매니저는 단지 이용하는 수단일 뿐이다

트랜잭션 카운트 계산 법

Begin Tran 

insert A

insert B

insert C

Commit Tran

= 1개

insert A (commit)

insert B (commit)

insert C (commit)

= 3개 

TransactionScope.BeginTran
Excute SP
TransactionScope.Commit 
= 1개

TransactionScope.BeginTran
Excute SP A
SP.BeginTran
Insert data a
SP.Commit
TransactionScope.Commit 
= 2개

TransactionScope.BeginTran
Excute SP A
SP.BeginTran
Insert data a
SP.Commit
SP.BeginTran
Insert data b
SP.Commit
Excute SP A
SP.BeginTran
Insert data c
SP.Commit
Excute SP A
SP.BeginTran
Insert data d
SP.Commit
TransactionScope.Commit 
= 1 + (1 * 2) + (1 * 1) + (1 * 1)  = 5개 


트랜잭션 간단 정리 


그냥 간단하게 남기려고 쓴다 

주 내용은 서비스개발실 SQL Server 책이랑 SQL 전문가 가이드 내용을 보고 남긴다. Head First SQL 보고 추가할 내용이 있으면 수정한다.  


트랜잭션이란? 

- 처리하는 작업의 논리적인 처리 단위

- 작업 단위는 그것을 구성하는 세부적인 작업의 연산(더하기 빼기 등등)들의 집합

-  데이터베이스 응용 프로그램을 트랜잭션 들의 집합으로도 볼 수있다고 한다 (기능 하나하나가 트랜잭션이라고 볼 때)


트랜잭션을 이해해야 하는 이유

- 불필요한 잠금을 피하고 잠금(Lock)을 원천적으로 피할 수 없지만 최대한으로 관리하기 위해서 


트랜잭션의 범위

- 트랜잭션 하나에 데이터 처리작업(입력, 수정, 삭제) 하나가 포함될 수도 있고 처리작업 여러개가 하나의 묶음으로 포함될 수 있다


All of Nothing 

- 말 그대로 여러데이터를 변경할 때 변경 처리가 모두 완료되거나 아니면 일부가 처리되지 못하면 이미 처리된 작업마저 취소해서 전부 되거나 전부 안되거나 그런 말이다. 


4대 속성 

- 원자성 : All of nothing

- 일관성 : 트랜잭션이 완료된 데이터는 일관되게 유지되어야 하는 것

- 격리성 : 동시에 트랜잭션이 실행될 때 서로간에 간섭하지 않는 것 

- 영속성 : 트랜잭션이 완료된 데이터베이스 변경사항이 영구적으로 저장되는 것


TCL(Transaction control language) 

- Commit : 올바르게 반경된 데이터를 데이터베이스에 반영시키는 것

- Rollback : 트랜잭션 시작 이전의 상태로 되돌리는 것

- Savepoint : 데이터 변경을 사전에 지정한 저장점 까지만 롤백하라는 것 


트랜잭션의 대상 

- UPDATE, INSERT, DELETE등 데이터를 수정하는 DLM문

- SELECT 문은 직접적인 트랜잭션의 대상이 아니지만 SELECT FOR UPDATE 등 배타적 LOCK을 요구하는 SELECT 문장은 트랜잭션의 대상이 된다고 한다

  --> 나중에 Lock 정리할 때 내용 추가


Commit이나 Rollback 이전의 데이터 상태 

- 메모리 Buffer에만 저장되어 있어서 데이터의 변경 이전 상태로 복구 가능

- 현재 사용자가 SELECT 문장으로 결과를확인 가능 (이런건 테스트 해보자)

- 다른 사용자는 현재 사용자가 수행한 명령의 결과를 볼 수 없다 (Lock?)

- 변경된 행은 잠금(Lock)이 설정되어서 다른 사용자가 변경할 수 없다. 


Commit 이후의 데이터 상태 

- 데이터에 대한 변경 사항이 데이터베디읏에 반영된다

- 이전 데이터는 영원히 잃어버린다. 

- 모든 사용자는 결과를 볼 수 있다 

- 관련된 행에 대한 잠금(Lock)이 풀리고 다른 사용자들이 행을 조작할 수 있게 된다


Auto Commit (이놈은 트랜잭션이냐... 아니냐...)

- SQL Server 기본값 

- DDL, DML 수행시 DBMS가 트랜잭션을 컨트롤하는 방식 명령어가 성공적으로 수행되면 Auto Commit이 되고 오류가 발생하면 Rollback을 자동으로 수행


암시적 트랜잭션

- Oracle 기본 값

- 트랜잭션의 시작은 DBMS가 처리하고 트랜잭션의 끝은 사용자가 명시적으로 Commit 또는 Rollback으로 처리하는 것

- 인스턴스(데이터베이스 자체 연결??)나 세션 단위(사용자 단위????)로도 설정할 수 있다????


명시적 트랜잭션

- 트랜잭션의 시작과 끝을 사용자가 명령어를 써서 지정하는 방식 

- BEGIN TRAN ~~~~ COMMIT or ROLLBACK 


Commit과 Rollback 으로 얻는 효과 

- 데이터 무결성 보장 (명령이 실행되어야만 반영되고 트랜잭션의 고립성 특성으로 데이터 접근이 제어되기 때문)

- 영구적인 변경을 하기 전에 데이터의 변경사항 확인 가능(이건 현재 사용자만 가능할 듯 다른 사용자는 안 됨)

- 논리적으로 연관된 작업을 그룹핑하여 처리 가능 (작업 내부의 세부적인 연산을 처리한다는 말과 같은 말)


트랜잭션 주의사항 

- Create, Alter, Drop, Rename, Truncate 등 DDL 문장을 실행하면 그 전후 시점에 자동으로 커밋된다

- DML 이후에 DDL이 수행되면 DDL 수행전에 자동으로 커밋된다는 말이다 

- 데이터베이스를 정상적으로 접속을 종료시키면 자동으로 트랜잭션이 커밋된다.

- 어플리케이션의 이상 종료로 데이터베이스와의 접속이 단절되면 트랜잭션이 자동으로 롤백된다 (진짜????? connection이 어떻게 관리되는데???)


트랜잭션의 개수 

- 트랜잭션 카운트라는 것이 있다 MS SQL에서는 @@TRANCOUNT 라는 것이 있어서 현재 BEGIN TRAN이 몇개나 있는지 알 수있다 

예를들면 중첩 트랜잭션을 보면 이해가 쉽다 

BEGIN TRY

             BEGIN TRAN

                           SELECT @@TRANCOUNT  -- 1

                           BEGIN TRAN

                                        SELECT @@TRANCOUNT -- 2

                                        SELECT TOP 1 * FROM sys.sysobjects

                           ROLLBACK TRAN

                           SELECT @@TRANCOUNT -- 0

             COMMIT TRAN  -- error

             SELECT @@TRANCOUNT -- 0

END TRY

BEGIN CATCH 


END CATCH

출처: http://ddoung2.tistory.com/178 [DDoung2]


뭐 저렇다는 것인데 저건 예시고 실제로는 .NET의 C# 코드에서 한번 DBMS가 실행하는 SP에서 한번이상 이런 식으로 BEGIN TRAN을 할 수 있다.

아래 훈스닷넷 커뮤니티에 예시가 잘되어 있는데 

http://ucclight.hoons.net/Board/qacshap/Content/97132


여기서 보면 C# 코드에서는 TransactionScope로 트랜잭션을 시작했고 SP안에도 Begin Tran이 있다 즉, TRANCOUNT가 2가 되는 것인데 

위의 중첩 트랜잭션 예시는 Try로 감싸서 오류가 나도 예외처리가 된다 Try를 제거하면 두 번의 트랜잭션이 시작되고 Rollback이 되면서 TRANCOUNT가 0이 되고 나서 다시 COMMIT 을 하게 되니 트랜잭션 처리할 것이 없는데 처리하라고 해서 생기는 오류가 난다


훈스닷넷의 예시를 보면 Code에서 시작한 트랜잭션이 SP 스크립트 안에서도 트랜잭션이 동작하면서 관리가 양쪽에서 다하니 힘들어지는?? Code, SP 양쪽에서 관리 한다니 ㅠㅠ 어쨌든 error가 나는 case이고 이것에 대한 트랜잭션 개수는 좀 더 정확히 파악해서 테스트를 하도록 한다 

또 한가지... Auto Commit이 일어 날 때 뭐가 달라지는 지 확인해 보자.. 어렵다 Auto Commit은 개수를 무시해야 하나???


어쨌든 훈스닷넷 예시에서는 기본적으로 트랜적션은 프로그램으로 하실꺼면 프로그램에서만 프로시저에서 할꺼면 프로시저에서만 하시는걸 추천드립니다. 

이렇게 말 하고 있다 그리고

개인적으론 특별한 경우가 아니면 프로시져에서만 합니다.  한쪽에서 하지 않으면 논리적인 오류가 날 가능성이 매우 큽니다.그리고 추적역시 쉽지 않지요. 그리고 프로그램에서 하실꺼면 .net2.0 이상이면 TransactionScope로 싸시는걸 추천드립니다.

이렇게 자신의 추천하는 방법도 제시해 주고 있다. 


약간 정답이 없는 문제이다. 모든 트랜잭션을 SP내부에 위임할 수도 있고 코드로 제어하는 것이 좋아서 .NET에서 TransactionScope나 ITransaction 인터페이스를 사용해 구현한 UnitOfWork(라이브러리 아님 커넥션 하나에서 트랜잭션을 처리하는 패턴 같음(회사에서 사용)) 에서 트랜잭션 관리를 위임할 수 있는 선택의 문제가 같이 있다 

어짜피 코드가 좋을지 SP에서가 좋을지 판단할 만한 경험이나 통찰력이 부족하다 일단 양쪽에 다 Transaction 관리가 된다는 것만 이해하고 나중에 개념을 업그레이드 시켜보자.



기본적인 내용은 여기까지!!!

 

Entity Framework

 

- 추억의 Entity Framewok이다. 이 내용을 보다가 좀 화가 났다.

  사실 C#을 처음 접하게 된 계기가 ASP.NET MVC3 이다.  ActiveX(ocx) 환경의 웹 화면을 ASP.NET MVC3 로 변경하게 되면서 보게 되었는데 문제는 이걸 하기로 한 C# 개발자 과장님이 실패하고 쫓겨나면서 이제 일한지 6개월도 안된 나에게 넘어 온 프레임워크이다.

 젠장 C#도 처음보고 DB도 모르고 ORM은 기억이 안나는 거 보니 검색도 못했던 것 같고 웹 개발이랑 console 개발이랑 차이도 모르니 당연히 프론트 쪽의 javascript나 html 도 잘 모르는데 다 다루고 앉아 있고 힘든건 이게 메인업무도 아닌데 매일마다 어디까지 했냐고 압박받으면서 ㅠㅠ 거의 10년차도 포기한 걸 억지도 떠맡아서 개고생하면서 XXXXXXXXXXXXXXXXXXXXXXXXXXX 어쨌든 웹 페이지 호출이랑 CRUD 기능 동작하는 웹 어플리케이션 개발한 것을 시연하고 풀려났던 기억이 있다.. 내가 틈틈히 주말 야간으로 2달간 고생한 걸 경력직 과장님이 와서 1주일도 안되서 해결한 걸 보고 자괴감은.. 플러스..

 이번에는 Code-First 에 대한 내용을 보다가 여기까지 올라왔는데 갑자기 첫 회사 신입때가 생각나서 울컥하는 마음에 남기게 되었다.... 결국은 내가 완성할 수 있는 솔루션 규모가 아니었는데 왜 그런식으로 시험을 받아야 했었는지..모르겠다 문제 해결 능력만으로 개발을 할 수 있는(최대한 좋게 말해서이다.) 것도 장점이 있었겠지만.. 단점이 너무 많았던 것도 같다 집에와서 해야 하니 머릿속에 온통 How에 대한 생각밖에 없었으니..

 그냥.. 잊자.. 어짜피 퇴사전이나 퇴사 이후에 충분히 내 몫을 해내고 내 스스로 다 했다는 말도 들었으니.. 그것으로 만족하자 다른 회사 면접날 전에 술먹인 회사는 여기밖에 없다  

 

간단히 남기려는 것은 2가지 이다.

 1. Entity Framework에서 Code-First로 Model을 생성하는 것에 대한

 2. Code-First Model에서 다른 Table과의 관계에 대한

내용을 간단히 남기고자 한다

 

일단 위에랑 직접적인 관련은 아니지만 위로 개념을 올린 김에 ORM 을 보자

C#과 같은 객체 지향형 프로그래밍 언어에서 데이터베이스를 쉽게 사용하기 위한 도구라고 한 줄로 말할 수 있다

OOP개념의 객체(Object)와 관계(Relation)형 DB의 테이블을 맵핑(Mapping)해서 (native)SQL 작성을 안하고 쉽게 DB의 데이터에 Access 할 수 있는 기술이라고도 할 수 있다

여기서 나오는 3개의 단어 Object / Relation / Mapping 이 것이 ORM에서 쓰는 핵심 단어이다. ASP.NET 에서는 데이터 엑세스하는 기본 프레임워크로 Entity Framework를 쓰기 때문에 기본값 처럼 사용하는 것이다. MFC를 했을 때 DB 엑세스 하는 기술은 OLE DB를 사용 했기 때문에 그런 기술로 생각하면 된다.

 

Entity Framwork 모델

크게 3가지가 있다고 한다

1. Code First

2. Model First

3. Database First   

Model First와 Database First 접근 모델은 Visual Studio 의 Visual Model Designer (EDMX) 를 통해 객체/테이블 매핑을 디자인 하는 방식으로 두 개간 차이점은 Database Frist는 기존 DB로 부터 테이블 구조를 읽어와서 디자이너를 통해 Visual Model로 구성되는 것을 말하고 Model First는 기존 DB가 없을 때 직접 Model Designer를 써서 Entity들을 추가해 가면서 모델을 구성하는 방식이다. 위 두가지는 Visual Model Designer로 디자인한 것을 edmx 파일에 저장하게 된다.

Code First 방식은 Model Designer / EDMX를 사용하지 않고 데이터 모델을 C# 클래스로 직접 코딩하는 방식으로 앞으로의 EF는 Code First 방식만을 지원한다고 한다. (http://www.csharpstudy.com/web/article/8-Entity-Framework)

 

Code First

C# 클래스로 테이블의 구조를 정의하고 클래스의 프로퍼티를 테이블 컬럼으로 맵핑한다.

Code First란 말 그대로 DB를 미리 설계하지 않고 C# 클래스로 Domain Object(Model???) 를 정의하고 프로그램 실행 시 DB가 없으면 자동으로 DB를 생성하는 방식을 취한다

-> 이 말은 코드 우선이니까 테이블 모델이 Code 기반에서 생성되고 DB에 대한 생성과 추가 수정이 Code 기반의 Class Model에서 된 다는 것을 말한다

     MS SQL의 management studio를 통해 추가/수정/설정을 하는 것이 아니라 code로 구현해서 DB 관리자가 설정하는 것 처럼 추가하고 변경 사항을

    할 수 있다는 것을 말한다

 

재밌는 부분은 Table 과 맵핑되는([Table("name")] 이런 형태의 단순한 Entity Class를 POCO(Plain Old CLR Obect) 라고 부른다고 한다. 기본적인 단순히 테이터를 저장하고 담아저 전달되는 모델을 말하는데 자바에서는 POJO라는 것이 있었다 (Plain Old Java Object) 두 개가 같은 개념으로 보인다. 제일 기본이 되는 Model 객체?? (비즈니스가 딱히 없는??) 이라고 생각하면 쉬울 것 같다

 

Code First를 활용하는 부분을 보다보면 크게 3가지가 나온다

1. DbContext

2. Fluent API

3. Data Annotation

위 내용을 깊게 들어가면 EF로 DB Table 추가하고 컬럼 변경 및 키지정 관계 지정 까지 프레임워크 사용법 까지 다루게 되어서 저런게 있다는 것만 간략히 말한다. DbContenxt는 간단히 System.Data.Entity의 클래스 이며 DB와 관련된 여러 API를 사용할 수 있다 Context란 말에서 뭔가 저장소Repository 란 단어가 생각나는데 Domain Classes 와 Database의 중간에 있는 녀석으로 생각하면 될 것 같다 간단히 그림으로

 

http://www.entityframeworktutorial.net/entityframework6/dbcontext.aspx 참고하자

 

DbContext가 DB 전체를 관리할 수 있는 API를 제공하는 느낌이라면 Fluent API는 Table에 대한 설정을 할 수있다 테이블 간의 관계나 키 설정과 관련된 매서드들을 사용할 수 있다 Data Annotation은 C#의 Attribute로 컬럼의 값에 대한 Data Validation이나 컬럼의 속성(기본키(Key, notMapped))에 대해 프로터티 바로 위에 지정할 수 있다

즉 DB 제어 / Table 제어 같은 것이지 않겠나

 

마지막으로

Code-First Model에서 다른 Table과의 관계에 대한 표현은 크게 3가지가 있다

1. 1:1 관계

2. 1:N 관계

3. N:M 관계

아마도 관계형 데이터베이스라는 개념에서 관계라는 것이 핵심일 듯 하다 이놈의 관계에서 정규화도 고려되고 인덱스 키나 Join 할 테이블도 고려된다 그냥 테이블 간의 관계라고만 생각하기보다는 Object 간의 연관된 관계로 생각해야 그 객체 안의 속성이 어떤 특성이며 이 특성은 어떤 요소들의 집합 모임인지까지 생각할 수 있을 것 같다

(수학에서 집합/확률통계/방정식(변수를 통한 식만들기) 는 체감적으로 많이 나오는 것 같다 (선형대수학이나 행렬은 아직 뭔가 더 해봐야 알 것 같다)

 

관계는 코드로 보는게 편하다 (https://www.c-sharpcorner.com/UploadFile/3d39b4/relationship-in-entity-framework-using-code-first-approach-w/)

일대일 관계

  1. namespace EF.Core.Data  
  2. {  
  3.    public class User : BaseEntity  
  4.     {  
  5.         public string UserName { getset; }  
  6.         public string Email { getset; }  
  7.         public string Password { getset; }  
  8.         public UserProfile UserProfile { getset; }  
  9.     }  
  10. }  

 

  1. namespace EF.Core.Data  
  2. {  
  3.    public class UserProfile : BaseEntity   
  4.     {  
  5.         public string FirstName { getset; }  
  6.         public string LastName { getset; }  
  7.         public string Address { getset; }  
  8.         public virtual User User { getset; }  
  9.     }  
  10.  

 

두 클래스간 virtual 속성으로 각각의 class 간 연결을 만들었다 즉 연결되어 있는 다른 테이블의 속성을 알 수 있다

 

일대다 관계

  1. using System.Collections.Generic;  
  2.   
  3. namespace EF.Core.Data  
  4. {  
  5.   public  class Customer : BaseEntity   
  6.     {  
  7.       public string Name { getset; }  
  8.       public string Email { getset; }  
  9.       public virtual ICollection<Order> Orders { getset; }  
  10.     }  
  11.  

 

  1. using System;  
  2.   
  3. namespace EF.Core.Data  
  4. {  
  5.     public class Order : BaseEntity  
  6.     {  
  7.         public byte Quanatity { getset; }  
  8.         public Decimal Price { getset; }  
  9.         public Int64 CustomerId { getset; }  
  10.         public virtual Customer Customer { getset; }  
  11.     }  
  12.  

 

virtual로 선언된 객체를 보면 한쪽이 Collection이다 Code First로 생성한 Entity는 HashSet(ICollection 상속된 클래스)으로 자료형이 되어 있는데 어쨌든 .Net 적으로 얘기하면 Collection 객체를 포함하고 있다는 것이다. 그러니 단순 List도 포함된다

하나의 객체가 다수의 다른 객체를 가지고 있으니 1:N 관계가 성립되는 것이고 부모하나에서 파생된 자식들이라고도 표현이 되고 Has 관계로 연관된 Object / Entity / Table 라는 개념을 포함할 수 있다

(OOP는 진짜 용어들만 이해해도... (개념적/논리적/물리적) 이런 구분인건가? 누군가에게 설명하기도 어렵고 하고 싶지 않는 부분이다...  

어쨌든 일대다 관계는 하나의 객체가 다수의 객체를 포함한다.. 이렇게 정의할 수 있다

 

다대다 관계

  1. using System.Collections.Generic;  
  2.   
  3. namespace EF.Core.Data  
  4. {  
  5.     public class Student : BaseEntity  
  6.     {  
  7.         public string Name { getset; }  
  8.         public byte Age { getset; }  
  9.         public bool IsCurrent { getset; }  
  10.         public virtual ICollection<Course> Courses { getset; }  
  11.     }  
  12.  
  1. using System;  
  2. using System.Collections.Generic;  
  3.   
  4. namespace EF.Core.Data  
  5. {  
  6.    public class Course : BaseEntity  
  7.     {  
  8.        public string Name { getset; }  
  9.        public Int64 MaximumStrength { getset; }  
  10.        public virtual ICollection<Student> Students { getset; }  
  11.     }  
  12.  

 

위의 일대다를 보고 다대다를 보면 매우 간단히 답이 나온다 각각 서로에 대한 Collection 객체를 가지고 있으면 다대다 관계이다.

여기서 포인트랄 하나 잡자면 다대대 관계를 그대로 쓰지 않는다 두개 간의 관계를 연결해주는 연결테이블 or 맵핑테이블 or 조인테이블 이런 단어로 사용되는 테이블이 가운데 있다 간단히 생각하면 다대다 관계를 다이렉트로 연결하는 것이 아니라 중간에 맵핑테이블을 두어서 일대다 관계 두 개로 쪼개는 것이다. 그러면 EF의 Database First에서 EDMX 파일에서는 맵핑 Class가 생기고 그 맵핑 정보를 각각 가지고 있는다 위의 코드는 관계만 정의된 것이고 맵핑 Entity에 대한 포함 객체는 없는 것이다. 여기서 DB의 관계와 객체인 Class간에 차이가 생길 수 있는데 (회사 업무에서 발견)

간단히 생각하면 객체에 대한 역정규화 기법?? 이라고 보인다

 

맵핑 객체를 EF가 만들어 준대로 바로 쓰는 것이 아니라 Business Model로 변경 시 해당 컬럼을 프로퍼티의 일부로 포함 할 수도 있고 이미 관계테이블에 포함한 프로퍼티일 경우 중복된 프로퍼티(컬럼)을 쓰지 않아도 되는 것이다.

물론 Entity 형태의 Model을 그대로 쓰는 것이 아니라 모델이 Customizing 되기 때문에 모델이 합쳐지거나 Colection이 포함되는 관계를 맺을 때 중복된 data를 제거하거나 1:N 으로 변경된 Class 내부에 포함하거나 해서 다른 class의 속성을 가져 올 수 있는 것이다.

 

DB에서 중복을 만들거나 두 개의 Entity를 합치는 경우 보통 역정규화라고 하는 것 처럼

Class에서도 맵핑 Class를 포함 하거나 Class를 해체해서 관련 Class의 프로퍼티로 포함 시키는 부분이 모델의 역정규화 인 것 처럼 보인다.

물론 Business Model / Customizing Model에서 쓰이는 부분이다.

 

예제로 만들면 좋겠지만... 개념적인 부분에 시간이 너무 들어가서 말로 압축한다.. 길어야 하루면 될게 몇시간 초과되었다... ㅠㅠ

기본 개념은 1:N 관계 2개의 Class가 생기고 그 Mapping Entity도 Class로 생성이 되나 Business Model을 만들면서 중복을 합치거나 Class를 해체해서 관계 Class에서 자기 속성으로 바로 접근하게 만드는 건 그때그때 다르다고 생각하면 된다. 

SQL JOIN에 대해 집합으로 잘 표현된 자료가 있다 


출처 : http://rapapa.net/?p=311




기본적인 JOIN문의 형태는 


SELECT column1, column2, column3..... 

FROM TableA A

(LEFT / RIGHT / FULL / INNER) JOIN TableB B

ON A.Key = B.Key


LEFT / RIGHT / FULL 은 OUTER Keyword를 써서 나타낼 수도 있다 (안 써도 결과는 같다)


위의 집합관계 그림에서 

- 차집합 Set(A-B) or Set(B-A)

- Set(U) - Set(A∩B) 

과 같은 경우 조건을 추가해서 나타내는 방법이 있다 


WHERE 절을 추가해서 반대 방향의 테이블의 Key(맵핑되는 컬럼) 가 IS NULL일 때 순수하게 한쪽에만 속하는 원소(Row)를 가져온다

아래 예제를 보자 


예제 data는 Oracle XE 버전을 설치하면 제공되는 기본 Scott DB를 이용했다 

ex)


SELECT * 

FROM EMP A

RIGHT OUTER JOIN DEPT B

ON A.DEPTNO = B.DEPTNO






SELECT * 

FROM EMP A

RIGHT OUTER JOIN DEPT B

ON A.DEPTNO = B.DEPTNO

WHERE A.DEPTNO IS NULL





차이가 느껴지는가?? 


JOIN의 결과는 INNER 처럼 Key가 같은 내용을 찾아주지만 LEFT나 RIGHT와 같이 쓰이면 한쪽에만 존재하는 data를 포함하게 된다 

그 반대방향의 Key가 Null인 경우만 필터링하면 순수한 A or B Table의 data를 가져올 수 있다 


교집합에 속하는 data를 가져와야 하는지 

차집합에 속하는 data를 가져와야 하는지 


그것에 따라 RIGHT 나 LEFT 또는 INNER를 잘 쓰도록 하자

+ Recent posts