Notice
Recent Posts
Recent Comments
Link
반응형
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
Tags
- 멀티쓰레드
- 메소드
- 인스턴스
- 코딩
- 생성자
- 친절한 sql튜닝
- 친절한 SQL 튜닝
- 자바
- 클라이언트
- java
- 인덱스설계
- JavaScript
- 클래스
- 카카오코딩테스트
- SQL
- 백준
- Spring
- 오라클
- SQLP
- 컬렉션프레임워크
- db버퍼캐시
- springboot
- Swing
- 서버
- DB
- Oracle
- 상속
- socket
- 인덱스 튜닝
- 깃허브
Archives
- Today
- Total
프리 정보 컨텐츠
친절한 SQL 튜닝 - 2장 인덱스 기본 본문
반응형
2장 인덱스 기본
- 인덱스에 대한 개념만 알고있을 뿐 명확한 그림이 없는 상태에서 설계하고 개발하니 성능이 좋을 리 없다.
- 인덱스 탐색 과정이 수직적 탐색과 수평적 탐색 두 단계로 이루어진다는 사실에 대해 유념하며 알아보자.
2.1 인덱스 구조 및 탐색
데이터를 찾는 두 가지 방법
- 어떤 초등학교를 방문해 '홍길동' 학생을 찾는 방법은 두 가지다.
- 첫째는, 1학년 1반부터 6학년 맨 마지막 반까지 모든 교실을 돌며 홍길동 학생을 찾는 것이다.
- 둘째는, 교무실에서 학생 명부를 조회해 홍길동 학생이 있는 교실만 찾아가는 것이다.
- 결과는 홍길동 학생이 많으면 첫째, 몇 안 되면 후자가 빠르다.
- 데이터베이스 테이블에서 데이터를 찾는 방법도 크게 아래 두 가지에 해당한다.
- 테이블 전체를 스캔한다.
- 인덱스를 이용한다.
- 모든 교실을 돌며 학생을 찾는 경우가 전자이고, 학생부를 이용한 경우가 후자인 것이다.
- 테이블 전체 스캔과 관련해서는 튜인 요소가 많지 않지만, 인덱스와 관련해서는 튜닝 요소가 매우 많고 기법도 다양하다.
인덱스 튜닝의 두 가지 핵심요소
- 인덱스 스캔 과정에서 발생하는 비효율을 줄이는 것. 즉, 인덱스 스캔 효율화 튜닝
- 학생명부에서 시력이 1.0 ~ 1.5 인 홍길동 학생을 찾는 경우를 예로 들어보자.
- 학생명부를 이름과 시력순으로 정렬해 두었다면 소량만 스캔하면 되지만 학생명부를 시력과 이름순으로 정렬해 두었다면 똑같이 두명을 찾는데도 많은 양을 스캔해야 한다.
- 테이블 액세스 횟수를 줄이는 것. 즉, 랜덤 액세스 최소화 튜닝
- 시력이 1.0 ~ 1.5인 학생은 50명이고 이름이 홍길동인 학생은 5명이고, 시력이 1.0 ~ 1.5인 홍길동 학생은 2명이다.
- 이름과 시력만으로 각각 정렬한 학생명부가 있다면 어느 쪽이 더 효과적일까?
- 당연히, 이름순으로 정렬한 학생명부를 선택하면 교실을 다섯 번만 찾아가면 되기 때문에 유리하다.
- 어떤 학생명부를 사용하느냐에 따라 교실 방문 횟수는 다르다.
- 인덱스 스캔 효율화 튜닝과 랜덤 액세스 최소화 튜닝 둘 다 중요하지만, 더 중요한 하나를 고른다면 랜덤 액세스 최소화 튜닝이다.
- 홍길동 학생을 찾는 예만 하더라도 학생명부를 스캔하는 과정에도 비효율이 있겠지만, 학생명부에 없는 나머지 정보를 얻기 위해 직접 교실을 찾아가는 부담이 훨씬 더 크다.
- 이 예제가 말해주는건 인덱스 튜닝의 핵심요소이며 가장 중요한 결론은 SQL 튜닝은 랜덤 I/O와의 전쟁이다.
SQL 튜닝은 랜덤 I/O 와의 전쟁
- 데이터베이스 성능이 느린 이유는 디스크 I/O 때문이다.
- 읽어야 할 데이터량이 많고, 그 과정에 I/O가 많이 발생할 때 느리다.
2.1.1 인덱스 구조
- 인덱스 : 대용량 테이블에서 필요한 데이터만 빠르게 효율적으로 액세스하기 위해 사용하는 오브젝트이다.
- 즉, 책 뒤쪽에 있는 색인과 같은 역할을 한다.
- 데이터베이스에서도 인덱스 없이 데이터를 검색하려면, 테이블을 처음부터 끝까지 모두 읽어야 한다.
- 반면, 인덱스를 이용하면 일부만 읽고 멈출 수 있으며 범위 스캔이 가능하다. 범위 스캔이 가능한 이유는 인덱스가 정렬돼 있기 때문이다.
- DBMS는 일반적으로 B+Tree 인덱스를 사용하는데 구조는 위의 그림과 같다.
- 나무를 거꾸로 뒤집은 모양으로 뿌리(Root)가 위에 있고 가지(Branch)를 거쳐 맨 아래에 잎사귀(Leaf)가 존재한다.
- 리프 블록에 저장된 각 레코드는 키값 순으로 정렬돼 있을 뿐만 아니라 테이블 레코드를 가리키는 주소값 ROWID를 갖으며 인덱스 키값이 같으면 ROWID 순으로 정렬된다.
- 인덱스를 스캔하는 이유는 검색 조건을 만족하는 소량의 데이터를 빨리 찾고 거기서 ROWID를 얻기위함이다.
- ROWID = 데이터 블록 주소 + 로우 번호
- 데이터 블록 주소 = 데이터 파일 번호 + 블록 번호
- 블록 번호 : 데이터파일 내에서 부여한 상대적 순번
- 로우 번호 : 블록 내 순번
- 수직적 탐색 : 인덱스 스캔 시작지점을 찾는 과정
- 수평적 탐색 : 데이터를 찾는 과정
2.2.2 인덱스 수직적 탐색
- 인덱스 수직적 탐색은 정렬된 인덱스 레코드 중 조건을 만족하는 첫 번째 레코드를 찾는 과정이며 즉, 인덱스 스캔 시작지점을 찾는 과정이다.
- 루트를 포함해 브랜치 블록에 저장된 각 인덱스 레코드는 하위 블록에 대한 주소값을 갖는다. 이것이 루트에서 시작해 리프블록까지 수직적 탐색이 가능한 이유이다.
- 수직적 탐색 과정에 찾고자 하는 값보다 크거나 같은 값을 만나면, 바로 직전 레코드가 가리키는 하위 블록으로 이동한다.
- 인덱스를 수직적으로 탐색할 때는, 등산 푯말과 같은 역할을 한다.
- 이는, 조건을 만족하는 첫 번째 레코드가 목표 지점임을 기억하자.
2.2.3 인덱스 수평적 탐색
- 수직적 탐색을 통해 스캔 시작점을 찾았으면, 찾고자 하는 데이터가 더 안나타날 때까지 인덱스 리프 블록을 수평적으로 스캔한다. 이는 인덱스에서 본격적으로 데이터를 찾는 과정이다.
- 인덱스 리프 블록끼리는 서로 앞뒤 블록에 대한 주소값을 갖는 양방향 연결 리스트(double linked list) 구조이다.
- 인덱스를 수평적으로 탐색하는 이유는 조건절을 만족하는 데이터를 모두 찾고 ROWID를 얻기 위해서이다.
- 필요한 컬럼을 인덱스가 모두 갖고 있어 인덱스만 스캔하고 끝나는 경우도 있지만, 일반적으로 액세스를 스캔하고서 테이블도 액세스한다. (이 때 ROWID가 필요하다.)
2.2.4 결합 인덱스 구조와 탐색
SELECT
이름, 설명
FROM
사원
WHERE
성별 = '여자'
AND 이름 = '유관순'
- 인덱스를 성별 + 이름 순으로 구성한 경우
- 총 사원 50명 중 성별 = 여자인 레코드 25건을 찾고, 이름을 검사해 최종적으로 2명 출력 -> 25번의 검사
- 인덱스를 이름+ 성별 순으로 구성한 경우
- 총 사원 50명 중 이름 = '유관순'인 레코드 2건을 찾고, 거기서 성별을 검사해 최종적으로 2명 출력 -> 2번의 검사
- 여기서 알 수 있듯, 선택도가 낮은 '이름' 컬럼을 앞쪽에 두고 결합인덱스를 생성해야 검사횟수를 줄일 수 있어 성능에 유리하다고들 알고 있다.
- 하지만 엑셀의 데이터 필터 기능과 같은 위와 달리 B+TREE 인덱스는 엑셀처럼 평면 구조가 아니기에 어느 컬럼을 앞에 두든 일량에는 차이가 없다.
2.2 인덱스 기본 사용법
- 인덱스 기본 사용법은 인덱스를 Range Scan 하는 방법을 의미한다.
- 인덱스를 Range Scan 할 수 없게 되는 이유를 알고 나면, Range Scan 하는 방법도 자연스럽게 터득한다.
- 인덱스 컬럼을 가공하지 않아야 인덱스를 정상적으로 사용할 수 있다.
- 인덱스를 정상적으로 사용한다라는 표현은 리프 블록에서 스캔 시작점을 찾아 거기서부터 스캔하다가 중간에 멈추는 것을 의미한다. (리프블록 일부만 스캔하는 Index Range Scan)
- 인덱스 컬럼을 가공해도 인덱스를 사용할 수는 있지만, 스캔 시작점을 찾을 수 없고 멈출 수 없어 리프 블록 전체를 스캔해야만 한다. (일부가 아닌 전체 스캔하는 Index Full Scan)
2.2.1 인덱스를 Range Scan 할 수 없는 이유
모든 SQL 튜닝 책은 이와 같이 말한다. 인덱스 컬럼을 가공하면 인덱스를 정상적으로 사용할 수 없다.
- 기본 중에서 기본에 해당하므로 모르는 사람은 없으나, 이유에 대해서는 잘설명하지 못한다 이유는 무엇일까??
- 이를 설명하기 위해서는 앞서 말한 수직적, 수평적 탐색인 인덱스 탐색 과정을 알아야 한다.
- 인덱스 컬럼을 가공했을 때 인덱스를 정상적으로 사용할 수 없는 이유는 인덱스 스캔 시작점을 찾을 수 없기 때문이다.
- 쉽게 말하면 Index Range Scan은 범위를 인덱스에서 범위를 스캔한다는 뜻인데, 일정 범위를 스캔하려면 시작 지점과 끝지점이 존재해야한다.
아래 예제를 통해서 알아보자
- 2007년 1월 1일 이후에 태어난 첫 번째 학생을 찾는 과정이 인덱스로 말하면 수직적 탐색에 해당한다.
- 데이터베이스에서 아래 조건절을 처리할 때도 똑같은 과정을 거친다.
where 생년월일 between '20070101' and '20070131'
- 이번에는 년도와 상관없이 5월에 태어난 학생을 찾아보자. 스캔 시작점은 어디고 어디서 멈춰야 할까??
where substr(생년월일, 5, 2) = '05'
- 위와 같이 조건절을 처리할 경우 인덱스에는 가공되지 않은 값이 저장되어있어 스캔 시작점과 끝지점을 찾을 수 없다.
- 아래 예제도 전부 마찬가지로 해당한다.
where nvl(주문수량, 0) < 100
where 업체명 like '%대한%'
where (전화번호 = :tel_no OR 고객명 = :cust_nm)
where (전화번호 in :tel_no1, :tel_no2)
- SQL을 아래와 같이 UNION ALL 방식으로 작성하면 각 브랜치 별로 인덱스 스캔 시작점을 찾을 수 있으므로 Range Scan이 가능하다.
- 위와 같은 쿼리를 UNION ALL 방식으로 아래와 같이 작성하면 Index Range Scan 을 사용할 수 있다.
select *
from 고객
where 고객명 = :cust_nm
union all
select *
from 고객
where 전화번호 = :tel_no
and (고객명 <> :cust_nm or 고객명 is null)
select *
from 고객
where 전화번호 = :tel_no1
select *
from 고객
where 전화번호 = :tel_no2
- 번외로 IN 조건절에 대해서는 SQL 옵티마이저가 IN_List Iterator 방식을 사용하여, IN-List 개수만큼 Index Range Scan을 반복함으로써 UNION ALL 으로 변환한 것과 같은 효과를 얻을 수가 있다.
- 정리하자면 위에서 정의한 nvl이나 like 처럼 가공한 인덱스 컬럼을 정상적으로 Index를 사용할 수 없으나, OR 또는 IN 조건절은 옵티마이저의 쿼리 변환을 통해 Index Range Scan 으로 처리되기도 한다.
2.2.2 더 중요한 인덱스 사용 조건
- 인덱스를 정상적으로 사용하는데 있어 더 중요한 선행조건이 있다.
- 인덱스를 아래 그림과 같이 소속팀 + 사원명 + 연령 순으로 구성했다고 가정하자.
SELECT 사원번호, 소속팀, 연령, 입사일자, 전화번호
FROM 사원
WHERE 사원명 = '홍길동'
- 데이터를 소속팀 순으로 정렬하고, 소속팀이 같으면 사원명 순으로 정렬하고, 사원명까지 같으면 연령순으로 정렬한다는 의미이다.
- 이름이 같은 사원이더라도 소속팀이 다르면 서로 멀리 떨어져 인덱스 스캔 시작점을 찾을 수 없고, 어디서 멈춰야 할지도 알 수 없다. (인덱스 리프 블록을 처음부터 끝까지 모두 스캔해야 한다.)
- 인덱스를 Range Scan 하기 위한 가장 첫 번째 조건은 인덱스 선두 컬럼이 조건절에 있어야 한다는 사실이다.
아래 예제는 인덱스 컬럼을 가공했는데 Range Scan하는 경우의 예이다.
-- TXA1234_TX02 인덱스 : 기준연도 + 과세구분코드 + 보고회차 + 실명확인번호
SELECT * FROM
WHERE 기준연도 = :stdr_year
AND SUBSTR(과세구분코드, 1, 4) =: txtn_dcd
AND 보고회차 = :rpt_tmrd
AND 실명확인번호 = :rnm_cnfm_no
- 인덱스를 Range Scan 하려면 인덱스 선두 컬럼이 가공되지 않은 상태로 조건절에 있어야 한다.
- 반대로 말해 인덱스 선두 컬럼이 가공되지 않은 상태로 조건절에 있으면 인덱스 Range Scan은 무조건 가능하다.
- 위의 예제에서는 선두 컬럼인 '기준연도'를 가공하지 않았으므로 인덱스 Range Scan이 가능하다.
인덱스 잘타니까 튜닝 끝??
- SQL을 개발하면서 실행계획을 확인하지 않는 개발자가 대다수이다.
- 확인하더라도 인덱스를 타는지, 안타는지 확인하는 수준에 그친다. (인덱스를 잘 타면 성능도 문제없다고 생각한다.)
- 인덱스를 정말 잘 타는지는 인덱스 리프 블록에서 스캔하는 양을 따져봐야 알 수 있다.
SELECT *
FROM 주문상품
WHERE 주문일자 =:ord_dt
AND 상품번호 LIKE '%PING%'
SELECT *
FROM 주문상품
WHERE 주문일자 =:ord_dt
AND SUBSTR(상품번호, 1, 4) = 'PING';
- 위 SQL에서는 상품번호는 스캔 범위를 줄이는데 전혀 역할을 하지 못한다.
- 첫 번째 SQL은 중간 값 검색이기 때문이고, 두 번재 SQL은 컬럼을 가공했기 때문이다.
2.2.3 인덱스를 이용한 소트 연산 생략
- 인덱스를 Range Scan 할 수 있는 이유는 데이터가 정렬돼 있기 때문이다.
- 찾고자 하는 데이터가 정렬된 상태로 서로 모여있기 때문에 전체가 아닌 일정 부분만 읽다가 멈출 수 있다.
- 인덱스 컬럼을 가공해도 인덱스를 사용할 수 있지만, 찾고자 하는 데이터가 전체 구간에 흩어져 있기 때문에 Range Scan이 불가능하거나 비효율이 발생한다.
- 하지만 테이블과 달리 인덱스는 정렬돼어있기 때문에 Range Scan이 가능하고, 소트 연산 생략효과도 부수적으로 얻게 된다.
- 위 그림에서는 장비번호 + 변경일자 + 변경순번으로 PK가 구성되어있다.
SELECT *
FROM 상태변경이력
WHERE 장비번호 = 'C'
AND 변경일자 = '20180316'
- 위와 같이 장비번호와 변경일자를 모두 '=' 조건으로 검색할 때 PK 인덱스를 사용하면 결과집합은 변경순번 순으로 출력된다.
- 옵티마이저는 이런 속성을 활용해 SQL에 ORDER BY 가 있어도 정렬 연산을 따로 수행하지 않는다.
- PK 인덱스를 스캔하면서 출력한 결과집합은 어차피 변경순번 순으로 정렬되기 때문이다.
- 실행계획에 SORT ORDER BY 연산이 없음을 확인해보자.
2.2.4 ORDER BY 절에서 컬럼 가공
- 인덱스 컬럼을 가공하면 인덱스를 정상적으로 사용할 수 없다 라고 말하는 인덱스 컬럼은 대게 조건절에 사용한 컬럼을 의미한다.
- 그런데 조건절이 아닌 ORDER BY 또는 SELECT-LIST 에서 컬럼을 가공함으로 인해 인덱스를 정상적으로 사용할 수 없는 경우도 종종 있다.
아래와 같은 예제를 주의하자
SELECT *
FROM
(
SELECT TO_CHAR(A.주문번호, 'FM000000') AS 주문번호, A.업체번호, A.주문금액
FROM 주문 A
WHERE A.주문일자 = :dt
AND A.주문번호 > NVL(:next_ord_no, 0)
ORDER BY 주문번호
)
WHERE ROWNUM <= 30
- 위의 예제는 주문일자와 주문번호가 PK로 되어있음에도 정렬 연산(SORT ORDER BY) 연산이 실행계획에 나타난다. 이유는 무엇일까??
- 원인은 ORDER BY 에 기술한 주문번호는 순수 주문번호가 아니라 TO_CHAR 함수로 한 번 가공한 주문번호를 가리키기 때문이다. 해결방법은 간단하게 아래와 같이 작성하면 된다.
SELECT *
FROM
(
SELECT TO_CHAR(A.주문번호, 'FM000000') AS 주문번호, A.업체번호, A.주문금액
FROM 주문 A
WHERE A.주문일자 = :dt
AND A.주문번호 > NVL(:next_ord_no, 0)
ORDER BY A.주문번호
)
WHERE ROWNUM <= 30
- 간단히 ORDER BY 절에 주문 테이블의 주문번호를 가리키는 Alias 를 붙여주기만 하면 된다.
2.2.5 SELECT-LIST 에서 컬럼 가공
- 인덱스를 장비번호 + 변경일자 + 변경순번 순으로 구성하면, 아래와 같이 변경순번 최소값을 구할 때도 옵티마이저는 정렬 연산을 따로 수행하지 않는다.
- 수직적 탐색을 통해 조건을 만족하는 가장 왼쪽 지점으로 내려가서 첫 번째 읽는 레코드가 바로 최소값이기 때문이다.
SELECT MIN(변경순번)
FROM 상태변경이력
WHERE 장비번호 = 'C'
AND 변경일자 = '20180316'
- 인덱스의 리프블록은 양방향 구조(DOUBLE LINKED LIST) 구조를 가지고 있기 때문에 최대값도 이하동문이다.
- 하지만 만약에 SQL을 아래와 같이 작성하면 정렬 연산을 생략할 수 없다. 이유는 뭘까??
SELECT NVL(MAX(TO_NUMBER(변경순번)), 0)
FROM 상태변경이력
WHERE 장비번호 = 'C'
AND 변경일자 = '20180316'
- 인덱스에는 문자열 기준으로 정렬돼 있는데, 이를 숫자값으로 변경하여 최종 변경순번을 요구하였기 때문에 SORT 연산이 추가된다.
- 이를 아래와 같이 바꾸면 정렬 연산 없이 최종 변경순번을 쉽게 찾을 수 있다.
SELECT NVL(TO_NUMBER(MAX(변경순번)), 0)
FROM 상태변경이력
WHERE 장비번호 = 'C'
AND 변경일자 = '20180316'
- 하지만 이는 애초에 변경순번 데이터타입을 숫자형으로 설계했다면 이렇게 튜닝할 일도 생기지 않는다.
또 다른 예제를 통해 알아보자
- 아래 SQL은 장비구분코드 = 'A001'에 해당하는 장비들의 최종 변경일자를 스칼라 서브쿼리를 이용해 상태변경이력 테이블에서 조회하고 있다.
SELECT 장비번호, 장비명, 상태코드
, (SELECT MAX(변경일자)
FROM 상태변경이력
WHERE 장비번호 = P.장비번호) 최종변경일자
FROM 장비 P
WHERE 장비구분코드 = 'A001'
- 정렬 연산 없이 MIN/MAX, FIRST ROW 방식으로 실행하고 있음을 실행계획에서 확인할 수 있다.
- 하지만 최종변경일자에 더해 최종 변경순번까지 출력하고자 한다면 SQL을 어떻게 작성해야 할까?
SELECT 장비번호, 장비명, 상태코드
, (SELECT MAX(변경일자)
FROM 상태변경이력
WHERE 장비번호 = P.장비번호) 최종변경일자
, (SELECT MAX(변경순번)
FROM 상태변경이력
WHERE 장비번호 = P.장비번호
AND 변경일자 = (SELECT MAX(변경일자)
FROM 상태변경이력
WHERE 장비번호 = P.장비번호)) 최종변경순번
FROM 장비 P
WHERE 장비구분코드 = 'A001'
- 위와 같이 작성하면 상태변경이력 테이블을 여러 번 읽어야하므로 비효율적이며, PK 컬럼이 더 많아질 수록 복잡해지므로 성능도 나빠진다.
- 아래와 같이 작성하면 PK 컬럼이 많아져도 덜 복잡해지며 이런식으로 많이 코딩한다.
SELECT 장비번호, 장비명, 상태코드
, SUBSTR(최종이력, 1, 8) 최종변경일자
, SUBSTR(최종이력, 9) 최종변경순번
FROM (
SELECT 장비번호, 장비명, 상태코드
,(SELECT MAX(변경일자 || 변경순번)
FROM 상태변경이력
WHERE 장비번호 = P.장비번호) 최종이력
FROM 장비 P
WHERE 장비구분코드 = 'A001'
)
- 각 장비당 이력이 많지 않으면 크게 상관없지만, 이력이 많다면 성능에 문제가 될 수 있는 패턴이다. (인덱스 컬럼을 가공했기 때문)
- 각 장비에 속한 과거 이력 데이터를 모두 읽어야하므로 장비당 이력 레코드가 많다면 바로 직전에 본 복잡한 SQL보다 성능이 더 안 좋을 수 있다.
- 이럴 때 SQL은 어떻게 작성해야 효과적인지는 Top N 알고리즘을 설명하는 5장에서 소개한다.
2.2.6 자동 형변환
SELECT * FROM 고객
WHERE 생년월일 = 19821225
- 위의 SQL같은 경우에는 인덱스를 가공하지 않았음에도 불구하고 테이블 전체 스캔을 선택했다.
- 이유는 조건절 비교값을 숫자형으로 표현했기 때문에 나타난 현상이다. (아래와 같이 실행계획이 나타난다.)
SELECT * FROM 고객 WHERE TO_NUMBER(생년월일) = 19821225
- 제대로 사용하려면 아래와 같이 날짜 포맷을 정학히 지정해 주는 코딩 습관이 필요하다.
SELECT * FROM 고객 WHERE 가입일자 = TO_DATE('01-JAN-2018', 'DD-MON-YYYY')
- 숫자형과 문자형이 만날 경우 숫자형이 이기지만 연산자가 LIKE일 경우에는 다르다.
- LIKE 자체가 문자열 비교 연산자이므로 문자형 기준으로 숫자형 컬럼이 변환된다.
- 실무에서 일을 하다보면 LIKE 조건을 옵션 조건 처리 목적으로 사용하는 경우가 종종 있다.
- 예를 들어 거래 데이터 조회시 계좌번호는 사용자가 입력할 수도 안 할 수도 있는 옵션 조건인데, 이를 처리하기 위해서는 아래와 같이 두 개의 SQL이 필요하다.
-- 사용자가 계좌번호를 입력할 경우
SELECT * FROM 거래
WHERE 계좌번호 = :acnt_no
AND 거래일자 between :trd_dt1 and :trd_dt2
-- 사용자가 계좌번호를 입력하지 않을 경우
SELECT * FROM 거래
WHERE 거래일자 between :trd_dt1 and :trd_dt2
- 이를 하나의 SQL로 처리하기 위해 다양한 방식을 사용하는데 LIKE 조건을 사용하는 방식이 그 중 하나이다.
- 조회할 때 사용자가 계좌번호를 입력하지 않으면 :acnt_no 변수에 NULL 값을 입력함으로써 모든 계좌번호가 조회되도록 하는 것이다.
SELECT * FROM 거래
WHERE 계좌번호 LIKE :acno_no || '%'
AND 거래일자 between :trd_dt1 and :trd_dt2
- 위의 방식을 사용하면 LIKE와 BETWEEN 조건을 같이 사용하여 인덱스 스캔 효율이 안 좋아진다.
- 숫자형 컬럼을 LIKE 조건으로 검색하면 자동 형봔으로 인해 계좌번호가 아예 인덱스 액세스 조건으로 사용되지 못하기 때문이다.
반응형
Comments