public static boolean dateCheck(String checkDate) {
  SimpleDateFormat dateFormatParser = new SimpleDateFormat("yyyyMMdd", Locale.KOREA);

  dateFormatParser.setLenient(false);
  try {
      dateFormatParser.parse(checkDate);
      return true;
  } catch (ParseException e) {
      return false;
  }
}

- checkDate가 yyyyMMdd 포멧에 부합하는지 체크

- dateFormatParser.setLenient(false); : false이면 엄격한 체크

- checkDate가 yyyyMMdd에 맞으면 true, 아니면 예외발생

 

- 해당 내용을 파일 파싱 배치에 추가하여 파싱할 파일에서 날짜값이 유효한 값인지 확인하기 위해 사용함

'JAVA > java' 카테고리의 다른 글

Comparator, Comparable  (0) 2021.02.28
mutable과 immutable  (0) 2021.02.25
toString()  (0) 2020.04.20
hashCode()  (0) 2020.04.20
private  (0) 2020.04.20

- 인덱스를 사용했는데 왜 느릴까?

인덱스를 사용하면 데이터가 많아도 금방 조회가 되는 경우가 있는데, 대량 데이터를 조회할 때 인덱스를 사용하니 테이블 전체를 스캔할 때 보다 훨씬 느리다

 

ROWID

- 인덱스를 스캔하는 이유는, 검색 조건을 만족하는 소량의 데이터를 인덱스에서 빠르게 찾고 거기서 테이블 레코드를 찾아가기 위한 주소값인 ROWID를 얻기 위함

- ROWID는 데이터파일 번호, 오브젝트 번호, 블록 번호와 같은 물리적인 요소로 구성되어 있긴 하지만, 물리적이라기 보단 논리적인 주소에 가까움

물리적으로 직접 연결되지는 않고, 디스크 상의 실제 테이블 레코드를 찾아가기 위한 논리적인 주소를 담고 있음

 

I/O 매커니즘

- DBA는 디스크 상에서 블록을 찾기 위한 주소 정보임

- 매번 디스크에서 블록을 읽을 수 없으므로, I/O성능을 높이려면 버퍼캐시를 활용해야 한다. 그래서 블록을 읽을 때는 바로 디스크로 가기 전에 버퍼캐시 부터 찾음

- 인덱스 스캔 → 리프블록 → ROWID → DBA → 해시함수(해싱알고리즘) → 버퍼헤더(메모리상 주소 = 포인터) →버퍼블록 접근

 

=> ROWID가 가리키는 테이블 블록을 버퍼캐시에서 먼저 찾아보고, 못 찾을 때만 디스크에서 블록을 읽음

=> ROWID를 이용하는 테이블 액세스는 고비용임

 

 

클러스터링 팩터

- 클러스터링 팩터, CF, 군집성 계수

- 특정 컬럼을 기준으로 값을 값을 가지는 데이터가 서로 모여있는 정도

- CF가 좋은 컬럼에서 생성한 인덱스를 검색 효율이 매우 좋음

- 데이터들이 물리적으로 근접해 있으면 흩어져 있을 때보다 데이터를 찾는 속도가 빠름 (근접해 있으면 블록 I/O과정 없이 포인터로 바로 액세스 가능)

 

인덱스 손익분계점

- 인덱스 ROWID를 사용한 테이블 액세스는 고비용 구조임

- 읽어야 하는 데이터의 개수가 일정량을 넘는 순간, Index Range Scan보다 그냥 Table Full Scan이 더 빠름

- Table Full Scan은 항상 테이블 전체를 읽기 때문에, 한 테이블에서 몇 건을 조회하든 성능이 일정함

- 인덱스를 이용한 조회에서는 전체 데이터 중에서 몇 건을 조회하는냐에 따라 성능이 크게 달라짐

- 추출 건수가 늘어나면 인덱스 스캔량도 늘고, 테이블 랜덤 엑세스가 늘어나기 때문

 

- Table Full Scan은 시퀀셜 액세스, 인덱스는 랜덤 액세스

- Table Full Scan은 멀티블록I/O, 인덱스는 싱글블록I/O

 

인덱스 손익분기점

 

 

=> CF에 따라 달라지지만, 인덱스의 손익 분기점을 5~20%(전체데이터 대비 추출 건수)의 낮은 수준임

 

- 여기서 말하는 5~20% 수준의 손익 분기점을 10만건~100만건 사이의 테이블에나 적용되는 수치임

- 1000만건 수준의 테이블에서는 손익분기점이 더 낮아짐

- 10만건~100만건 테이블을 조회할 때에는 버퍼캐시에서 데이터를 찾을 가능성이 있지만.... 1000만건 테이블을 조회하는 경우 버퍼캐시에서 데이터를 찾을 가능성이 매우매우매우 낮아서, 거의 모든 데이터를 디스크에서 읽기 때문에, 손익분기점 자체가 의미가 없어져서, 인덱스를 이용하는 것 보다 그냥 Table Full Scan이 더 빠름

 

 

 

 

옵티마이저가 수행하는 자동 형변환 때문에 Index Range Scan을 못할 수 있음

SELECT *
FROM 고객
WHERE 생년월일 = 19930109
SELECT *
FROM 고객
WHERE TO_NUMBER(생년월일) = 19930109

- 첫번째 쿼리는 옵티마이저에 의해 두번째 쿼리로 수행됨

- 생년월일 컬럼은 문자형인데, WHERE절의 비교 값을 숫자인 19930109로 작성하여 문자형 인덱스 칼럼인 생년월일이 숫자형으로 가공되어서 Index Range Scan을 못함

- 오라클에서 숫자형과 문자형이 만나면 숫자형이 이김 → 문자형이 TO_NUMBER로 자동 형변환됨

 

SELECT *
FROM 고객
WHERE 가입일자 = '01-JAN-1993'

- 날짜형과 문자형이 만나면 날짜형이 이기므로 비교 값이 날짜형으로 변환되므로 Index Range Scan이 가능

 

SELECT *
FROM 고객
WHERE 고객번호 LIKE '9874%'
SELECT *
FROM 고객
WHERE TO_CHAR(고객번호) LIKE '9874%'

- 숫자형과 문자형이 만나면 숫자형이 이긴다고 했지만, LIKE연산에서는 다름

- LIKE는 문자형 연산자이기 때문에 이 경우에는 숫자형이 문자형으로 형 변환됨

- 숫자형 인덱스 칼럼 고객번호가 형변환되어 Index Range Scan 불가

 

- 옵티마이저의 형변환 기능에 의존하지 말고, 인덱스 컬럼 기준으로  WHERE절에서 반대편 컬럼 또는 값을 정확하게 명시적으로 형 변환해주기

- 형변환 함수를 사용하면 연산수가 늘어서 성능이 안 좋아질 거라 생각할 수 있지만, SQL의 성능은 그런 것에서 결정되지 않고 블록 I/O를 줄이는 것임

- 형변환 함수를 생략한다고 연산 횟수가 줄어드는 것도 아님

(개발자가 형 변환 함수를 생략해도 옵티마이저가 자동으로 형 변환함)

 

apache 웹 서버에 정적 파일을 캐싱하도록 하여 최초 접속 이후 재접속 시 로딩을 빠르게 해 줄 수 있음

mod_expiers.so

apache의 modules 디렉토리 > mod_expires.so 확인

https://httpd.apache.org/docs/2.2/ko/mod/mod_expires.html

 

httpd.conf 수정

- apache의 conf 디렉토리 > httpd.conf수정

LoadModule expires_module modules/mod_expires.so

- mod_expires모듈 주석이 되어있다면 주석 해제, 아예 없으면 추가

 

<IfModule mod_expires.c>
	ExpiresActive On
	ExpiresByType image/gif "access plus 1 month"
	ExpiresByType image/png "access plus 1 month"
	ExpiresByType image/jpeg "access plus 1 month"
	ExpiresByType text/css "access plus 1 month"
	ExpiresByType application/javascript "access plus 1 month"
    ...
    ...
</IfModule>

- 캐싱처리할 정적파일에 대한 설정 작성

- month는 캐싱을 처리한 기간으로 초단위까지 원하는 기간으로 변경이 가능

- apahce 재시작하면 반영완료

- Index Range Scan이 가능한 이유는 데이터가 정렬되어 있기 때문임

- 데이터가 정렬되어 있기 때문에 전체가 아닌 일부분만 읽다가 멈출 수 있음

- 인덱스 컬럼을 가공해도 인덱스를 사용할 수는 있지만, 찾고자 하는 데이터가 전체 구간에 흩어져 있어서 Index Range Scan이 불가능하거나 비효율적임

- 인덱스가 정렬되어 있다는 특성을 활용하여 소트 연산을 생략할 수 있는 효과를 가질 수 있음

 

Ex)

인덱스 : 장비번호+변경일자+변경순번

select *
from 상태변경이력
where 장비번호 = 'C'
and 변경일자 = '20200622'

- 해당 쿼리의 결과는 장비번호가 C이고 변경일자가 20200622인 것들을 변경순번 순으로 조회함

- 이때 ORDER BY연산을 하지 않음 (ORDER BY를 명시해도 수행하지 않음)

 

select *
from 상태변경이력
where 장비번호 = 'C'
and 변경일자 = '20200622'
order by 변경순번 desc

- 내림차순 정렬을 요구하는 경우 인덱스 스캔을 반대로 함

 

select *
from 상태변경이력
where 장비번호 = 'C'
order by 변경일자 || 변경순번

- 이런 경우 인덱스를 활용한 소팅 연산을 생략할 수 없음

- 인덱스에는 가공되지 않은 값을 저장했는데, 가공한 값( || )으로 정렬을 해달라 해서..

 

Ex

인덱스 : 주문일자+주문번호

select to_char(A.주문번호, 'FM000000') as 주문번호, A.업체번호, A.주문금액
from 주문 A
where A.주문일자 = dt
and A.주문번호 > NVL(next_ord_no, 0)
order by 주문번호

- 이 쿼리도 인덱스를 이용한 소팅 연산 생략 불가

- to_char로 가공된 값인 주문번호로 정렬을 해달라고 했기 때문

select to_char(A.주문번호, 'FM000000') as 주문번호, A.업체번호, A.주문금액
from 주문 A
where A.주문일자 = dt
and A.주문번호 > NVL(next_ord_no, 0)
order by A.주문번호

- order by의 주문번호에 A. 를 붙이면 인덱스를 이용한 소팅 생략 가능

 

 

Ex

인덱스 : 장비번호+변경일자+변경순번

select MIN(변경순번)
from 상태변경이력
where 장비번호 = 'C'
and 변경일자 = '20200622'

select MAX(변경순번)
from 상태변경이력
where 장비번호 = 'C'
and 변경일자 = '20200622'

- 최솟값/최대값을 구할 때 소팅 연산이 생략됨

- 최소값 : 수직적 탐색으로 가장 왼쪽으로 내려가서 읽는 첫 번째 레코드(FIRST ROW)

- 최댓값 : 수직적 탐색으로 가장 오른쪽으로 내려가서 읽는 첫 번째 레코드(FIRST ROW)

 

 

select NVL(MAX(TO_NUMBER(변경순번)), 0)
from 상태변경이력
where 장비번호 = 'C'
and 변경일자 = '20200622'

select NVL(TO_NUMBER(MAX(변경순번)), 0)
from 상태변경이력
where 장비번호 = 'C'
and 변경일자 = '20200622'

- 첫 번째 쿼리는 문자 타입의 변경순번 컬럼을 TO_NUMBER로 가공했기 때문에 소팅 연산 생략 불가

 

Index Range Scan VS Index Full Scan

- Leaf블록 스캔 시작점을 알 수 있냐 없냐

 

1. Index Range Scan

- Leaf 블록의 일부만 스캔

- 인덱스 컬럼 (선두 컬럼)을 가공하지 않아야 인덱스를 정상적으로 사용할 수 있음

- 정상적으로 사용 = 수직적 탐색을 통해 Leaf블록에서 스캔 시작점을 찾고 거기서부터 수평적 탐색을 하다가 멈추는 것

 

2. Index Full Scan

- 인덱스 컬럼을 가공해도 인덱스를 사용할 수는 있지만, 스캔 시작점을 찾을 수 없고, 모든 Leaf 블록을 전부 스캔해야 함

 

=> 인덱스 컬럼을 가공하면  Index Range Scan을 사용할 수 없음

- 인덱스 컬럼을 가공했을 때 인덱스를 정상적으로 사용할 수 없는 이유는 인덱스 스캔 시작점을 찾을 수 없기 때문

- Index Range Scan을 특정 Range만 스캔한다는 것으로 Range에는 시작점과 끝점이 있음

- 인덱스는 가공되지 않은 값이 저장되어 있는데, 가공된 값을 가지고 검색을 하려면 시작점을 특정 지을 수 없음

- OR와 IN은 옵티마이저의 쿼리 변환 기능을 통해 Range Scan이 가능할 수도 있음

where substr(생년월일, 5, 2) = '05' 
-- 5월에 태어난 사람, substr로 가공함

where nvl(주문수량, 0) < 100 
-- nvl로 가공함

where like '%대한%' 
-- '대한'으로 시작하면 Range Scan이 가능하지만 '대한'을 포함하는 값은 흩어져 있음

where (전화번호 = tel_no OR 고객명 = cust_nm)
-- 수직적 탐색을 통해 전화번호가 '01012345678' 이거나 이름이 '홍길동'인 어느 한 지점을 특정할 수 없음

where 전화번호 in (tel_no1, tel_no2)
-- in은 or와 같은 것임

 

인덱스 사용 조건

- Index Range Scan을 하기 위한 가장 첫 번째 조건은 인덱스 선두 컬럼이 가공되지 않은 상태로 조건절에 있어야 함

 

Ex:

인덱스 : [소속팀 + 사원명 + 연령] 

= 데이터를 소속팀 순으로 정렬하고, 소속팀이 같으면 사원명으로 정렬하고, 사원명도 같으면 연령으로 정렬함

select 사원번호, 소속팀, 연령, 입사일, 전화번호
from 사원
where 사원명 = '홍길동'

-- 인덱스 선두 컬럼인 소속팀이 조건절 첫 줄에 없으므로 Index Range Scan은 불가능
-- 이름이 같은 사원이더라도 소속팀이 같으면 멀리 떨어져 있으므로 Index Full Scan해야함 (Leaf 블록 전 구간에 흩어짐)

 

Ex:

인덱스 : [기준연도 + 과세구분코드 + 보고회차 + 실명확인번호]

select * from TXA1234
where 기준연도 = stdr_year
and substr(과세구분코드, 1, 4) = txtn_dcd
and 보고회차 = rpt_tmrd
and 실명확인번호 = rnm_cnfm_no

-- 인덱스 컬럼 중하나인 과세구분코드가 substr로 가공되었지만,
-- 인덱스 선두 컬럼인 기준연도는 가공되지 않은 상태로 조건절에 있어서 Index Range Scan이 가능

 

- 인덱스를 탄다라는 말은 Index Range Scan을 한다는 뜻인데, Index Range Scan을 한다고 무조건 성능이 좋은 것이라고 할 수 없음

- Index Range Scan을 하며 인덱스를 잘 타는 것 같아도, 실제로 인덱스를 정말 잘 활용하는지는 인덱스 리프 블록에서 스캔하는 양을 따져봐야 함

 

테이블에서 데이터를 찾는 두 가지 방법

1. 테이블 전체를 스캔

2. 인덱스를 이용 : 인덱스는 큰 테이블에서 소량의 데이터를 검색할 때 사용

 

인덱스 튜닝

1. 인덱스 스캔과정에서의 비효율을 줄이기 (인덱스 스캔 효율화, 소량만 스캔)

2. 테이블 액세스 횟수를 줄이기 (랜덤 액세스 최소화)

 

SQL 튜닝은 랜덤 I/O 줄이기

- 데이터베이스가 느린 이유는 디스크 I/O 때문임, 읽어야 하는 데이터량이 많고, 그 과정에서 디스크  I/O가 많이 발생할 때 느려짐

- 인덱스를 많이 사용하는 웹 시스템에서는 랜덤 I/O 가 중요

 

인덱스 구조 (B-Tree)

수직적 탐색 > 수평적 탐색 > 랜덤 액세스

- 인덱스는 대용량 테이블에서 필요한 소량의 데이터만 빠르고 효율적으로 액세스 하기 위해서 사용하는 오브젝트

- 테이블에서 인덱스 없이 데이터를 검색하려면, 테이블 전체를 스캔해야 하는데, 인덱스를 이용하면 일부만 스캔하고 멈출 수 있음

- 인덱스를 사용하면 Range Scan이 가능하고, 가능한 이유는 인덱스가 정렬되어있기 때문임

- Root블록과 Branch블록의 각 레코드는 하위 블록에 대한 주소 값을 가짐

- Leaf블록에 저장된 각 레코드는 키값 순으로 정렬되어 있고, 실제 테이블 레코드를 가리키는 주소 값인 ROWID를 가지고 있음

- 인덱스를 스캔하는 이유는 소량의 데이터만 빨리 찾고, ROWID를 얻기 위해서임

 

 

수직적 탐색 > 수평적 탐색 > ROWID로 테이블접근

 

수직적 탐색

- 인덱스 스캔의 시작점을 찾는 과정 (Root -> Branch -> Leaf)

- 수직적 탐색은 인덱스 스캔을 하기 위한 첫 번째 시작 지점을 찾는 과정

- 조건을 만족하는 첫 번째 레코드를 찾는 과정

 

수평적 탐색

- 데이터를 찾는 과정 (...... Leaf <-> Leaf <-> Leaf......)

- 인덱스 Leaf 블록은 서로 앞뒤 블록에 대한 주소 값을 가짐 (Double Linked List)

- 수직적 탐색으로 찾은 첫 번째 레코드에서부터 시작하여 수평적으로 탐색

- 조건절을 만족하는 모든 데이터를 찾기

- ROWID를 얻기

 

 

Table Full Scan

- 테이블에 속한 블록 전체를 읽어서 사용자가 원하는 데이터를 찾음

- 시퀀셜 액세스 + Multi Block I/O

- Table Full Scan는 피해야 한다는 인식이 많지만, 오히려 인덱스를 사용하는 것이 성능을 떨어뜨리는 경우가 더 많음

- 캐시에서 못 찾으면 한 번의 I/O Call로 인접한 수십-수백 개의 블록을 한꺼번에 불러오는 것이 좋음

 

Index Range Scan

- 인덱스 선두 컬럼이 가공되지 않은 상태로 조건절에 있어야 Index Range Scan이 가능

- 인덱스에서 일정량을 스캔하면서 얻은 ROWID로 테이블 레코드를 찾음

- 시퀀셜 액세스 + Multi Block I/O가 좋아도 소량의 데이터를 찾을 때 테이블 전체를 스캔하는  것은 비효율적임, 큰 테이블에서 소량의 데이터를 찾을 때는 반드시 인덱스를 사용해야 함

- 랜덤 액세스 + Single Block I/O

- 캐시에서 못 찾으면 레코드 하나를 읽기 위해 매번 I/O Call이 필요, 그러므로 많은 데이터를 읽을 때는 Table Full Scan보다 불리함

 

- 인덱스는 큰 테이블에서 아주 적은 일부의 데이터를 빨리 찾기 위한 도구일 뿐, 읽을 데이터가 일정 수량을 넘으면 인덱스보다는 Table Full Scan을 사용하는 것이 유리

- 인덱스를 안 타서 느린 경우보다 불필요하게 인덱스를 타서 느린 경우가 더 많더라...

- 예상 카디널리티가 일정량을 넘는 경우 인덱스 스캔을 하지 말도록... 힌트를 사용하여 Table Full Scan을 유도

 

Indes Full Scan

- 인덱스 선두 컬럼이 조건절에 없으면, Index Range Scan을 불가능하므로, 옵티마이저는 Table Full Scan을 고려함

- 그런데, 대용량 테이블이라 Table Full Scan에 대한 부담이 너무 크면, Index를 활용해야 할 필요가 있음

- 인덱스의 전체 크기는 테이블의 전체 크기보다 훨씬 적으므로, Index Range Scan을 할 수 없을 때, Table Full Scan보다는 Index Full Scan을 하는 것이 좋을 수 있음

- Index Scan을 통해 대부분의 레코드를 필터링하고 아주 일부만 테이블 액세스하는 상황이라면 크기가 큰 테이블을 Table Full Scan을 하기보다는 Index Full Scan 하는 것이 유리함

 

SELECT *
FROM  EMP
WHERE SAL > 9000
ORDER BY ENAME;

- 조건절 선두에 인덱스 선두 컬럼인 ENAME이 없어서 Index Range Scan은 불가능

- SAL > 9000인 레코드가 전체 테이블에서 아주 작은 일부라면 Table Full Scan보다는 Index Full Scan이 유리

- 하지만 이러한 Index Full Scan은 Index Range Scan을 하지 못한 차선책이므로, 수행 빈도가 낮은 SQL이라면 상관없지만, 그렇지 않다면 SAL칼럼이 인덱스 선두 컬럼인 인덱스를 생성하는 것이 좋음

SQL이 느린 이유

- SQL이 느린 이유는 디스크 I/O 때문임

- OS 또는 I/O 서브시스템이 I/O를 처리하는 동안 프로세스는 멈춰지고 기다림 (동기방식)

- 인터럽트 없이 실행되던 프로세스도 디스크에서 데이터를 읽어야 할 땐 CPU를 OS에 반환하고 잠시 WAITING상태가 되어 I/O완료를 기다림 -> I/O가 오래 걸리면 프로세스가 느려짐

- I/O튜닝이 안 된 시스템에서는 수많은 프로세스들이 동시다발적으로 발생하는 I/O Call 때문에 디스크 경합이 심해지고 그만큼 대기 시간도 길어짐 -> SQL이 느려짐 -> 디스크 I/O가 SQL 성능을 좌우함

 

 

데이터베이스 저장 구조

- 테이블스페이스 > 세그먼트 > 익스텐트 > 블록(페이지) > 데이터 레코드

(데이터베이스 > 테이블스페이스 > 데이터파일)

테이블스페이스 > 세그먼트 > 익스텐트 > 블록(페이지) > 데이터레코드

 

- 데이터를 저장하려면 테이블 스페이스를 생성해야 함

- 테이블 스페이스는 세그먼트를 담는 컨테이너로, 여러 개의 데이터 파일(디스크 상의 물리적인 OS파일)로 구성됨

 

세그먼트

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

- 파티션 구조가 아니라면 테이블도 하나의 세그먼트이고, 인덱스도 세그먼트

- 여러 익스텐트로 구성됨

 

익스텐트

- 공간을 확장하는 단위

- 테이블이나 인덱스에 데이터를 입력하다가 공간이 부족하면, 테이블 스페이스로부터 추가 익스텐트를 할당 받음

- 하나의 익스텐트는 하나의 테이블이 독점, 한 익스텐트에 있는 블록은 모두 같은 테이블의 블록

- 연속된 블록의 집합체

- 익스텐트 내 블록은 연속적이지만, 익스텐트 끼리는 연속된 공간이 아님

 

블록

- 실제 사용자가 입력한 레코드 데이터를 저장하는 공간

- 하나의 블록은 하나의 테이블이 독점, 한 블록에 있는 레코드들은 모두 같은 테이블의 레코드

- 데이터베이스에서 데이터를 읽고 쓰는 단위

- 데이터 I/O의 단위가 블록이므로, 테이블의 레코드 하나, 칼럼 하나만 읽고 싶어도 전체 블록을 통째로 읽음

 

데이터 파일

- 세그먼트에 공간이 부족하면 테이블 스페이스로부터 익스텐트를 부여받는데, 하나의 세그먼트를 구성하는 익스텐트들은 모두 같은 데이터 파일에 존재하지 않을 수 있고, 그럴 가능성이 더 높음

- 하나의 테이블 스페이스를 파일 경합을 줄이기 위해 여러 개의 데이터 파일로 구성됨

 

데이터 파일

 

테이블(세그먼트) > 익스텐트 > 블록 접근 방법

- 모든 데이터 블록은 디스크 상에서 몇 번 데이터 파일의 몇 번째 블록인지를 나타내는 고유 주소 값(DBA)을 가짐

- 데이터를 읽고 쓰는 단위가 블록이므로 데이터에 접근하려면 DBA부터 확인해야 함

- 인덱스를 사용하여 테이블 레코드를 읽을 때는 인덱스 ROWID을 이용함, ROWID는 DBA+로우 번호(블록 내 순번)로 구성됨

- 테이블을 스캔할 때는 테이블 세그먼트의 익스텐트 맵(세그먼트에 할당된 익스텐트 목록)을 이용

- 익스텐트 맵에서 각 익스텐트의 첫 번째 블록의 DBA를 알 수 있음

- 익스텐트는 연속된 블록의 집합이므로 첫 번째 블록을 읽고 뒤이어서 연속된 블록을 읽으면 됨

- 데이터 I/O의 단위가 블록이므로, 테이블의 레코드 하나, 칼럼 하나만 읽고 싶어도 전체 블록을 통째로 읽음

 

시퀀셜 액세스 VS 랜덤 액세스

- 테이블 또는 인덱스 블록을 액세스 하는 방식에는 시퀀셜 액세스와 랜덤 액세스가 있음

- 시퀀셜 : 논리적/물리적으로 연결된 순서에 따라 차례로 블록을 읽음, 주소 값에 따라 앞 또는 뒤로 순차적으로 읽음

- 랜덤 : 논리적/물리적 순서를 따르지 않고, 레코드 하나를 읽기 위해서 한 블록씩 접근

 

DB 버퍼 캐시

- DB의 성능은 디스크 I/O가 중요

- SQL을 수행하는 과정에서 계속해서 데이터 블록을 읽어오는데, 자주 읽는 블록을 매번 디스크에서 읽는 것을 비효율적

- 데이터 캐싱을 통해 디스크 I/O를 줄임

- DB 버퍼 캐시도 라이브러리 캐시(코드캐시) 처럼 SGA의 구성요소임

- 라이브러리 캐시가 SQL, 실행계획, 함수, 프로시저를 캐싱하는 코드 캐시라면, DB 버퍼 캐시는 데이터 블록을 캐싱해서 동일한 데이터 블록에 대한 반복적인 I/O를 줄여주는 데이터 캐시의 역할을 함

- 데이터 블록을 읽을 때는 항상 버퍼 캐시부터 탐색함

 

논리적 I/O, 물리적 I/O

- 논리적 I/O는 SQL를 처리하는 과정에서 버퍼 캐시에서 블록을 읽는 경우(메모리 버퍼 캐시에서 읽어 들임)

- 물리적 I/O는 원하는 블록이 버퍼 캐시에 없으므로 디스크에 액세스 하여 블록을 읽어옴(디스크에서 읽음 -> 느림)

- 물리적 I/O는 디스크에 접근하므로 메모리에 접근하는 논리적 I/O에 비해 매우 느림

- 블록을 읽을 때는 해당 블록을 먼저 버퍼 캐시에서 찾아보고 없을 때만 디스크에서 읽는데, 이때도 디스크에서 곧바로 읽는 게 아니라 먼저 버퍼 캐시에 적재하고서 읽음

- SQL 성능을 높이기 위해서는 논리적 I/O를 줄여야 함, SQL 튜닝을 해서 논리적 I/O를 줄여야 물리적 I/O도 줄어듬

- 논리적 I/O을 줄이는 것은, SQL을 튜닝해서 읽어 들이는 총 블록의 개수를 줄이면 됨

 

Single Block I/O VS Multi Block I/O

- 모든 데이터를 전부 버퍼 캐시에 적재할 수는 없으므로, 전체 데이터 중에서 일부만 캐시에 적재

- 캐시에서 찾지 못한 블록은 디스크 I/O를 통해 디스크에서 버퍼 캐시로 적재하고 읽음

- Single Block I/O : 메모리 적재 시 한 번에 한 블록씩 요청

- Multi Block I/O : 메모리 적재 시 한 번에 여러 블록씩 요청

- 기본적으로 인덱스와 테이블 블록 모두 Single Block I/O를 사용

- 인덱스는 소량의 데이터를 읽을 때 주로 사용(Single Block I/O) 반대로, 많은 데이터 블록을 읽을 때는 Multi Block I/O 가 효율적

- 인덱스를 이용하지 않고 테이블 전체를 스캔할 때 Multi Block I/O을 사용

- Multi Block I/O는 캐시에 찾지 못한 특정 블록을 읽으려고 디스크 상에 해당 블록과 인접하며 같은 익스텐트에 속한 블록들을 한꺼번에 읽어서 미리 적재

라이브러리 캐시

- SQL파싱, 최적화, 로우 소스 생성을 통해 생성한 내부 프로시저를 반복 재사용할 수 있도록 캐싱을 해두는 메모리 공간을 라이브러리 캐시라고 함

- 라이브러리 캐시는 SGA의 구성요소

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

 

소프트/하드 캐싱

- 사용자가 SQL을 전달하면 DBMS는 파싱 후에 해당 SQL이 라이브러리 캐시에 존재하는지를 먼저 확인

- 캐시에 있으면 바로 실행 단계로 : 소프트 캐싱

- 캐시에 없으면 최적화 및 로우 소스 생성 후에 실행 단계로 : 하드 캐싱

 

하드 캐싱

- SQL 최적화 과정은 하드한 작업임

- 옵티마이저의 SQL최적화 작업은 매우 많은 일을 수행함

- JOIN순서, JOIN방법, 테이블 스캔 방법, 인덱스 스캔 방법 등 옵티마이저가 계산해야 하는 연산이 엄청나게 많음

- 하나의 쿼리 수행을 위해서 후보가 되는 무수히 많은 실행 경로를 만들고, 딕셔너리와 통계정보를 가지고 효율성을 판단하는 최적화 작업은 하드 한 작업임

- 데이터베이스의 처리는 대부분 I/O 작업이지만, 하드 파싱은 CPU 자원까지 사용하는 무거운 작업임

- 이렇게 하드 파싱으로 만들어낸 내부 프로시저를 한 번만 사용하고 버리는 것은 비효율적이며, 그래서 라이브러리 캐시를 사용하여 재활용

 

SQL캐싱

- 함수, 프로시저, 트리거 등은 고유의 이름을 가지며, 컴파일 상태로 딕셔너리에 영구 보관됨

- 실행할 때 라이브러리 캐시에 적재하여 여러 사용자가 공유하여 사용함

- SQL은 이름이 따로 없어서, 전체 SQL텍스트가 이름의 역할을 함

- 처음 SQL을 실행할 때 최적화 과정을 거쳐 생성한 내부 프로시저를 라이브러리 캐시에 적재하여, 그다음부터 재사용함, 캐시 공간이 부족하면 버려졌다가 다음에 다시 실행할 때 똑같은 최적화 과정을 거쳐 캐시에 적재됨

 

SQL영구 저장

- DB2는 SQL도 함수, 프로시저처럼 영구로 저장을 하지만, 다른 DBMS는 그렇게 하지 않음

- SQL은 이름이 따로 없고, 텍스트 자체가 이름이기 때문에 텍스트의 작은 하나라도 변경이 되면 그 순간 다름 객체임

- DBMS에서 수행되는 일회성, 무효화된 SQL까지 모두 영구 저장하면 많은 공간이 필요하고, 그만큼 캐시에서 SQL을 찾는 속도도 느려짐